github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/modelcache/worker.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelcache
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/kr/pretty"
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"gopkg.in/juju/worker.v1"
    13  	"gopkg.in/juju/worker.v1/catacomb"
    14  
    15  	"github.com/juju/juju/core/cache"
    16  	"github.com/juju/juju/core/life"
    17  	"github.com/juju/juju/core/status"
    18  	"github.com/juju/juju/state"
    19  	"github.com/juju/juju/state/multiwatcher"
    20  )
    21  
    22  // Config describes the necessary fields for NewWorker.
    23  type Config struct {
    24  	Logger               Logger
    25  	StatePool            *state.StatePool
    26  	PrometheusRegisterer prometheus.Registerer
    27  	Cleanup              func()
    28  	// Notify is used primarily for testing, and is passed through
    29  	// to the cache.Controller. It is called every time the controller
    30  	// processes an event.
    31  	Notify func(interface{})
    32  }
    33  
    34  // Validate ensures all the necessary values are specified
    35  func (c *Config) Validate() error {
    36  	if c.Logger == nil {
    37  		return errors.NotValidf("missing logger")
    38  	}
    39  	if c.StatePool == nil {
    40  		return errors.NotValidf("missing state pool")
    41  	}
    42  	if c.PrometheusRegisterer == nil {
    43  		return errors.NotValidf("missing prometheus registerer")
    44  	}
    45  	if c.Cleanup == nil {
    46  		return errors.NotValidf("missing cleanup func")
    47  	}
    48  	return nil
    49  }
    50  
    51  type cacheWorker struct {
    52  	config     Config
    53  	catacomb   catacomb.Catacomb
    54  	controller *cache.Controller
    55  	changes    chan interface{}
    56  	watcher    *state.Multiwatcher
    57  	mu         sync.Mutex
    58  }
    59  
    60  // NewWorker creates a new cacheWorker, and starts an
    61  // all model watcher.
    62  func NewWorker(config Config) (worker.Worker, error) {
    63  	if err := config.Validate(); err != nil {
    64  		return nil, errors.Trace(err)
    65  	}
    66  	w := &cacheWorker{
    67  		config:  config,
    68  		changes: make(chan interface{}),
    69  	}
    70  	controller, err := cache.NewController(
    71  		cache.ControllerConfig{
    72  			Changes: w.changes,
    73  			Notify:  config.Notify,
    74  		})
    75  	if err != nil {
    76  		return nil, errors.Trace(err)
    77  	}
    78  	w.controller = controller
    79  	if err := catacomb.Invoke(catacomb.Plan{
    80  		Site: &w.catacomb,
    81  		Work: w.loop,
    82  		Init: []worker.Worker{w.controller},
    83  	}); err != nil {
    84  		return nil, errors.Trace(err)
    85  	}
    86  	return w, nil
    87  }
    88  
    89  // Report returns information that is used in the dependency engine report.
    90  func (c *cacheWorker) Report() map[string]interface{} {
    91  	return c.controller.Report()
    92  }
    93  
    94  func (c *cacheWorker) loop() error {
    95  	defer c.config.Cleanup()
    96  	pool := c.config.StatePool
    97  
    98  	allWatcherStarts := prometheus.NewCounter(prometheus.CounterOpts{
    99  		Namespace: "juju_worker_modelcache",
   100  		Name:      "watcher_starts",
   101  		Help:      "The number of times the all model watcher has been started.",
   102  	})
   103  
   104  	collector := cache.NewMetricsCollector(c.controller)
   105  	c.config.PrometheusRegisterer.Register(collector)
   106  	c.config.PrometheusRegisterer.Register(allWatcherStarts)
   107  	defer c.config.PrometheusRegisterer.Unregister(allWatcherStarts)
   108  	defer c.config.PrometheusRegisterer.Unregister(collector)
   109  
   110  	watcherChanges := make(chan []multiwatcher.Delta)
   111  	// This worker needs to be robust with respect to the multiwatcher
   112  	// errors. If we get an unexpected error we should get a new allWatcher.
   113  	// We don't want a weird error in the multiwatcher taking down the apiserver,
   114  	// which is what would happen if this worker errors out.
   115  	// We do need to consider cache invalidation for multiwatcher entities
   116  	// that may be in our cache but when we restart the watcher, they aren't there.
   117  	// Cache invalidation is a hard problem, but here at least we should perhaps
   118  	// be able to do some form of mark and sweep. When we create a new watcher
   119  	// we should mark entities in the controller, and when we are done with the
   120  	// first call to Next(), which returns the state of the world, we can issue
   121  	// a sweep to remove anything that wasn't updated since the Mark.
   122  	// TODO: This is left for upcoming work.
   123  	var wg sync.WaitGroup
   124  	wg.Add(1)
   125  	defer func() {
   126  		c.mu.Lock()
   127  		// If we have been stopped before we have properly been started
   128  		// there may not be a watcher yet.
   129  		if c.watcher != nil {
   130  			c.watcher.Stop()
   131  		}
   132  		c.mu.Unlock()
   133  		wg.Wait()
   134  	}()
   135  	go func() {
   136  		// Ensure we don't leave the main loop until the goroutine is done.
   137  		defer wg.Done()
   138  		for {
   139  			c.mu.Lock()
   140  			select {
   141  			case <-c.catacomb.Dying():
   142  				c.mu.Unlock()
   143  				return
   144  			default:
   145  				// Continue through.
   146  			}
   147  			allWatcherStarts.Inc()
   148  			watcher := pool.SystemState().WatchAllModels(pool)
   149  			c.watcher = watcher
   150  			c.mu.Unlock()
   151  
   152  			err := c.processWatcher(watcher, watcherChanges)
   153  			if err == nil {
   154  				// We are done, so exit
   155  				watcher.Stop()
   156  				return
   157  			}
   158  			c.config.Logger.Errorf("watcher error, %v, getting new watcher", err)
   159  			watcher.Stop()
   160  		}
   161  	}()
   162  
   163  	for {
   164  		select {
   165  		case <-c.catacomb.Dying():
   166  			return c.catacomb.ErrDying()
   167  		case deltas := <-watcherChanges:
   168  			// Process changes and send info down changes channel
   169  			for _, d := range deltas {
   170  				if logger := c.config.Logger; logger.IsTraceEnabled() {
   171  					logger.Tracef(pretty.Sprint(d))
   172  				}
   173  				value := c.translate(d)
   174  				if value != nil {
   175  					select {
   176  					case c.changes <- value:
   177  					case <-c.catacomb.Dying():
   178  						return nil
   179  					}
   180  				}
   181  			}
   182  		}
   183  	}
   184  }
   185  
   186  func (c *cacheWorker) processWatcher(w *state.Multiwatcher, watcherChanges chan<- []multiwatcher.Delta) error {
   187  	for {
   188  		deltas, err := w.Next()
   189  		if err != nil {
   190  			if errors.Cause(err) == state.ErrStopped {
   191  				return nil
   192  			} else {
   193  				return errors.Trace(err)
   194  			}
   195  		}
   196  		select {
   197  		case <-c.catacomb.Dying():
   198  			return nil
   199  		case watcherChanges <- deltas:
   200  		}
   201  	}
   202  }
   203  
   204  func coreStatus(info multiwatcher.StatusInfo) status.StatusInfo {
   205  	return status.StatusInfo{
   206  		Status:  info.Current,
   207  		Message: info.Message,
   208  		Data:    info.Data,
   209  		Since:   info.Since,
   210  	}
   211  }
   212  
   213  func (c *cacheWorker) translate(d multiwatcher.Delta) interface{} {
   214  	id := d.Entity.EntityId()
   215  	switch id.Kind {
   216  	case "model":
   217  		if d.Removed {
   218  			return cache.RemoveModel{
   219  				ModelUUID: id.ModelUUID,
   220  			}
   221  		}
   222  		value, ok := d.Entity.(*multiwatcher.ModelInfo)
   223  		if !ok {
   224  			c.config.Logger.Errorf("unexpected type %T", d.Entity)
   225  			return nil
   226  		}
   227  		return cache.ModelChange{
   228  			ModelUUID: value.ModelUUID,
   229  			Name:      value.Name,
   230  			Life:      life.Value(value.Life),
   231  			Owner:     value.Owner,
   232  			Config:    value.Config,
   233  			Status:    coreStatus(value.Status),
   234  			// TODO: constraints, sla
   235  		}
   236  	case "application":
   237  		if d.Removed {
   238  			return cache.RemoveApplication{
   239  				ModelUUID: id.ModelUUID,
   240  				Name:      id.Id,
   241  			}
   242  		}
   243  		value, ok := d.Entity.(*multiwatcher.ApplicationInfo)
   244  		if !ok {
   245  			c.config.Logger.Errorf("unexpected type %T", d.Entity)
   246  			return nil
   247  		}
   248  		return cache.ApplicationChange{
   249  			ModelUUID:       value.ModelUUID,
   250  			Name:            value.Name,
   251  			Exposed:         value.Exposed,
   252  			CharmURL:        value.CharmURL,
   253  			Life:            life.Value(value.Life),
   254  			MinUnits:        value.MinUnits,
   255  			Constraints:     value.Constraints,
   256  			Config:          value.Config,
   257  			Subordinate:     value.Subordinate,
   258  			Status:          coreStatus(value.Status),
   259  			WorkloadVersion: value.WorkloadVersion,
   260  		}
   261  	default:
   262  		return nil
   263  	}
   264  }
   265  
   266  // Kill is part of the worker.Worker interface.
   267  func (c *cacheWorker) Kill() {
   268  	c.catacomb.Kill(nil)
   269  }
   270  
   271  // Wait is part of the worker.Worker interface.
   272  func (c *cacheWorker) Wait() error {
   273  	return c.catacomb.Wait()
   274  }