github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/aagent/watchers/watcher/watcher.go (about)

     1  // Copyright (c) 2020-2022, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package watcher
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/json"
    11  	"fmt"
    12  	"os"
    13  	"sync"
    14  	"text/template"
    15  	"time"
    16  
    17  	"github.com/choria-io/go-choria/aagent/model"
    18  	"github.com/choria-io/go-choria/backoff"
    19  	"github.com/choria-io/go-choria/internal/util"
    20  	"github.com/choria-io/go-choria/lifecycle"
    21  	governor "github.com/choria-io/go-choria/providers/governor/streams"
    22  	"github.com/tidwall/gjson"
    23  )
    24  
    25  type Watcher struct {
    26  	name             string
    27  	wtype            string
    28  	announceInterval time.Duration
    29  	statechg         chan struct{}
    30  	activeStates     []string
    31  	machine          model.Machine
    32  	succEvent        string
    33  	failEvent        string
    34  
    35  	deleteCb       func()
    36  	currentStateCb func() any
    37  	govCancel      func()
    38  
    39  	mu sync.Mutex
    40  }
    41  
    42  func NewWatcher(name string, wtype string, announceInterval time.Duration, activeStates []string, machine model.Machine, fail string, success string) (*Watcher, error) {
    43  	if name == "" {
    44  		return nil, fmt.Errorf("name is required")
    45  	}
    46  
    47  	if wtype == "" {
    48  		return nil, fmt.Errorf("watcher type is required")
    49  	}
    50  
    51  	if machine == nil {
    52  		return nil, fmt.Errorf("machine is required")
    53  	}
    54  
    55  	w := &Watcher{
    56  		name:             name,
    57  		wtype:            wtype,
    58  		announceInterval: announceInterval,
    59  		statechg:         make(chan struct{}, 1),
    60  		failEvent:        fail,
    61  		succEvent:        success,
    62  		machine:          machine,
    63  		activeStates:     activeStates,
    64  	}
    65  
    66  	return w, nil
    67  }
    68  
    69  func (w *Watcher) FactsFile() (string, error) {
    70  	tf, err := os.CreateTemp("", "")
    71  	if err != nil {
    72  		return "", err
    73  	}
    74  
    75  	_, err = tf.Write(w.machine.Facts())
    76  	if err != nil {
    77  		tf.Close()
    78  		os.Remove(tf.Name())
    79  		return "", err
    80  	}
    81  	tf.Close()
    82  
    83  	return tf.Name(), nil
    84  }
    85  
    86  func (w *Watcher) DataCopyFile() (string, error) {
    87  	dat := w.machine.Data()
    88  
    89  	j, err := json.Marshal(dat)
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  
    94  	tf, err := os.CreateTemp("", "")
    95  	if err != nil {
    96  		return "", err
    97  	}
    98  
    99  	_, err = tf.Write(j)
   100  	if err != nil {
   101  		tf.Close()
   102  		os.Remove(tf.Name())
   103  		return "", err
   104  	}
   105  	tf.Close()
   106  
   107  	return tf.Name(), nil
   108  }
   109  
   110  func (w *Watcher) CancelGovernor() {
   111  	w.mu.Lock()
   112  	defer w.mu.Unlock()
   113  
   114  	if w.govCancel != nil {
   115  		w.govCancel()
   116  	}
   117  }
   118  
   119  func (w *Watcher) sendGovernorLC(t lifecycle.GovernorEventType, name string, seq uint64) {
   120  	w.machine.PublishLifecycleEvent(lifecycle.Governor,
   121  		lifecycle.Identity(w.machine.Identity()),
   122  		lifecycle.Component(w.machine.Name()),
   123  		lifecycle.GovernorType(t),
   124  		lifecycle.GovernorSequence(seq),
   125  		lifecycle.GovernorName(name))
   126  }
   127  
   128  func (w *Watcher) EnterGovernor(ctx context.Context, name string, timeout time.Duration) (governor.Finisher, error) {
   129  	var err error
   130  
   131  	name, err = w.ProcessTemplate(name)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("could not parse governor name template: %s", err)
   134  	}
   135  
   136  	w.Infof("Using governor %s", name)
   137  
   138  	mgr, err := w.machine.JetStreamConnection()
   139  	if err != nil {
   140  		return nil, fmt.Errorf("JetStream connection not set")
   141  	}
   142  
   143  	w.Infof("Obtaining a slot in the %s Governor with %v timeout", name, timeout)
   144  	subj := util.GovernorSubject(name, w.machine.MainCollective())
   145  	gov := governor.New(name, mgr.NatsConn(), governor.WithLogger(w), governor.WithSubject(subj), governor.WithBackoff(backoff.FiveSec))
   146  
   147  	var gCtx context.Context
   148  	w.mu.Lock()
   149  	gCtx, w.govCancel = context.WithTimeout(ctx, timeout)
   150  	w.mu.Unlock()
   151  	defer w.CancelGovernor()
   152  
   153  	fin, seq, err := gov.Start(gCtx, fmt.Sprintf("Auto Agent  %s#%s @ %s", w.machine.Name(), w.name, w.machine.Identity()))
   154  	if err != nil {
   155  		w.Errorf("Could not obtain a slot in the Governor %s: %s", name, err)
   156  		w.sendGovernorLC(lifecycle.GovernorTimeoutEvent, name, 0)
   157  		return nil, err
   158  	}
   159  
   160  	w.sendGovernorLC(lifecycle.GovernorEnterEvent, name, seq)
   161  
   162  	finisher := func() error {
   163  		w.sendGovernorLC(lifecycle.GovernorExitEvent, name, seq)
   164  		return fin()
   165  	}
   166  
   167  	return finisher, nil
   168  }
   169  
   170  func (w *Watcher) ProcessTemplate(s string) (string, error) {
   171  	funcs, err := w.templateFuncMap()
   172  	if err != nil {
   173  		return "", err
   174  	}
   175  
   176  	t, err := template.New("machine").Funcs(funcs).Parse(s)
   177  	if err != nil {
   178  		return "", err
   179  	}
   180  
   181  	buf := bytes.NewBuffer([]byte{})
   182  
   183  	err = t.Execute(buf, nil)
   184  	if err != nil {
   185  		return "", err
   186  	}
   187  
   188  	return buf.String(), nil
   189  }
   190  
   191  func (w *Watcher) templateFuncMap() (template.FuncMap, error) {
   192  	facts := w.machine.Facts()
   193  	data := w.machine.Data()
   194  	jdata, err := json.Marshal(data)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	input := map[string]json.RawMessage{
   200  		"facts": facts,
   201  		"data":  jdata,
   202  	}
   203  
   204  	jinput, err := json.Marshal(input)
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	return util.FuncMap(map[string]any{
   210  		"lookup": func(q string, dflt any) any {
   211  			r := gjson.GetBytes(jinput, q)
   212  			if !r.Exists() {
   213  				w.Infof("Query did not match any data, returning default: %s", q)
   214  
   215  				return dflt
   216  			}
   217  
   218  			return r.Value()
   219  		},
   220  	}), nil
   221  }
   222  
   223  func (w *Watcher) Machine() model.Machine {
   224  	return w.machine
   225  }
   226  
   227  func (w *Watcher) SuccessEvent() string {
   228  	return w.succEvent
   229  }
   230  
   231  func (w *Watcher) FailEvent() string {
   232  	return w.failEvent
   233  }
   234  
   235  func (w *Watcher) StateChangeC() chan struct{} {
   236  	return w.statechg
   237  }
   238  
   239  func (w *Watcher) SetDeleteFunc(f func()) {
   240  	w.mu.Lock()
   241  	defer w.mu.Unlock()
   242  
   243  	w.deleteCb = f
   244  }
   245  
   246  func (w *Watcher) NotifyWatcherState(state any) {
   247  	w.machine.NotifyWatcherState(w.name, state)
   248  }
   249  
   250  func (w *Watcher) SuccessTransition() error {
   251  	if w.succEvent == "" {
   252  		return nil
   253  	}
   254  
   255  	w.Infof("success transitioning using %s event", w.succEvent)
   256  	return w.machine.Transition(w.succEvent)
   257  }
   258  
   259  func (w *Watcher) FailureTransition() error {
   260  	if w.failEvent == "" {
   261  		return nil
   262  	}
   263  
   264  	w.Infof("fail transitioning using %s event", w.failEvent)
   265  	return w.machine.Transition(w.failEvent)
   266  }
   267  
   268  func (w *Watcher) Transition(event string) error {
   269  	if event == "" {
   270  		return nil
   271  	}
   272  
   273  	return w.machine.Transition(event)
   274  }
   275  
   276  func (w *Watcher) NotifyStateChance() {
   277  	w.mu.Lock()
   278  	defer w.mu.Unlock()
   279  
   280  	select {
   281  	case w.statechg <- struct{}{}:
   282  	default:
   283  	}
   284  }
   285  
   286  func (w *Watcher) CurrentState() any {
   287  	if w.currentStateCb != nil {
   288  		return w.currentStateCb()
   289  	}
   290  
   291  	return nil
   292  }
   293  
   294  func (w *Watcher) AnnounceInterval() time.Duration {
   295  	return w.announceInterval
   296  }
   297  
   298  func (w *Watcher) Type() string {
   299  	return w.wtype
   300  }
   301  
   302  func (w *Watcher) Name() string {
   303  	return w.name
   304  }
   305  
   306  func (w *Watcher) Delete() {
   307  	w.mu.Lock()
   308  	defer w.mu.Unlock()
   309  
   310  	if w.deleteCb != nil {
   311  		w.deleteCb()
   312  	}
   313  }
   314  
   315  func (w *Watcher) ShouldWatch() bool {
   316  	if len(w.activeStates) == 0 {
   317  		return true
   318  	}
   319  
   320  	for _, e := range w.activeStates {
   321  		if e == w.machine.State() {
   322  			return true
   323  		}
   324  	}
   325  
   326  	return false
   327  }
   328  
   329  func (w *Watcher) Debugf(format string, args ...any) {
   330  	w.machine.Debugf(w.name, format, args...)
   331  }
   332  
   333  func (w *Watcher) Infof(format string, args ...any) {
   334  	w.machine.Infof(w.name, format, args...)
   335  }
   336  
   337  func (w *Watcher) Warnf(format string, args ...any) {
   338  	w.machine.Warnf(w.name, format, args...)
   339  }
   340  
   341  func (w *Watcher) Errorf(format string, args ...any) {
   342  	w.machine.Errorf(w.name, format, args...)
   343  }