github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/cache/controller.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cache
     5  
     6  import (
     7  	"sync"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/loggo"
    11  	"github.com/juju/pubsub"
    12  	"gopkg.in/tomb.v2"
    13  )
    14  
    15  // We use a package level logger here because the cache is only
    16  // ever in machine agents, so will never need to be in an alternative
    17  // logging context.
    18  
    19  // ControllerConfig is a simple config value struct for the controller.
    20  type ControllerConfig struct {
    21  	// Changes from the event source come over this channel.
    22  	// The changes channel must be non-nil.
    23  	Changes <-chan interface{}
    24  
    25  	// Notify is a callback function used primarily for testing, and is
    26  	// called by the controller main processing loop after processing a change.
    27  	// The change processed is passed in as the arg to notify.
    28  	Notify func(interface{})
    29  }
    30  
    31  // Validate ensures the controller has the right values to be created.
    32  func (c *ControllerConfig) Validate() error {
    33  	if c.Changes == nil {
    34  		return errors.NotValidf("nil Changes")
    35  	}
    36  	return nil
    37  }
    38  
    39  // Controller is the primary cached object.
    40  type Controller struct {
    41  	config  ControllerConfig
    42  	tomb    tomb.Tomb
    43  	mu      sync.Mutex
    44  	models  map[string]*Model
    45  	hub     *pubsub.SimpleHub
    46  	metrics *ControllerGauges
    47  }
    48  
    49  // NewController creates a new cached controller intance.
    50  // The changes channel is what is used to supply the cache with the changes
    51  // in order for the cache to be kept up to date.
    52  func NewController(config ControllerConfig) (*Controller, error) {
    53  	if err := config.Validate(); err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  	c := &Controller{
    57  		config: config,
    58  		models: make(map[string]*Model),
    59  		hub: pubsub.NewSimpleHub(&pubsub.SimpleHubConfig{
    60  			// TODO: (thumper) add a get child method to loggers.
    61  			Logger: loggo.GetLogger("juju.core.cache.hub"),
    62  		}),
    63  		metrics: createControllerGauges(),
    64  	}
    65  	c.tomb.Go(c.loop)
    66  	return c, nil
    67  }
    68  
    69  func (c *Controller) loop() error {
    70  	for {
    71  		select {
    72  		case <-c.tomb.Dying():
    73  			return nil
    74  		case change := <-c.config.Changes:
    75  			switch ch := change.(type) {
    76  			case ModelChange:
    77  				c.updateModel(ch)
    78  			case RemoveModel:
    79  				c.removeModel(ch)
    80  			case ApplicationChange:
    81  				c.updateApplication(ch)
    82  			case RemoveApplication:
    83  				c.removeApplication(ch)
    84  			}
    85  			if c.config.Notify != nil {
    86  				c.config.Notify(change)
    87  			}
    88  		}
    89  	}
    90  }
    91  
    92  // Report returns information that is used in the dependency engine report.
    93  func (c *Controller) Report() map[string]interface{} {
    94  	result := make(map[string]interface{})
    95  
    96  	c.mu.Lock()
    97  	for uuid, model := range c.models {
    98  		result[uuid] = model.Report()
    99  	}
   100  	c.mu.Unlock()
   101  
   102  	return result
   103  }
   104  
   105  // ModelUUIDs returns the UUIDs of the models in the cache.
   106  func (c *Controller) ModelUUIDs() []string {
   107  	c.mu.Lock()
   108  
   109  	result := make([]string, 0, len(c.models))
   110  	for uuid := range c.models {
   111  		result = append(result, uuid)
   112  	}
   113  
   114  	c.mu.Unlock()
   115  	return result
   116  }
   117  
   118  // Kill is part of the worker.Worker interface.
   119  func (c *Controller) Kill() {
   120  	c.tomb.Kill(nil)
   121  }
   122  
   123  // Wait is part of the worker.Worker interface.
   124  func (c *Controller) Wait() error {
   125  	return c.tomb.Wait()
   126  }
   127  
   128  // Model returns the model for the specified UUID.
   129  // If the model isn't found, a NotFoundError is returned.
   130  func (c *Controller) Model(uuid string) (*Model, error) {
   131  	c.mu.Lock()
   132  	defer c.mu.Unlock()
   133  
   134  	model, found := c.models[uuid]
   135  	if !found {
   136  		return nil, errors.NotFoundf("model %q", uuid)
   137  	}
   138  	return model, nil
   139  }
   140  
   141  // updateModel will add or update the model details as
   142  // described in the ModelChange.
   143  func (c *Controller) updateModel(ch ModelChange) {
   144  	c.mu.Lock()
   145  
   146  	model, found := c.models[ch.ModelUUID]
   147  	if !found {
   148  		model = newModel(c.metrics, c.hub)
   149  		c.models[ch.ModelUUID] = model
   150  	}
   151  	model.setDetails(ch)
   152  
   153  	c.mu.Unlock()
   154  }
   155  
   156  // removeModel removes the model from the cache.
   157  func (c *Controller) removeModel(ch RemoveModel) {
   158  	c.mu.Lock()
   159  	delete(c.models, ch.ModelUUID)
   160  	c.mu.Unlock()
   161  }
   162  
   163  // updateApplication adds or updates the application in the specified model.
   164  func (c *Controller) updateApplication(ch ApplicationChange) {
   165  	c.mu.Lock()
   166  
   167  	// While it is likely that we will receive a change update for the model
   168  	// before we get an update for the application for that model, but the
   169  	// cache needs to be resilient enough to make sure that we can handle
   170  	// the situation where this is not the case.
   171  	model, found := c.models[ch.ModelUUID]
   172  	if !found {
   173  		model = newModel(c.metrics, c.hub)
   174  		c.models[ch.ModelUUID] = model
   175  	}
   176  	model.updateApplication(ch)
   177  
   178  	c.mu.Unlock()
   179  }
   180  
   181  // removeApplication removes the application for the cached model.
   182  // If the cache does not have the model loaded for the application yet,
   183  // then it will not have the application cached.
   184  func (c *Controller) removeApplication(ch RemoveApplication) {
   185  	c.mu.Lock()
   186  
   187  	model, found := c.models[ch.ModelUUID]
   188  	if found {
   189  		model.removeApplication(ch)
   190  	}
   191  
   192  	c.mu.Unlock()
   193  }