github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/caasoperator/remotestate/watcher.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package remotestate
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/worker/v3/catacomb"
    11  
    12  	jworker "github.com/juju/juju/worker"
    13  )
    14  
    15  // logger is here to stop the desire of creating a package level logger.
    16  // Don't do this, instead pass one through as config to the worker.
    17  type logger interface{}
    18  
    19  var _ logger = struct{}{}
    20  
    21  type Logger interface {
    22  	Debugf(message string, args ...interface{})
    23  }
    24  
    25  // RemoteStateWatcher collects application information from separate state watchers,
    26  // and updates a Snapshot which is sent on a channel upon change.
    27  type RemoteStateWatcher struct {
    28  	config      WatcherConfig
    29  	application string
    30  
    31  	catacomb catacomb.Catacomb
    32  
    33  	out     chan struct{}
    34  	mu      sync.Mutex
    35  	current Snapshot
    36  }
    37  
    38  // WatcherConfig holds configuration parameters for the
    39  // remote state watcher.
    40  type WatcherConfig struct {
    41  	Logger             Logger
    42  	Application        string
    43  	CharmGetter        charmGetter
    44  	ApplicationWatcher applicationWatcher
    45  }
    46  
    47  // NewWatcher returns a RemoteStateWatcher that handles state changes pertaining to the
    48  // supplied application.
    49  func NewWatcher(config WatcherConfig) (*RemoteStateWatcher, error) {
    50  	w := &RemoteStateWatcher{
    51  		config:      config,
    52  		application: config.Application,
    53  		// Note: it is important that the out channel be buffered!
    54  		// The remote state watcher will perform a non-blocking send
    55  		// on the channel to wake up the observer. It is non-blocking
    56  		// so that we coalesce events while the observer is busy.
    57  		out:     make(chan struct{}, 1),
    58  		current: Snapshot{},
    59  	}
    60  	err := catacomb.Invoke(catacomb.Plan{
    61  		Site: &w.catacomb,
    62  		Work: func() error {
    63  			return w.loop()
    64  		},
    65  	})
    66  	if err != nil {
    67  		return nil, errors.Trace(err)
    68  	}
    69  	return w, nil
    70  }
    71  
    72  // Kill is part of the worker.Worker interface.
    73  func (w *RemoteStateWatcher) Kill() {
    74  	w.catacomb.Kill(nil)
    75  }
    76  
    77  // Wait is part of the worker.Worker interface.
    78  func (w *RemoteStateWatcher) Wait() error {
    79  	return w.catacomb.Wait()
    80  }
    81  
    82  func (w *RemoteStateWatcher) RemoteStateChanged() <-chan struct{} {
    83  	return w.out
    84  }
    85  
    86  func (w *RemoteStateWatcher) Snapshot() Snapshot {
    87  	w.mu.Lock()
    88  	defer w.mu.Unlock()
    89  	snapshot := w.current
    90  	return snapshot
    91  }
    92  
    93  func (w *RemoteStateWatcher) loop() (err error) {
    94  	defer func() {
    95  		if errors.IsNotFound(err) {
    96  			w.config.Logger.Debugf("application %q removed, terminating agent", w.application)
    97  			err = jworker.ErrTerminateAgent
    98  		}
    99  	}()
   100  
   101  	var requiredEvents int
   102  
   103  	var seenApplicationChange bool
   104  	applicationw, err := w.config.ApplicationWatcher.Watch(w.config.Application)
   105  	if err != nil {
   106  		return errors.Trace(err)
   107  	}
   108  	if err := w.catacomb.Add(applicationw); err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  	applicationChanges := applicationw.Changes()
   112  	requiredEvents++
   113  
   114  	var eventsObserved int
   115  	observedEvent := func(flag *bool) {
   116  		if !*flag {
   117  			*flag = true
   118  			eventsObserved++
   119  		}
   120  	}
   121  
   122  	// fire will, once the first event for each watcher has
   123  	// been observed, send a signal on the out channel.
   124  	fire := func() {
   125  		if eventsObserved != requiredEvents {
   126  			return
   127  		}
   128  		select {
   129  		case w.out <- struct{}{}:
   130  		default:
   131  		}
   132  	}
   133  
   134  	for {
   135  		select {
   136  		case <-w.catacomb.Dying():
   137  			return w.catacomb.ErrDying()
   138  		case _, ok := <-applicationChanges:
   139  			w.config.Logger.Debugf("got application change")
   140  			if !ok {
   141  				return errors.New("application watcher closed")
   142  			}
   143  			if err := w.applicationChanged(); err != nil {
   144  				return errors.Trace(err)
   145  			}
   146  			observedEvent(&seenApplicationChange)
   147  		}
   148  
   149  		// Something changed.
   150  		fire()
   151  	}
   152  }
   153  
   154  // applicationChanged responds to changes in the application.
   155  func (w *RemoteStateWatcher) applicationChanged() error {
   156  	info, err := w.config.CharmGetter.Charm(w.application)
   157  	if err != nil {
   158  		return errors.Trace(err)
   159  	}
   160  	w.mu.Lock()
   161  	w.current.CharmURL = info.URL
   162  	w.current.ForceCharmUpgrade = info.ForceUpgrade
   163  	w.current.CharmModifiedVersion = info.CharmModifiedVersion
   164  	w.mu.Unlock()
   165  	return nil
   166  }