github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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/loggo"
    11  	"gopkg.in/juju/worker.v1/catacomb"
    12  )
    13  
    14  var logger = loggo.GetLogger("juju.worker.caasoperator.remotestate")
    15  
    16  // RemoteStateWatcher collects application information from separate state watchers,
    17  // and updates a Snapshot which is sent on a channel upon change.
    18  type RemoteStateWatcher struct {
    19  	config      WatcherConfig
    20  	application string
    21  
    22  	catacomb catacomb.Catacomb
    23  
    24  	out     chan struct{}
    25  	mu      sync.Mutex
    26  	current Snapshot
    27  }
    28  
    29  // WatcherConfig holds configuration parameters for the
    30  // remote state watcher.
    31  type WatcherConfig struct {
    32  	Application        string
    33  	CharmGetter        charmGetter
    34  	ApplicationWatcher applicationWatcher
    35  }
    36  
    37  // NewWatcher returns a RemoteStateWatcher that handles state changes pertaining to the
    38  // supplied application.
    39  func NewWatcher(config WatcherConfig) (*RemoteStateWatcher, error) {
    40  	w := &RemoteStateWatcher{
    41  		config:      config,
    42  		application: config.Application,
    43  		// Note: it is important that the out channel be buffered!
    44  		// The remote state watcher will perform a non-blocking send
    45  		// on the channel to wake up the observer. It is non-blocking
    46  		// so that we coalesce events while the observer is busy.
    47  		out:     make(chan struct{}, 1),
    48  		current: Snapshot{},
    49  	}
    50  	err := catacomb.Invoke(catacomb.Plan{
    51  		Site: &w.catacomb,
    52  		Work: func() error {
    53  			return w.loop()
    54  		},
    55  	})
    56  	if err != nil {
    57  		return nil, errors.Trace(err)
    58  	}
    59  	return w, nil
    60  }
    61  
    62  // Kill is part of the worker.Worker interface.
    63  func (w *RemoteStateWatcher) Kill() {
    64  	w.catacomb.Kill(nil)
    65  }
    66  
    67  // Wait is part of the worker.Worker interface.
    68  func (w *RemoteStateWatcher) Wait() error {
    69  	return w.catacomb.Wait()
    70  }
    71  
    72  func (w *RemoteStateWatcher) RemoteStateChanged() <-chan struct{} {
    73  	return w.out
    74  }
    75  
    76  func (w *RemoteStateWatcher) Snapshot() Snapshot {
    77  	w.mu.Lock()
    78  	defer w.mu.Unlock()
    79  	snapshot := w.current
    80  	return snapshot
    81  }
    82  
    83  func (w *RemoteStateWatcher) loop() (err error) {
    84  	var requiredEvents int
    85  
    86  	var seenApplicationChange bool
    87  	applicationw, err := w.config.ApplicationWatcher.Watch(w.config.Application)
    88  	if err != nil {
    89  		return errors.Trace(err)
    90  	}
    91  	if err := w.catacomb.Add(applicationw); err != nil {
    92  		return errors.Trace(err)
    93  	}
    94  	applicationChanges := applicationw.Changes()
    95  	requiredEvents++
    96  
    97  	var eventsObserved int
    98  	observedEvent := func(flag *bool) {
    99  		if !*flag {
   100  			*flag = true
   101  			eventsObserved++
   102  		}
   103  	}
   104  
   105  	// fire will, once the first event for each watcher has
   106  	// been observed, send a signal on the out channel.
   107  	fire := func() {
   108  		if eventsObserved != requiredEvents {
   109  			return
   110  		}
   111  		select {
   112  		case w.out <- struct{}{}:
   113  		default:
   114  		}
   115  	}
   116  
   117  	for {
   118  		select {
   119  		case <-w.catacomb.Dying():
   120  			return w.catacomb.ErrDying()
   121  
   122  		case _, ok := <-applicationChanges:
   123  			logger.Debugf("got application change")
   124  			if !ok {
   125  				return errors.New("application watcher closed")
   126  			}
   127  			if err := w.applicationChanged(); err != nil {
   128  				return errors.Trace(err)
   129  			}
   130  			observedEvent(&seenApplicationChange)
   131  		}
   132  
   133  		// Something changed.
   134  		fire()
   135  	}
   136  }
   137  
   138  // applicationChanged responds to changes in the application.
   139  func (w *RemoteStateWatcher) applicationChanged() error {
   140  	url, force, _, ver, err := w.config.CharmGetter.Charm(w.application)
   141  	if err != nil {
   142  		return errors.Trace(err)
   143  	}
   144  	w.mu.Lock()
   145  	w.current.CharmURL = url
   146  	w.current.ForceCharmUpgrade = force
   147  	w.current.CharmModifiedVersion = ver
   148  	w.mu.Unlock()
   149  	return nil
   150  }