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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package modelcache_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/juju/testing/factory"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	jc "github.com/juju/testing/checkers"
    14  	"github.com/prometheus/client_golang/prometheus"
    15  	gc "gopkg.in/check.v1"
    16  	"gopkg.in/juju/worker.v1"
    17  	"gopkg.in/juju/worker.v1/workertest"
    18  
    19  	"github.com/juju/juju/core/cache"
    20  	"github.com/juju/juju/core/life"
    21  	"github.com/juju/juju/state"
    22  	statetesting "github.com/juju/juju/state/testing"
    23  	"github.com/juju/juju/testing"
    24  	"github.com/juju/juju/worker/modelcache"
    25  )
    26  
    27  type WorkerSuite struct {
    28  	statetesting.StateSuite
    29  	logger loggo.Logger
    30  	config modelcache.Config
    31  	notify func(interface{})
    32  }
    33  
    34  var _ = gc.Suite(&WorkerSuite{})
    35  
    36  func (s *WorkerSuite) SetUpTest(c *gc.C) {
    37  	s.StateSuite.SetUpTest(c)
    38  	s.notify = nil
    39  	s.logger = loggo.GetLogger("test")
    40  	s.logger.SetLogLevel(loggo.TRACE)
    41  	s.config = modelcache.Config{
    42  		Logger:               s.logger,
    43  		StatePool:            s.StatePool,
    44  		PrometheusRegisterer: noopRegisterer{},
    45  		Cleanup:              func() {},
    46  	}
    47  }
    48  
    49  func (s *WorkerSuite) TestConfigMissingLogger(c *gc.C) {
    50  	s.config.Logger = nil
    51  	err := s.config.Validate()
    52  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    53  	c.Check(err, gc.ErrorMatches, "missing logger not valid")
    54  }
    55  
    56  func (s *WorkerSuite) TestConfigMissingStatePool(c *gc.C) {
    57  	s.config.StatePool = nil
    58  	err := s.config.Validate()
    59  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    60  	c.Check(err, gc.ErrorMatches, "missing state pool not valid")
    61  }
    62  
    63  func (s *WorkerSuite) TestConfigMissingRegisterer(c *gc.C) {
    64  	s.config.PrometheusRegisterer = nil
    65  	err := s.config.Validate()
    66  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    67  	c.Check(err, gc.ErrorMatches, "missing prometheus registerer not valid")
    68  }
    69  
    70  func (s *WorkerSuite) TestConfigMissingCleanup(c *gc.C) {
    71  	s.config.Cleanup = nil
    72  	err := s.config.Validate()
    73  	c.Check(err, jc.Satisfies, errors.IsNotValid)
    74  	c.Check(err, gc.ErrorMatches, "missing cleanup func not valid")
    75  }
    76  
    77  func (s *WorkerSuite) getController(c *gc.C, w worker.Worker) *cache.Controller {
    78  	var controller *cache.Controller
    79  	err := modelcache.ExtractCacheController(w, &controller)
    80  	c.Assert(err, jc.ErrorIsNil)
    81  	return controller
    82  }
    83  
    84  func (s *WorkerSuite) TestExtractCacheController(c *gc.C) {
    85  	var controller *cache.Controller
    86  	var empty worker.Worker
    87  	err := modelcache.ExtractCacheController(empty, &controller)
    88  	c.Assert(err.Error(), gc.Equals, "in should be a *modelcache.cacheWorker; got <nil>")
    89  }
    90  
    91  func (s *WorkerSuite) start(c *gc.C) worker.Worker {
    92  	config := s.config
    93  	config.Notify = s.notify
    94  	w, err := modelcache.NewWorker(config)
    95  	c.Assert(err, jc.ErrorIsNil)
    96  	s.AddCleanup(func(c *gc.C) {
    97  		workertest.CleanKill(c, w)
    98  	})
    99  	return w
   100  }
   101  
   102  func (s *WorkerSuite) captureModelEvents(c *gc.C) <-chan interface{} {
   103  	events := make(chan interface{})
   104  	s.notify = func(change interface{}) {
   105  		send := false
   106  		switch change.(type) {
   107  		case cache.ModelChange:
   108  			send = true
   109  		case cache.RemoveModel:
   110  			send = true
   111  		default:
   112  			// no-op
   113  		}
   114  		if send {
   115  			c.Logf("sending %#v", change)
   116  			select {
   117  			case events <- change:
   118  			case <-time.After(testing.LongWait):
   119  				c.Fatalf("change not processed by test")
   120  			}
   121  		}
   122  	}
   123  	return events
   124  }
   125  
   126  func (s *WorkerSuite) checkModel(c *gc.C, obtained interface{}, model *state.Model) {
   127  	change, ok := obtained.(cache.ModelChange)
   128  	c.Assert(ok, jc.IsTrue)
   129  
   130  	c.Check(change.ModelUUID, gc.Equals, model.UUID())
   131  	c.Check(change.Name, gc.Equals, model.Name())
   132  	c.Check(change.Life, gc.Equals, life.Value(model.Life().String()))
   133  	c.Check(change.Owner, gc.Equals, model.Owner().Name())
   134  	cfg, err := model.Config()
   135  	c.Assert(err, jc.ErrorIsNil)
   136  	c.Check(change.Config, jc.DeepEquals, cfg.AllAttrs())
   137  	status, err := model.Status()
   138  	c.Assert(err, jc.ErrorIsNil)
   139  	c.Check(change.Status, jc.DeepEquals, status)
   140  }
   141  
   142  func (s *WorkerSuite) nextChange(c *gc.C, changes <-chan interface{}) interface{} {
   143  	var obtained interface{}
   144  	select {
   145  	case obtained = <-changes:
   146  	case <-time.After(testing.LongWait):
   147  		c.Fatalf("no change")
   148  	}
   149  	return obtained
   150  }
   151  
   152  func (s *WorkerSuite) TestInitialModel(c *gc.C) {
   153  	changes := s.captureModelEvents(c)
   154  	s.start(c)
   155  
   156  	obtained := s.nextChange(c, changes)
   157  	expected, err := s.State.Model()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	s.checkModel(c, obtained, expected)
   160  }
   161  
   162  func (s *WorkerSuite) TestModelConfigChange(c *gc.C) {
   163  	changes := s.captureModelEvents(c)
   164  	w := s.start(c)
   165  	// discard initial event
   166  	s.nextChange(c, changes)
   167  
   168  	model, err := s.State.Model()
   169  	c.Assert(err, jc.ErrorIsNil)
   170  
   171  	c.Logf("\nupdating status\n\n")
   172  
   173  	// Add a different logging config value.
   174  	expected := "juju=INFO;missing=DEBUG;unit=DEBUG"
   175  	err = model.UpdateModelConfig(map[string]interface{}{
   176  		"logging-config": expected,
   177  	}, nil)
   178  	c.Assert(err, jc.ErrorIsNil)
   179  	s.State.StartSync()
   180  
   181  	// Wait for the change.
   182  	s.nextChange(c, changes)
   183  
   184  	controller := s.getController(c, w)
   185  	cachedModel, err := controller.Model(s.State.ModelUUID())
   186  	c.Assert(err, jc.ErrorIsNil)
   187  
   188  	c.Assert(cachedModel.Config()["logging-config"], gc.Equals, expected)
   189  }
   190  
   191  func (s *WorkerSuite) TestNewModel(c *gc.C) {
   192  	changes := s.captureModelEvents(c)
   193  	w := s.start(c)
   194  	// grab and discard the event for the initial model
   195  	s.nextChange(c, changes)
   196  
   197  	newState := s.Factory.MakeModel(c, nil)
   198  	s.State.StartSync()
   199  	defer newState.Close()
   200  
   201  	obtained := s.nextChange(c, changes)
   202  	expected, err := newState.Model()
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	s.checkModel(c, obtained, expected)
   205  
   206  	controller := s.getController(c, w)
   207  	c.Assert(controller.ModelUUIDs(), gc.HasLen, 2)
   208  }
   209  
   210  func (s *WorkerSuite) TestRemovedModel(c *gc.C) {
   211  	changes := s.captureModelEvents(c)
   212  	w := s.start(c)
   213  
   214  	// grab and discard the event for the initial model
   215  	s.nextChange(c, changes)
   216  
   217  	st := s.Factory.MakeModel(c, nil)
   218  	s.State.StartSync()
   219  	defer st.Close()
   220  
   221  	// grab and discard the event for the new model
   222  	s.nextChange(c, changes)
   223  
   224  	model, err := st.Model()
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	err = model.Destroy(state.DestroyModelParams{})
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	s.State.StartSync()
   229  
   230  	// grab and discard the event for the new model
   231  	obtained := s.nextChange(c, changes)
   232  	modelChange, ok := obtained.(cache.ModelChange)
   233  	c.Assert(ok, jc.IsTrue)
   234  	c.Assert(modelChange.Life, gc.Equals, life.Value("dying"))
   235  
   236  	err = st.ProcessDyingModel()
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	s.State.StartSync()
   239  
   240  	err = st.RemoveDyingModel()
   241  	c.Assert(err, jc.ErrorIsNil)
   242  	s.State.StartSync()
   243  
   244  	obtained = s.nextChange(c, changes)
   245  
   246  	change, ok := obtained.(cache.RemoveModel)
   247  	c.Assert(ok, jc.IsTrue)
   248  	c.Check(change.ModelUUID, gc.Equals, model.UUID())
   249  
   250  	// Controller just has the system state again.
   251  	controller := s.getController(c, w)
   252  	c.Assert(controller.ModelUUIDs(), jc.SameContents, []string{s.State.ModelUUID()})
   253  }
   254  
   255  func (s *WorkerSuite) captureApplicationEvents(c *gc.C) <-chan interface{} {
   256  	events := make(chan interface{})
   257  	s.notify = func(change interface{}) {
   258  		send := false
   259  		switch change.(type) {
   260  		case cache.ApplicationChange:
   261  			send = true
   262  		case cache.RemoveApplication:
   263  			send = true
   264  		default:
   265  			// no-op
   266  		}
   267  		if send {
   268  			c.Logf("sending %#v", change)
   269  			select {
   270  			case events <- change:
   271  			case <-time.After(testing.LongWait):
   272  				c.Fatalf("change not processed by test")
   273  			}
   274  		}
   275  	}
   276  	return events
   277  }
   278  
   279  func (s *WorkerSuite) TestAddApplication(c *gc.C) {
   280  	changes := s.captureApplicationEvents(c)
   281  	w := s.start(c)
   282  
   283  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{})
   284  	s.State.StartSync()
   285  
   286  	change := s.nextChange(c, changes)
   287  	obtained, ok := change.(cache.ApplicationChange)
   288  	c.Assert(ok, jc.IsTrue)
   289  	c.Check(obtained.Name, gc.Equals, app.Name())
   290  
   291  	controller := s.getController(c, w)
   292  	modUUIDs := controller.ModelUUIDs()
   293  	c.Check(modUUIDs, gc.HasLen, 1)
   294  
   295  	mod, err := controller.Model(modUUIDs[0])
   296  	c.Assert(err, jc.ErrorIsNil)
   297  
   298  	cachedApp, err := mod.Application(app.Name())
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	c.Check(cachedApp, gc.NotNil)
   301  }
   302  
   303  func (s *WorkerSuite) TestRemoveApplication(c *gc.C) {
   304  	changes := s.captureApplicationEvents(c)
   305  	w := s.start(c)
   306  
   307  	app := s.Factory.MakeApplication(c, &factory.ApplicationParams{})
   308  	s.State.StartSync()
   309  	_ = s.nextChange(c, changes)
   310  
   311  	controller := s.getController(c, w)
   312  	modUUID := controller.ModelUUIDs()[0]
   313  
   314  	c.Assert(app.Destroy(), jc.ErrorIsNil)
   315  	s.State.StartSync()
   316  
   317  	// We will either get our application event,
   318  	// or time-out after processing all the changes.
   319  	for {
   320  		change := s.nextChange(c, changes)
   321  		if _, ok := change.(cache.RemoveApplication); ok {
   322  			mod, err := controller.Model(modUUID)
   323  			c.Assert(err, jc.ErrorIsNil)
   324  
   325  			_, err = mod.Application(app.Name())
   326  			c.Check(errors.IsNotFound(err), jc.IsTrue)
   327  			return
   328  		}
   329  	}
   330  }
   331  
   332  type noopRegisterer struct {
   333  	prometheus.Registerer
   334  }
   335  
   336  func (noopRegisterer) Register(prometheus.Collector) error {
   337  	return nil
   338  }
   339  
   340  func (noopRegisterer) Unregister(prometheus.Collector) bool {
   341  	return true
   342  }