github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/cache/model.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  	"sort"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/pubsub"
    13  	"gopkg.in/tomb.v2"
    14  )
    15  
    16  const modelConfigChange = "model-config-change"
    17  
    18  func newModel(metrics *ControllerGauges, hub *pubsub.SimpleHub) *Model {
    19  	m := &Model{
    20  		metrics: metrics,
    21  		// TODO: consider a separate hub per model for better scalability
    22  		// when many models.
    23  		hub:          hub,
    24  		applications: make(map[string]*Application),
    25  	}
    26  	return m
    27  }
    28  
    29  // Model is a cached model in the controller. The model is kept up to
    30  // date with changes flowing into the cached controller.
    31  type Model struct {
    32  	metrics *ControllerGauges
    33  	hub     *pubsub.SimpleHub
    34  	mu      sync.Mutex
    35  
    36  	details      ModelChange
    37  	configHash   string
    38  	hashCache    *modelConfigHashCache
    39  	applications map[string]*Application
    40  }
    41  
    42  // Report returns information that is used in the dependency engine report.
    43  func (m *Model) Report() map[string]interface{} {
    44  	m.mu.Lock()
    45  	defer m.mu.Unlock()
    46  
    47  	return map[string]interface{}{
    48  		"name":              m.details.Owner + "/" + m.details.Name,
    49  		"life":              m.details.Life,
    50  		"application-count": len(m.applications),
    51  	}
    52  }
    53  
    54  // Application returns the application for the input name.
    55  // If the application is not found, a NotFoundError is returned.
    56  func (m *Model) Application(appName string) (*Application, error) {
    57  	m.mu.Lock()
    58  	defer m.mu.Unlock()
    59  
    60  	app, found := m.applications[appName]
    61  	if !found {
    62  		return nil, errors.NotFoundf("application %q", appName)
    63  	}
    64  	return app, nil
    65  
    66  }
    67  
    68  // updateApplication adds or updates the application in the model.
    69  func (m *Model) updateApplication(ch ApplicationChange) {
    70  	m.mu.Lock()
    71  
    72  	app, found := m.applications[ch.Name]
    73  	if !found {
    74  		app = newApplication(m.metrics, m.hub)
    75  		m.applications[ch.Name] = app
    76  	}
    77  	app.setDetails(ch)
    78  
    79  	m.mu.Unlock()
    80  }
    81  
    82  // removeApplication removes the application from the model.
    83  func (m *Model) removeApplication(ch RemoveApplication) {
    84  	m.mu.Lock()
    85  	delete(m.applications, ch.Name)
    86  	m.mu.Unlock()
    87  }
    88  
    89  // modelTopic prefixes the topic with the model UUID.
    90  func (m *Model) modelTopic(topic string) string {
    91  	return m.details.ModelUUID + ":" + topic
    92  }
    93  
    94  func (m *Model) setDetails(details ModelChange) {
    95  	m.mu.Lock()
    96  
    97  	m.details = details
    98  	hashCache, configHash := newModelConfigHashCache(m.metrics, details.Config)
    99  	if configHash != m.configHash {
   100  		m.configHash = configHash
   101  		m.hashCache = hashCache
   102  		m.hub.Publish(m.modelTopic(modelConfigChange), hashCache)
   103  	}
   104  
   105  	m.mu.Unlock()
   106  }
   107  
   108  // Config returns the current model config.
   109  func (m *Model) Config() map[string]interface{} {
   110  	m.mu.Lock()
   111  	m.metrics.ModelConfigReads.Inc()
   112  	m.mu.Unlock()
   113  	return m.details.Config
   114  }
   115  
   116  // WatchConfig creates a watcher for the model config.
   117  // If keys are specified, the watcher is only signals a change when
   118  // those keys change values. If no keys are specified, any change in the
   119  // config will trigger the watcher.
   120  func (m *Model) WatchConfig(keys ...string) *modelConfigWatcher {
   121  	// We use a single entry buffered channel for the changes.
   122  	// This allows the config changed handler to send a value when there
   123  	// is a change, but if that value hasn't been consumed before the
   124  	// next change, the second change is discarded.
   125  	sort.Strings(keys)
   126  	watcher := &modelConfigWatcher{
   127  		keys:    keys,
   128  		changes: make(chan struct{}, 1),
   129  	}
   130  	watcher.hash = m.hashCache.getHash(keys)
   131  	// Send initial event down the channel. We know that this will
   132  	// execute immediately because it is a buffered channel.
   133  	watcher.changes <- struct{}{}
   134  
   135  	unsub := m.hub.Subscribe(m.modelTopic(modelConfigChange), watcher.configChanged)
   136  
   137  	watcher.tomb.Go(func() error {
   138  		<-watcher.tomb.Dying()
   139  		unsub()
   140  		return nil
   141  	})
   142  
   143  	return watcher
   144  }
   145  
   146  type modelConfigHashCache struct {
   147  	metrics *ControllerGauges
   148  	config  map[string]interface{}
   149  	// The key to the hash map is the stringified keys of the watcher.
   150  	// They should be sorted and comma delimited.
   151  	hash map[string]string
   152  	mu   sync.Mutex
   153  }
   154  
   155  func newModelConfigHashCache(metrics *ControllerGauges, config map[string]interface{}) (*modelConfigHashCache, string) {
   156  	configCache := &modelConfigHashCache{
   157  		metrics: metrics,
   158  		config:  config,
   159  		hash:    make(map[string]string),
   160  	}
   161  	// Generate the hash for the entire config.
   162  	allHash := configCache.generateHash(nil)
   163  	configCache.hash[""] = allHash
   164  	return configCache, allHash
   165  }
   166  
   167  func (c *modelConfigHashCache) getHash(keys []string) string {
   168  	c.mu.Lock()
   169  	defer c.mu.Unlock()
   170  
   171  	key := strings.Join(keys, ",")
   172  	value, found := c.hash[key]
   173  	if found {
   174  		c.metrics.ModelHashCacheHit.Inc()
   175  		return value
   176  	}
   177  	value = c.generateHash(keys)
   178  	c.hash[key] = value
   179  	return value
   180  }
   181  
   182  func (c *modelConfigHashCache) generateHash(keys []string) string {
   183  	// We are generating a hash, so call it a miss.
   184  	c.metrics.ModelHashCacheMiss.Inc()
   185  
   186  	interested := c.config
   187  	if len(keys) > 0 {
   188  		interested = make(map[string]interface{})
   189  		for _, key := range keys {
   190  			if value, found := c.config[key]; found {
   191  				interested[key] = value
   192  			}
   193  		}
   194  	}
   195  	h, err := hash(interested)
   196  	if err != nil {
   197  		logger.Errorf("invariant error - model config should be yaml serializable and hashable, %v", err)
   198  		return ""
   199  	}
   200  	return h
   201  }
   202  
   203  type modelConfigWatcher struct {
   204  	keys    []string
   205  	hash    string
   206  	tomb    tomb.Tomb
   207  	changes chan struct{}
   208  	// We can't send down a closed channel, so protect the sending
   209  	// with a mutex and bool. Since you can't really even ask a channel
   210  	// if it is closed.
   211  	closed bool
   212  	mu     sync.Mutex
   213  }
   214  
   215  func (w *modelConfigWatcher) configChanged(topic string, value interface{}) {
   216  	hashCache, ok := value.(*modelConfigHashCache)
   217  	if !ok {
   218  		logger.Errorf("programming error, value not a *modelConfigHashCache")
   219  	}
   220  	hash := hashCache.getHash(w.keys)
   221  	if hash == w.hash {
   222  		// Nothing that we care about has changed, so we're done.
   223  		return
   224  	}
   225  	// Let the listener know.
   226  	w.mu.Lock()
   227  	defer w.mu.Unlock()
   228  	if w.closed {
   229  		return
   230  	}
   231  
   232  	select {
   233  	case w.changes <- struct{}{}:
   234  	default:
   235  		// Already a pending change, so do nothing.
   236  	}
   237  }
   238  
   239  // Changes is part of the core watcher definition.
   240  // The changes channel is never closed.
   241  func (w *modelConfigWatcher) Changes() <-chan struct{} {
   242  	return w.changes
   243  }
   244  
   245  // Kill is part of the worker.Worker interface.
   246  func (w *modelConfigWatcher) Kill() {
   247  	w.mu.Lock()
   248  	w.closed = true
   249  	close(w.changes)
   250  	w.mu.Unlock()
   251  	w.tomb.Kill(nil)
   252  }
   253  
   254  // Wait is part of the worker.Worker interface.
   255  func (w *modelConfigWatcher) Wait() error {
   256  	return w.tomb.Wait()
   257  }
   258  
   259  // Stop is currently required by the Resources wrapper in the apiserver.
   260  func (w *modelConfigWatcher) Stop() error {
   261  	w.Kill()
   262  	return w.Wait()
   263  }