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

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package caasunitprovisioner_test
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock/testclock"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/testing"
    12  	jc "github.com/juju/testing/checkers"
    13  	"github.com/juju/utils"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/names.v2"
    16  	"gopkg.in/juju/worker.v1"
    17  	"gopkg.in/juju/worker.v1/workertest"
    18  	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    19  
    20  	apicaasunitprovisioner "github.com/juju/juju/api/caasunitprovisioner"
    21  	"github.com/juju/juju/apiserver/params"
    22  	"github.com/juju/juju/caas"
    23  	"github.com/juju/juju/core/application"
    24  	"github.com/juju/juju/core/constraints"
    25  	"github.com/juju/juju/core/life"
    26  	"github.com/juju/juju/core/status"
    27  	"github.com/juju/juju/core/watcher/watchertest"
    28  	"github.com/juju/juju/storage"
    29  	coretesting "github.com/juju/juju/testing"
    30  	"github.com/juju/juju/worker/caasunitprovisioner"
    31  )
    32  
    33  type WorkerSuite struct {
    34  	testing.IsolationSuite
    35  
    36  	config             caasunitprovisioner.Config
    37  	applicationGetter  mockApplicationGetter
    38  	applicationUpdater mockApplicationUpdater
    39  	serviceBroker      mockServiceBroker
    40  	containerBroker    mockContainerBroker
    41  	podSpecGetter      mockProvisioningInfoGetterGetter
    42  	lifeGetter         mockLifeGetter
    43  	unitUpdater        mockUnitUpdater
    44  	statusSetter       mockProvisioningStatusSetter
    45  
    46  	applicationChanges      chan []string
    47  	applicationScaleChanges chan struct{}
    48  	caasUnitsChanges        chan struct{}
    49  	caasOperatorChanges     chan struct{}
    50  	containerSpecChanges    chan struct{}
    51  	serviceDeleted          chan struct{}
    52  	serviceEnsured          chan struct{}
    53  	serviceUpdated          chan struct{}
    54  	clock                   *testclock.Clock
    55  }
    56  
    57  var _ = gc.Suite(&WorkerSuite{})
    58  
    59  var (
    60  	containerSpec = `
    61  containers:
    62    - name: gitlab
    63      image: gitlab/latest
    64      ports:
    65      - containerPort: 80
    66        protocol: TCP
    67      - containerPort: 443
    68      config:
    69        attr: foo=bar; fred=blogs
    70        foo: bar
    71  `[1:]
    72  
    73  	parsedSpec = caas.PodSpec{
    74  		Containers: []caas.ContainerSpec{{
    75  			Name:  "gitlab",
    76  			Image: "gitlab/latest",
    77  			Ports: []caas.ContainerPort{
    78  				{ContainerPort: 80, Protocol: "TCP"},
    79  				{ContainerPort: 443},
    80  			},
    81  			Config: map[string]interface{}{
    82  				"attr": "foo=bar; fred=blogs",
    83  				"foo":  "bar",
    84  			}},
    85  		}}
    86  
    87  	expectedServiceParams = &caas.ServiceParams{
    88  		PodSpec:      &parsedSpec,
    89  		ResourceTags: map[string]string{"foo": "bar"},
    90  		Placement:    "placement",
    91  		Constraints:  constraints.MustParse("mem=4G"),
    92  		Filesystems: []storage.KubernetesFilesystemParams{{
    93  			StorageName: "database",
    94  			Size:        100,
    95  		}},
    96  	}
    97  )
    98  
    99  func (s *WorkerSuite) SetUpTest(c *gc.C) {
   100  	s.IsolationSuite.SetUpTest(c)
   101  
   102  	s.applicationChanges = make(chan []string)
   103  	s.applicationScaleChanges = make(chan struct{})
   104  	s.caasUnitsChanges = make(chan struct{})
   105  	s.caasOperatorChanges = make(chan struct{})
   106  	s.containerSpecChanges = make(chan struct{}, 1)
   107  	s.serviceDeleted = make(chan struct{})
   108  	s.serviceEnsured = make(chan struct{})
   109  	s.serviceUpdated = make(chan struct{})
   110  
   111  	s.applicationGetter = mockApplicationGetter{
   112  		watcher:      watchertest.NewMockStringsWatcher(s.applicationChanges),
   113  		scaleWatcher: watchertest.NewMockNotifyWatcher(s.applicationScaleChanges),
   114  	}
   115  	s.applicationUpdater = mockApplicationUpdater{
   116  		updated: s.serviceUpdated,
   117  	}
   118  
   119  	s.podSpecGetter = mockProvisioningInfoGetterGetter{
   120  		watcher: watchertest.NewMockNotifyWatcher(s.containerSpecChanges),
   121  	}
   122  	s.podSpecGetter.setProvisioningInfo(apicaasunitprovisioner.ProvisioningInfo{
   123  		PodSpec:     containerSpec,
   124  		Tags:        map[string]string{"foo": "bar"},
   125  		Placement:   "placement",
   126  		Constraints: constraints.MustParse("mem=4G"),
   127  		Filesystems: []storage.KubernetesFilesystemParams{{
   128  			StorageName: "database",
   129  			Size:        100,
   130  		}},
   131  	})
   132  
   133  	s.unitUpdater = mockUnitUpdater{}
   134  
   135  	s.containerBroker = mockContainerBroker{
   136  		unitsWatcher:    watchertest.NewMockNotifyWatcher(s.caasUnitsChanges),
   137  		operatorWatcher: watchertest.NewMockNotifyWatcher(s.caasOperatorChanges),
   138  		podSpec:         &parsedSpec,
   139  	}
   140  	s.lifeGetter = mockLifeGetter{}
   141  	s.lifeGetter.setLife(life.Alive)
   142  	s.serviceBroker = mockServiceBroker{
   143  		ensured: s.serviceEnsured,
   144  		deleted: s.serviceDeleted,
   145  		podSpec: &parsedSpec,
   146  	}
   147  	s.statusSetter = mockProvisioningStatusSetter{}
   148  
   149  	s.config = caasunitprovisioner.Config{
   150  		ApplicationGetter:        &s.applicationGetter,
   151  		ApplicationUpdater:       &s.applicationUpdater,
   152  		ServiceBroker:            &s.serviceBroker,
   153  		ContainerBroker:          &s.containerBroker,
   154  		ProvisioningInfoGetter:   &s.podSpecGetter,
   155  		LifeGetter:               &s.lifeGetter,
   156  		UnitUpdater:              &s.unitUpdater,
   157  		ProvisioningStatusSetter: &s.statusSetter,
   158  	}
   159  }
   160  
   161  func (s *WorkerSuite) sendContainerSpecChange(c *gc.C) {
   162  	select {
   163  	case s.containerSpecChanges <- struct{}{}:
   164  	case <-time.After(coretesting.LongWait):
   165  		c.Fatal("timed out sending pod spec change")
   166  	}
   167  }
   168  
   169  func (s *WorkerSuite) TestValidateConfig(c *gc.C) {
   170  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   171  		config.ApplicationGetter = nil
   172  	}, `missing ApplicationGetter not valid`)
   173  
   174  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   175  		config.ApplicationUpdater = nil
   176  	}, `missing ApplicationUpdater not valid`)
   177  
   178  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   179  		config.ServiceBroker = nil
   180  	}, `missing ServiceBroker not valid`)
   181  
   182  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   183  		config.ContainerBroker = nil
   184  	}, `missing ContainerBroker not valid`)
   185  
   186  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   187  		config.ProvisioningInfoGetter = nil
   188  	}, `missing ProvisioningInfoGetter not valid`)
   189  
   190  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   191  		config.LifeGetter = nil
   192  	}, `missing LifeGetter not valid`)
   193  	s.testValidateConfig(c, func(config *caasunitprovisioner.Config) {
   194  		config.ProvisioningStatusSetter = nil
   195  	}, `missing ProvisioningStatusSetter not valid`)
   196  }
   197  
   198  func (s *WorkerSuite) testValidateConfig(c *gc.C, f func(*caasunitprovisioner.Config), expect string) {
   199  	config := s.config
   200  	f(&config)
   201  	w, err := caasunitprovisioner.NewWorker(config)
   202  	if err == nil {
   203  		workertest.DirtyKill(c, w)
   204  	}
   205  	c.Check(err, gc.ErrorMatches, expect)
   206  }
   207  
   208  func (s *WorkerSuite) TestStartStop(c *gc.C) {
   209  	w, err := caasunitprovisioner.NewWorker(s.config)
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	workertest.CheckAlive(c, w)
   212  	workertest.CleanKill(c, w)
   213  }
   214  
   215  func (s *WorkerSuite) setupNewUnitScenario(c *gc.C) worker.Worker {
   216  	w, err := caasunitprovisioner.NewWorker(s.config)
   217  	c.Assert(err, jc.ErrorIsNil)
   218  
   219  	s.podSpecGetter.SetErrors(nil, errors.NotFoundf("spec"))
   220  
   221  	select {
   222  	case s.applicationChanges <- []string{"gitlab"}:
   223  	case <-time.After(coretesting.LongWait):
   224  		c.Fatal("timed out sending applications change")
   225  	}
   226  
   227  	s.applicationGetter.scale = 1
   228  	select {
   229  	case s.applicationScaleChanges <- struct{}{}:
   230  	case <-time.After(coretesting.LongWait):
   231  		c.Fatal("timed out sending scale change")
   232  	}
   233  
   234  	// We seed a "not found" error above to indicate that
   235  	// there is not yet a pod spec; the broker should
   236  	// not be invoked.
   237  	s.sendContainerSpecChange(c)
   238  	select {
   239  	case <-s.serviceEnsured:
   240  		c.Fatal("service ensured unexpectedly")
   241  	case <-time.After(coretesting.ShortWait):
   242  	}
   243  
   244  	s.sendContainerSpecChange(c)
   245  	s.podSpecGetter.assertSpecRetrieved(c)
   246  	select {
   247  	case <-s.serviceEnsured:
   248  	case <-time.After(coretesting.LongWait):
   249  		c.Fatal("timed out waiting for service to be ensured")
   250  	}
   251  	s.statusSetter.CheckCall(c, 0, "SetOperatorStatus", "gitlab", status.Waiting, "ensuring", map[string]interface{}{"foo": "bar"})
   252  	select {
   253  	case <-s.serviceUpdated:
   254  	case <-time.After(coretesting.LongWait):
   255  		c.Fatal("timed out waiting for service to be updated")
   256  	}
   257  	return w
   258  }
   259  
   260  func (s *WorkerSuite) TestScaleChanged(c *gc.C) {
   261  	w := s.setupNewUnitScenario(c)
   262  	defer workertest.CleanKill(c, w)
   263  
   264  	s.applicationGetter.CheckCallNames(c, "WatchApplications", "WatchApplicationScale", "ApplicationScale", "ApplicationConfig")
   265  	s.podSpecGetter.CheckCallNames(c, "WatchPodSpec", "ProvisioningInfo", "ProvisioningInfo")
   266  	s.podSpecGetter.CheckCall(c, 0, "WatchPodSpec", "gitlab")
   267  	s.podSpecGetter.CheckCall(c, 1, "ProvisioningInfo", "gitlab") // not found
   268  	s.podSpecGetter.CheckCall(c, 2, "ProvisioningInfo", "gitlab")
   269  	s.lifeGetter.CheckCallNames(c, "Life")
   270  	s.lifeGetter.CheckCall(c, 0, "Life", "gitlab")
   271  	s.serviceBroker.CheckCallNames(c, "EnsureService", "Service")
   272  	s.serviceBroker.CheckCall(c, 0, "EnsureService",
   273  		"gitlab", expectedServiceParams, 1, application.ConfigAttributes{"juju-external-hostname": "exthost"})
   274  	s.serviceBroker.CheckCall(c, 1, "Service", "gitlab")
   275  
   276  	s.serviceBroker.ResetCalls()
   277  	// Add another unit.
   278  	s.applicationGetter.scale = 2
   279  	select {
   280  	case s.applicationScaleChanges <- struct{}{}:
   281  	case <-time.After(coretesting.LongWait):
   282  		c.Fatal("timed out sending scale change")
   283  	}
   284  
   285  	select {
   286  	case <-s.serviceEnsured:
   287  	case <-time.After(coretesting.LongWait):
   288  		c.Fatal("timed out waiting for service to be ensured")
   289  	}
   290  
   291  	newExpectedParams := *expectedServiceParams
   292  	newExpectedParams.PodSpec = &parsedSpec
   293  	s.serviceBroker.CheckCallNames(c, "EnsureService")
   294  	s.serviceBroker.CheckCall(c, 0, "EnsureService",
   295  		"gitlab", &newExpectedParams, 2, application.ConfigAttributes{"juju-external-hostname": "exthost"})
   296  
   297  	s.serviceBroker.ResetCalls()
   298  	// Delete a unit.
   299  	s.applicationGetter.scale = 1
   300  	select {
   301  	case s.applicationScaleChanges <- struct{}{}:
   302  	case <-time.After(coretesting.LongWait):
   303  		c.Fatal("timed out sending scale change")
   304  	}
   305  
   306  	select {
   307  	case <-s.serviceEnsured:
   308  	case <-time.After(coretesting.LongWait):
   309  		c.Fatal("timed out waiting for service to be ensured")
   310  	}
   311  
   312  	s.serviceBroker.CheckCallNames(c, "EnsureService")
   313  	s.serviceBroker.CheckCall(c, 0, "EnsureService",
   314  		"gitlab", &newExpectedParams, 1, application.ConfigAttributes{"juju-external-hostname": "exthost"})
   315  }
   316  
   317  func (s *WorkerSuite) TestNewPodSpecChange(c *gc.C) {
   318  	w := s.setupNewUnitScenario(c)
   319  	defer workertest.CleanKill(c, w)
   320  
   321  	s.serviceBroker.ResetCalls()
   322  
   323  	// Same spec, nothing happens.
   324  	s.sendContainerSpecChange(c)
   325  	s.podSpecGetter.assertSpecRetrieved(c)
   326  	select {
   327  	case <-s.serviceEnsured:
   328  		c.Fatal("service/unit ensured unexpectedly")
   329  	case <-time.After(coretesting.ShortWait):
   330  	}
   331  
   332  	var (
   333  		anotherSpec = `
   334  containers:
   335    - name: gitlab
   336      image-name: gitlab/latest
   337  `[1:]
   338  
   339  		anotherParsedSpec = caas.PodSpec{
   340  			Containers: []caas.ContainerSpec{{
   341  				Name:  "gitlab",
   342  				Image: "gitlab/latest",
   343  			}}}
   344  	)
   345  
   346  	s.serviceBroker.podSpec = &anotherParsedSpec
   347  
   348  	s.podSpecGetter.setProvisioningInfo(apicaasunitprovisioner.ProvisioningInfo{
   349  		PodSpec: anotherSpec,
   350  		Tags:    map[string]string{"foo": "bar"},
   351  	})
   352  	s.sendContainerSpecChange(c)
   353  	s.podSpecGetter.assertSpecRetrieved(c)
   354  
   355  	select {
   356  	case <-s.serviceEnsured:
   357  	case <-time.After(coretesting.LongWait):
   358  		c.Fatal("timed out waiting for service to be ensured")
   359  	}
   360  
   361  	expectedParams := &caas.ServiceParams{
   362  		PodSpec:      &anotherParsedSpec,
   363  		ResourceTags: map[string]string{"foo": "bar"},
   364  	}
   365  	s.serviceBroker.CheckCallNames(c, "EnsureService")
   366  	s.serviceBroker.CheckCall(c, 0, "EnsureService",
   367  		"gitlab", expectedParams, 1, application.ConfigAttributes{"juju-external-hostname": "exthost"})
   368  }
   369  
   370  func (s *WorkerSuite) TestNewPodSpecChangeCrd(c *gc.C) {
   371  	w := s.setupNewUnitScenario(c)
   372  	defer workertest.CleanKill(c, w)
   373  
   374  	s.serviceBroker.ResetCalls()
   375  
   376  	// Same spec, nothing happens.
   377  	s.sendContainerSpecChange(c)
   378  	s.podSpecGetter.assertSpecRetrieved(c)
   379  	select {
   380  	case <-s.serviceEnsured:
   381  		c.Fatal("service/unit ensured unexpectedly")
   382  	case <-time.After(coretesting.ShortWait):
   383  	}
   384  
   385  	float64Ptr := func(f float64) *float64 { return &f }
   386  
   387  	var (
   388  		anotherSpec = `
   389  crd:
   390    - group: kubeflow.org
   391      version: v1alpha2
   392      scope: Namespaced
   393      kind: TFJob
   394      validation:
   395        properties:
   396          tfReplicaSpecs:
   397            properties:
   398              Worker:
   399                properties:
   400                  replicas:
   401                    type: integer
   402                    minimum: 1
   403  `[1:]
   404  
   405  		anotherParsedSpec = caas.PodSpec{
   406  			CustomResourceDefinitions: []caas.CustomResourceDefinition{
   407  				{
   408  					Kind:    "TFJob",
   409  					Group:   "kubeflow.org",
   410  					Version: "v1alpha2",
   411  					Scope:   "Namespaced",
   412  					Validation: caas.CustomResourceDefinitionValidation{
   413  						Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
   414  							"tfReplicaSpecs": {
   415  								Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
   416  									"Worker": {
   417  										Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
   418  											"replicas": {
   419  												Type:    "integer",
   420  												Minimum: float64Ptr(1),
   421  											},
   422  										},
   423  									},
   424  								},
   425  							},
   426  						},
   427  					},
   428  				},
   429  			},
   430  		}
   431  	)
   432  
   433  	s.serviceBroker.podSpec = &anotherParsedSpec
   434  
   435  	s.podSpecGetter.setProvisioningInfo(apicaasunitprovisioner.ProvisioningInfo{
   436  		PodSpec: anotherSpec,
   437  		Tags:    map[string]string{"foo": "bar"},
   438  	})
   439  	s.sendContainerSpecChange(c)
   440  	s.podSpecGetter.assertSpecRetrieved(c)
   441  
   442  	select {
   443  	case <-s.serviceEnsured:
   444  	case <-time.After(coretesting.LongWait):
   445  		c.Fatal("timed out waiting for service to be ensured")
   446  	}
   447  
   448  	s.serviceBroker.CheckCallNames(c, "EnsureCustomResourceDefinition", "EnsureService")
   449  	s.serviceBroker.CheckCall(c, 0, "EnsureCustomResourceDefinition", "gitlab", &anotherParsedSpec)
   450  }
   451  
   452  func (s *WorkerSuite) TestScaleZero(c *gc.C) {
   453  	w := s.setupNewUnitScenario(c)
   454  	defer workertest.CleanKill(c, w)
   455  
   456  	s.serviceBroker.ResetCalls()
   457  	// Add another unit.
   458  	s.applicationGetter.scale = 2
   459  	select {
   460  	case s.applicationScaleChanges <- struct{}{}:
   461  	case <-time.After(coretesting.LongWait):
   462  		c.Fatal("timed out sending scale change")
   463  	}
   464  
   465  	select {
   466  	case <-s.serviceEnsured:
   467  	case <-time.After(coretesting.LongWait):
   468  		c.Fatal("timed out waiting for service to be ensured")
   469  	}
   470  	s.serviceBroker.ResetCalls()
   471  
   472  	// Now the scale down to 0.
   473  	s.applicationGetter.scale = 0
   474  	select {
   475  	case s.applicationScaleChanges <- struct{}{}:
   476  	case <-time.After(coretesting.LongWait):
   477  		c.Fatal("timed out sending scale change")
   478  	}
   479  
   480  	select {
   481  	case <-s.serviceEnsured:
   482  	case <-time.After(coretesting.LongWait):
   483  		c.Fatal("timed out waiting for service to be ensured")
   484  	}
   485  	s.serviceBroker.CheckCallNames(c, "EnsureService")
   486  	s.serviceBroker.CheckCall(c, 0, "EnsureService",
   487  		"gitlab", &caas.ServiceParams{}, 0, application.ConfigAttributes(nil))
   488  }
   489  
   490  func (s *WorkerSuite) TestUnitAllRemoved(c *gc.C) {
   491  	w := s.setupNewUnitScenario(c)
   492  	defer workertest.CleanKill(c, w)
   493  
   494  	s.serviceBroker.ResetCalls()
   495  	// Add another unit.
   496  	s.applicationGetter.scale = 2
   497  	select {
   498  	case s.applicationScaleChanges <- struct{}{}:
   499  	case <-time.After(coretesting.LongWait):
   500  		c.Fatal("timed out sending scale change")
   501  	}
   502  
   503  	select {
   504  	case <-s.serviceEnsured:
   505  	case <-time.After(coretesting.LongWait):
   506  		c.Fatal("timed out waiting for service to be ensured")
   507  	}
   508  	s.serviceBroker.ResetCalls()
   509  
   510  	s.podSpecGetter.SetErrors(apicaasunitprovisioner.ErrNoUnits)
   511  
   512  	select {
   513  	case s.applicationScaleChanges <- struct{}{}:
   514  	case <-time.After(coretesting.LongWait):
   515  		c.Fatal("timed out sending scale change")
   516  	}
   517  	select {
   518  	case <-s.serviceEnsured:
   519  	case <-time.After(coretesting.LongWait):
   520  		c.Fatal("timed out waiting for service to be ensured")
   521  	}
   522  	s.serviceBroker.CheckCallNames(c, "EnsureService")
   523  	s.serviceBroker.CheckCall(c, 0, "EnsureService",
   524  		"gitlab", &caas.ServiceParams{}, 0, application.ConfigAttributes(nil))
   525  }
   526  
   527  func (s *WorkerSuite) TestApplicationDeadRemovesService(c *gc.C) {
   528  	w := s.setupNewUnitScenario(c)
   529  	defer workertest.CleanKill(c, w)
   530  
   531  	s.serviceBroker.ResetCalls()
   532  	s.containerBroker.ResetCalls()
   533  
   534  	s.lifeGetter.SetErrors(errors.NotFoundf("application"))
   535  	select {
   536  	case s.applicationChanges <- []string{"gitlab"}:
   537  	case <-time.After(coretesting.LongWait):
   538  		c.Fatal("timed out sending application change")
   539  	}
   540  
   541  	select {
   542  	case <-s.serviceDeleted:
   543  	case <-time.After(coretesting.LongWait):
   544  		c.Fatal("timed out waiting for service to be deleted")
   545  	}
   546  
   547  	s.serviceBroker.CheckCallNames(c, "UnexposeService", "DeleteService")
   548  	s.serviceBroker.CheckCall(c, 0, "UnexposeService", "gitlab")
   549  	s.serviceBroker.CheckCall(c, 1, "DeleteService", "gitlab")
   550  }
   551  
   552  func (s *WorkerSuite) TestWatchApplicationDead(c *gc.C) {
   553  	w, err := caasunitprovisioner.NewWorker(s.config)
   554  	c.Assert(err, jc.ErrorIsNil)
   555  	defer workertest.CleanKill(c, w)
   556  
   557  	s.lifeGetter.setLife(life.Dead)
   558  	select {
   559  	case s.applicationChanges <- []string{"gitlab"}:
   560  	case <-time.After(coretesting.LongWait):
   561  		c.Fatal("timed out sending applications change")
   562  	}
   563  
   564  	select {
   565  	case s.applicationScaleChanges <- struct{}{}:
   566  		c.Fatal("unexpected watch for application scale")
   567  	case <-time.After(coretesting.ShortWait):
   568  	}
   569  
   570  	workertest.CleanKill(c, w)
   571  	// There should just be the initial watch call, no subsequent calls to watch/get scale etc.
   572  	s.applicationGetter.CheckCallNames(c, "WatchApplications")
   573  }
   574  
   575  func (s *WorkerSuite) TestRemoveApplicationStopsWatchingApplicationScale(c *gc.C) {
   576  	w, err := caasunitprovisioner.NewWorker(s.config)
   577  	c.Assert(err, jc.ErrorIsNil)
   578  	defer workertest.CleanKill(c, w)
   579  
   580  	select {
   581  	case s.applicationChanges <- []string{"gitlab"}:
   582  	case <-time.After(coretesting.LongWait):
   583  		c.Fatal("timed out sending applications change")
   584  	}
   585  
   586  	// Check that the gitlab worker is running or not;
   587  	// given it time to startup.
   588  	shortAttempt := &utils.AttemptStrategy{
   589  		Total: coretesting.LongWait,
   590  		Delay: 10 * time.Millisecond,
   591  	}
   592  	running := false
   593  	for a := shortAttempt.Start(); a.Next(); {
   594  		_, running = caasunitprovisioner.AppWorker(w, "gitlab")
   595  		if running {
   596  			break
   597  		}
   598  	}
   599  	c.Assert(running, jc.IsTrue)
   600  
   601  	// Add an additional app worker so we can check that the correct one is accessed.
   602  	caasunitprovisioner.NewAppWorker(w, "mysql")
   603  
   604  	s.lifeGetter.SetErrors(errors.NotFoundf("application"))
   605  	select {
   606  	case s.applicationChanges <- []string{"gitlab"}:
   607  	case <-time.After(coretesting.LongWait):
   608  		c.Fatal("timed out sending applications change")
   609  	}
   610  
   611  	select {
   612  	case <-s.serviceDeleted:
   613  	case <-time.After(coretesting.LongWait):
   614  		c.Fatal("timed out waiting for service to be deleted")
   615  	}
   616  
   617  	// The mysql worker should still be running.
   618  	_, ok := caasunitprovisioner.AppWorker(w, "mysql")
   619  	c.Assert(ok, jc.IsTrue)
   620  
   621  	// Check that the gitlab worker is running or not;
   622  	// given it time to shutdown.
   623  	for a := shortAttempt.Start(); a.Next(); {
   624  		_, running = caasunitprovisioner.AppWorker(w, "gitlab")
   625  		if !running {
   626  			break
   627  		}
   628  	}
   629  	c.Assert(running, jc.IsFalse)
   630  	workertest.CheckKilled(c, s.applicationGetter.scaleWatcher)
   631  }
   632  
   633  func (s *WorkerSuite) TestWatcherErrorStopsWorker(c *gc.C) {
   634  	w, err := caasunitprovisioner.NewWorker(s.config)
   635  	c.Assert(err, jc.ErrorIsNil)
   636  	defer workertest.DirtyKill(c, w)
   637  
   638  	s.applicationGetter.scale = 1
   639  	select {
   640  	case s.applicationChanges <- []string{"gitlab"}:
   641  	case <-time.After(coretesting.LongWait):
   642  		c.Fatal("timed out sending applications change")
   643  	}
   644  
   645  	select {
   646  	case s.applicationScaleChanges <- struct{}{}:
   647  	case <-time.After(coretesting.LongWait):
   648  		c.Fatal("timed out sending scale change")
   649  	}
   650  
   651  	s.podSpecGetter.watcher.KillErr(errors.New("splat"))
   652  	workertest.CheckKilled(c, s.podSpecGetter.watcher)
   653  	workertest.CheckKilled(c, s.applicationGetter.watcher)
   654  	err = workertest.CheckKilled(c, w)
   655  	c.Assert(err, gc.ErrorMatches, "splat")
   656  }
   657  
   658  func (s *WorkerSuite) TestUnitsChange(c *gc.C) {
   659  	w, err := caasunitprovisioner.NewWorker(s.config)
   660  	c.Assert(err, jc.ErrorIsNil)
   661  	defer workertest.DirtyKill(c, w)
   662  
   663  	select {
   664  	case s.applicationChanges <- []string{"gitlab"}:
   665  	case <-time.After(coretesting.LongWait):
   666  		c.Fatal("timed out sending applications change")
   667  	}
   668  	defer workertest.CleanKill(c, w)
   669  
   670  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   671  		if len(s.containerBroker.Calls()) > 0 {
   672  			break
   673  		}
   674  	}
   675  	s.containerBroker.CheckCallNames(c, "WatchUnits", "WatchOperator")
   676  
   677  	s.assertUnitChange(c, status.Allocating, status.Allocating)
   678  	s.assertUnitChange(c, status.Allocating, status.Unknown)
   679  }
   680  
   681  func (s *WorkerSuite) TestOperatorChange(c *gc.C) {
   682  	w, err := caasunitprovisioner.NewWorker(s.config)
   683  	c.Assert(err, jc.ErrorIsNil)
   684  	defer workertest.DirtyKill(c, w)
   685  
   686  	select {
   687  	case s.applicationChanges <- []string{"gitlab"}:
   688  	case <-time.After(coretesting.LongWait):
   689  		c.Fatal("timed out sending applications change")
   690  	}
   691  
   692  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   693  		if len(s.containerBroker.Calls()) > 0 {
   694  			break
   695  		}
   696  	}
   697  	s.containerBroker.CheckCallNames(c, "WatchUnits", "WatchOperator")
   698  	s.containerBroker.ResetCalls()
   699  
   700  	select {
   701  	case s.caasOperatorChanges <- struct{}{}:
   702  	case <-time.After(coretesting.LongWait):
   703  		c.Fatal("timed out sending applications change")
   704  	}
   705  	s.containerBroker.reportedOperatorStatus = status.Active
   706  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   707  		if len(s.containerBroker.Calls()) > 0 {
   708  			break
   709  		}
   710  	}
   711  	s.containerBroker.CheckCallNames(c, "Operator")
   712  	c.Assert(s.containerBroker.Calls()[0].Args, jc.DeepEquals, []interface{}{"gitlab"})
   713  
   714  	s.statusSetter.CheckCallNames(c, "SetOperatorStatus")
   715  	c.Assert(s.statusSetter.Calls()[0].Args, jc.DeepEquals, []interface{}{
   716  		"gitlab", status.Active, "testing 1. 2. 3.", map[string]interface{}{"zip": "zap"},
   717  	})
   718  }
   719  
   720  func (s *WorkerSuite) assertUnitChange(c *gc.C, reported, expected status.Status) {
   721  	s.containerBroker.ResetCalls()
   722  	s.unitUpdater.ResetCalls()
   723  	s.containerBroker.reportedUnitStatus = reported
   724  
   725  	select {
   726  	case s.caasUnitsChanges <- struct{}{}:
   727  	case <-time.After(coretesting.LongWait):
   728  		c.Fatal("timed out sending units change")
   729  	}
   730  
   731  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   732  		if len(s.containerBroker.Calls()) > 0 {
   733  			break
   734  		}
   735  	}
   736  	s.containerBroker.CheckCallNames(c, "Units")
   737  	c.Assert(s.containerBroker.Calls()[0].Args, jc.DeepEquals, []interface{}{"gitlab"})
   738  
   739  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   740  		if len(s.unitUpdater.Calls()) > 0 {
   741  			break
   742  		}
   743  	}
   744  	s.unitUpdater.CheckCallNames(c, "UpdateUnits")
   745  	c.Assert(s.unitUpdater.Calls()[0].Args, jc.DeepEquals, []interface{}{
   746  		params.UpdateApplicationUnits{
   747  			ApplicationTag: names.NewApplicationTag("gitlab").String(),
   748  			Units: []params.ApplicationUnitParams{
   749  				{ProviderId: "u1", Address: "10.0.0.1", Ports: []string(nil), Status: expected.String(),
   750  					FilesystemInfo: []params.KubernetesFilesystemInfo{
   751  						{StorageName: "database", MountPoint: "/path-to-here", ReadOnly: true,
   752  							FilesystemId: "fs-id", Size: 100, Pool: "",
   753  							Volume: params.KubernetesVolumeInfo{
   754  								VolumeId: "vol-id", Size: 200,
   755  								Persistent: true, Status: "error", Info: "vol not ready"},
   756  							Status: "attaching", Info: "not ready"},
   757  					}},
   758  			},
   759  		},
   760  	})
   761  }