github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/facades/controller/caasunitprovisioner/provisioner_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  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/charm/v12"
    11  	"github.com/juju/clock"
    12  	"github.com/juju/clock/testclock"
    13  	"github.com/juju/names/v5"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/worker/v3/workertest"
    16  	gc "gopkg.in/check.v1"
    17  
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/facades/controller/caasunitprovisioner"
    20  	apiservertesting "github.com/juju/juju/apiserver/testing"
    21  	k8sconstants "github.com/juju/juju/caas/kubernetes/provider/constants"
    22  	"github.com/juju/juju/core/constraints"
    23  	"github.com/juju/juju/core/life"
    24  	"github.com/juju/juju/core/network"
    25  	"github.com/juju/juju/core/status"
    26  	"github.com/juju/juju/rpc/params"
    27  	"github.com/juju/juju/state"
    28  	statetesting "github.com/juju/juju/state/testing"
    29  	storageprovider "github.com/juju/juju/storage/provider"
    30  	coretesting "github.com/juju/juju/testing"
    31  	jujuversion "github.com/juju/juju/version"
    32  )
    33  
    34  var _ = gc.Suite(&CAASProvisionerSuite{})
    35  
    36  type CAASProvisionerSuite struct {
    37  	coretesting.BaseSuite
    38  
    39  	clock               clock.Clock
    40  	st                  *mockState
    41  	storage             *mockStorage
    42  	storagePoolManager  *mockStoragePoolManager
    43  	registry            *mockStorageRegistry
    44  	devices             *mockDeviceBackend
    45  	applicationsChanges chan []string
    46  	podSpecChanges      chan struct{}
    47  	scaleChanges        chan struct{}
    48  	settingsChanges     chan []string
    49  
    50  	resources  *common.Resources
    51  	authorizer *apiservertesting.FakeAuthorizer
    52  	facade     *caasunitprovisioner.Facade
    53  
    54  	isRawK8sSpec *bool
    55  }
    56  
    57  func boolptr(i bool) *bool {
    58  	return &i
    59  }
    60  
    61  func (s *CAASProvisionerSuite) SetUpTest(c *gc.C) {
    62  	s.BaseSuite.SetUpTest(c)
    63  
    64  	s.applicationsChanges = make(chan []string, 1)
    65  	s.podSpecChanges = make(chan struct{}, 1)
    66  	s.scaleChanges = make(chan struct{}, 1)
    67  	s.settingsChanges = make(chan []string, 1)
    68  	s.isRawK8sSpec = boolptr(false)
    69  	s.st = &mockState{
    70  		application: mockApplication{
    71  			tag:             names.NewApplicationTag("gitlab"),
    72  			life:            state.Alive,
    73  			scaleWatcher:    statetesting.NewMockNotifyWatcher(s.scaleChanges),
    74  			settingsWatcher: statetesting.NewMockStringsWatcher(s.settingsChanges),
    75  			scale:           5,
    76  		},
    77  		applicationsWatcher: statetesting.NewMockStringsWatcher(s.applicationsChanges),
    78  		model: mockModel{
    79  			podSpecWatcher: statetesting.NewMockNotifyWatcher(s.podSpecChanges),
    80  			isRawK8sSpec:   s.isRawK8sSpec,
    81  		},
    82  		unit: mockUnit{
    83  			life: state.Dying,
    84  		},
    85  	}
    86  	s.storage = &mockStorage{
    87  		storageFilesystems: make(map[names.StorageTag]names.FilesystemTag),
    88  		storageVolumes:     make(map[names.StorageTag]names.VolumeTag),
    89  		storageAttachments: make(map[names.UnitTag]names.StorageTag),
    90  		backingVolume:      names.NewVolumeTag("66"),
    91  	}
    92  	s.storagePoolManager = &mockStoragePoolManager{}
    93  	s.devices = &mockDeviceBackend{}
    94  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.applicationsWatcher) })
    95  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.application.scaleWatcher) })
    96  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.application.settingsWatcher) })
    97  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.model.podSpecWatcher) })
    98  
    99  	s.resources = common.NewResources()
   100  	s.authorizer = &apiservertesting.FakeAuthorizer{
   101  		Tag:        names.NewMachineTag("0"),
   102  		Controller: true,
   103  	}
   104  	s.clock = testclock.NewClock(time.Now())
   105  	s.PatchValue(&jujuversion.OfficialBuild, 0)
   106  
   107  	facade, err := caasunitprovisioner.NewFacade(
   108  		s.resources, s.authorizer, s.st, s.storage, s.devices,
   109  		s.storagePoolManager, s.registry, nil, nil, s.clock)
   110  	c.Assert(err, jc.ErrorIsNil)
   111  	s.facade = facade
   112  }
   113  
   114  func (s *CAASProvisionerSuite) TestPermission(c *gc.C) {
   115  	s.authorizer = &apiservertesting.FakeAuthorizer{
   116  		Tag: names.NewMachineTag("0"),
   117  	}
   118  	_, err := caasunitprovisioner.NewFacade(
   119  		s.resources, s.authorizer, s.st, s.storage, s.devices,
   120  		s.storagePoolManager, s.registry, nil, nil, s.clock)
   121  	c.Assert(err, gc.ErrorMatches, "permission denied")
   122  }
   123  
   124  func (s *CAASProvisionerSuite) TestWatchApplications(c *gc.C) {
   125  	applicationNames := []string{"db2", "hadoop"}
   126  	s.applicationsChanges <- applicationNames
   127  	result, err := s.facade.WatchApplications()
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	c.Assert(result.Error, gc.IsNil)
   130  	c.Assert(result.StringsWatcherId, gc.Equals, "1")
   131  	c.Assert(result.Changes, jc.DeepEquals, applicationNames)
   132  
   133  	resource := s.resources.Get("1")
   134  	c.Assert(resource, gc.Equals, s.st.applicationsWatcher)
   135  }
   136  
   137  func (s *CAASProvisionerSuite) TestWatchPodSpec(c *gc.C) {
   138  	s.podSpecChanges <- struct{}{}
   139  
   140  	results, err := s.facade.WatchPodSpec(params.Entities{
   141  		Entities: []params.Entity{
   142  			{Tag: "application-gitlab"},
   143  			{Tag: "unit-gitlab-0"},
   144  		},
   145  	})
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	c.Assert(results.Results, gc.HasLen, 2)
   148  	c.Assert(results.Results[0].Error, gc.IsNil)
   149  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   150  		Message: `"unit-gitlab-0" is not a valid application tag`,
   151  	})
   152  
   153  	c.Assert(results.Results[0].NotifyWatcherId, gc.Equals, "1")
   154  	resource := s.resources.Get("1")
   155  	c.Assert(resource, gc.Equals, s.st.model.podSpecWatcher)
   156  }
   157  
   158  func (s *CAASProvisionerSuite) TestWatchApplicationsScale(c *gc.C) {
   159  	s.scaleChanges <- struct{}{}
   160  
   161  	results, err := s.facade.WatchApplicationsScale(params.Entities{
   162  		Entities: []params.Entity{
   163  			{Tag: "application-gitlab"},
   164  			{Tag: "unit-gitlab-0"},
   165  		},
   166  	})
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	c.Assert(results.Results, gc.HasLen, 2)
   169  	c.Assert(results.Results[0].Error, gc.IsNil)
   170  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   171  		Message: `"unit-gitlab-0" is not a valid application tag`,
   172  	})
   173  
   174  	c.Assert(results.Results[0].NotifyWatcherId, gc.Equals, "1")
   175  	resource := s.resources.Get("1")
   176  	c.Assert(resource, gc.Equals, s.st.application.scaleWatcher)
   177  }
   178  
   179  func (s *CAASProvisionerSuite) TestWatchApplicationsConfigSetingsHash(c *gc.C) {
   180  	s.settingsChanges <- []string{"hash"}
   181  
   182  	results, err := s.facade.WatchApplicationsTrustHash(params.Entities{
   183  		Entities: []params.Entity{
   184  			{Tag: "application-gitlab"},
   185  			{Tag: "unit-gitlab-0"},
   186  		},
   187  	})
   188  	c.Assert(err, jc.ErrorIsNil)
   189  	c.Assert(results.Results, gc.HasLen, 2)
   190  	c.Assert(results.Results[0].Error, gc.IsNil)
   191  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   192  		Message: `"unit-gitlab-0" is not a valid application tag`,
   193  	})
   194  
   195  	c.Assert(results.Results[0].StringsWatcherId, gc.Equals, "1")
   196  	resource := s.resources.Get("1")
   197  	c.Assert(resource, gc.Equals, s.st.application.settingsWatcher)
   198  }
   199  
   200  func (s *CAASProvisionerSuite) assertProvisioningInfo(c *gc.C, isRawK8sSpec bool) {
   201  	s.st.application.units = []caasunitprovisioner.Unit{
   202  		&mockUnit{name: "gitlab/0", life: state.Dying},
   203  		&mockUnit{name: "gitlab/1", life: state.Alive},
   204  	}
   205  	s.st.application.charm = &mockCharm{
   206  		meta: charm.Meta{
   207  			Storage: map[string]charm.Storage{
   208  				"data": {
   209  					Name:     "data",
   210  					Type:     charm.StorageFilesystem,
   211  					ReadOnly: true,
   212  				},
   213  				"logs": {
   214  					Name: "logs",
   215  					Type: charm.StorageFilesystem,
   216  				},
   217  			},
   218  			Deployment: &charm.Deployment{
   219  				DeploymentType: charm.DeploymentStateful,
   220  				ServiceType:    charm.ServiceLoadBalancer,
   221  			},
   222  		},
   223  	}
   224  	*s.isRawK8sSpec = isRawK8sSpec
   225  	results, err := s.facade.ProvisioningInfo(params.Entities{
   226  		Entities: []params.Entity{
   227  			{Tag: "application-gitlab"},
   228  			{Tag: "unit-gitlab-0"},
   229  		},
   230  	})
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	expectedVersion := jujuversion.Current
   233  	expectedVersion.Build = 666
   234  	// Maps are harder to check...
   235  	// http://ci.jujucharms.com/job/make-check-juju/4853/testReport/junit/github/com_juju_juju_apiserver_facades_controller_caasunitprovisioner/TestAll/
   236  	expectedResult := &params.KubernetesProvisioningInfo{
   237  		DeploymentInfo: &params.KubernetesDeploymentInfo{
   238  			DeploymentType: "stateful",
   239  			ServiceType:    "loadbalancer",
   240  		},
   241  		ImageRepo: params.DockerImageInfo{
   242  			RegistryPath: fmt.Sprintf("docker.io/jujusolutions/jujud-operator:%s", expectedVersion.String()),
   243  		},
   244  		Devices: []params.KubernetesDeviceParams{
   245  			{
   246  				Type:       "nvidia.com/gpu",
   247  				Count:      3,
   248  				Attributes: map[string]string{"gpu": "nvidia-tesla-p100"},
   249  			},
   250  		},
   251  		Constraints: constraints.MustParse("mem=64G"),
   252  		Tags: map[string]string{
   253  			"juju-model-uuid":      coretesting.ModelTag.Id(),
   254  			"juju-controller-uuid": coretesting.ControllerTag.Id()},
   255  	}
   256  	if isRawK8sSpec {
   257  		expectedResult.RawK8sSpec = "raw spec(gitlab)"
   258  	} else {
   259  		expectedResult.PodSpec = "spec(gitlab)"
   260  	}
   261  	expectedFileSystems := map[string]params.KubernetesFilesystemParams{
   262  		"data": {
   263  			StorageName: "data",
   264  			Provider:    string(k8sconstants.StorageProviderType),
   265  			Size:        100,
   266  			Attributes: map[string]interface{}{
   267  				"storage-class": "k8s-storage",
   268  				"foo":           "bar",
   269  			},
   270  			Tags: map[string]string{
   271  				"juju-storage-owner":   "gitlab",
   272  				"juju-model-uuid":      coretesting.ModelTag.Id(),
   273  				"juju-controller-uuid": coretesting.ControllerTag.Id()},
   274  			Attachment: &params.KubernetesFilesystemAttachmentParams{
   275  				Provider:   string(k8sconstants.StorageProviderType),
   276  				MountPoint: "/var/lib/juju/storage/data/0",
   277  				ReadOnly:   true,
   278  			},
   279  		},
   280  		"logs": {
   281  			StorageName: "logs",
   282  			Provider:    string(storageprovider.RootfsProviderType),
   283  			Size:        200,
   284  			Attributes:  map[string]interface{}{},
   285  			Tags: map[string]string{
   286  				"juju-storage-owner":   "gitlab",
   287  				"juju-model-uuid":      coretesting.ModelTag.Id(),
   288  				"juju-controller-uuid": coretesting.ControllerTag.Id()},
   289  			Attachment: &params.KubernetesFilesystemAttachmentParams{
   290  				Provider:   string(storageprovider.RootfsProviderType),
   291  				MountPoint: "/var/lib/juju/storage/logs/0",
   292  			},
   293  		}}
   294  	c.Assert(results.Results[0].Error, gc.IsNil)
   295  	obtained := results.Results[0].Result
   296  	c.Assert(obtained, gc.NotNil)
   297  	c.Assert(obtained.PodSpec, jc.DeepEquals, expectedResult.PodSpec)
   298  	c.Assert(obtained.RawK8sSpec, jc.DeepEquals, expectedResult.RawK8sSpec)
   299  	c.Assert(obtained.DeploymentInfo, jc.DeepEquals, expectedResult.DeploymentInfo)
   300  	c.Assert(obtained.ImageRepo.RegistryPath, gc.Equals, expectedResult.ImageRepo.RegistryPath)
   301  	c.Assert(obtained.CharmModifiedVersion, jc.DeepEquals, 888)
   302  	c.Assert(len(obtained.Filesystems), gc.Equals, len(expectedFileSystems))
   303  	for _, fs := range obtained.Filesystems {
   304  		c.Assert(fs, gc.DeepEquals, expectedFileSystems[fs.StorageName])
   305  	}
   306  	c.Assert(obtained.Devices, jc.DeepEquals, expectedResult.Devices)
   307  	c.Assert(obtained.Constraints, jc.DeepEquals, expectedResult.Constraints)
   308  	c.Assert(obtained.Tags, jc.DeepEquals, expectedResult.Tags)
   309  	c.Assert(results.Results[1], jc.DeepEquals, params.KubernetesProvisioningInfoResult{
   310  		Error: &params.Error{
   311  			Message: `"unit-gitlab-0" is not a valid application tag`,
   312  		},
   313  	})
   314  	s.st.CheckCallNames(c, "Model", "Application", "ControllerConfig", "ResolveConstraints")
   315  	s.st.CheckCall(c, 3, "ResolveConstraints", constraints.MustParse("mem=64G"))
   316  	s.storagePoolManager.CheckCallNames(c, "Get", "Get")
   317  }
   318  
   319  func (s *CAASProvisionerSuite) TestProvisioningInfoK8sSpec(c *gc.C) {
   320  	s.assertProvisioningInfo(c, false)
   321  }
   322  func (s *CAASProvisionerSuite) TestProvisioningInfoRawK8sSpec(c *gc.C) {
   323  	s.assertProvisioningInfo(c, true)
   324  }
   325  
   326  func (s *CAASProvisionerSuite) TestApplicationScale(c *gc.C) {
   327  	results, err := s.facade.ApplicationsScale(params.Entities{
   328  		Entities: []params.Entity{
   329  			{Tag: "application-gitlab"},
   330  			{Tag: "unit-gitlab-0"},
   331  		},
   332  	})
   333  	c.Assert(err, jc.ErrorIsNil)
   334  	c.Assert(results, jc.DeepEquals, params.IntResults{
   335  		Results: []params.IntResult{{
   336  			Result: 5,
   337  		}, {
   338  			Error: &params.Error{
   339  				Message: `"unit-gitlab-0" is not a valid application tag`,
   340  			},
   341  		}},
   342  	})
   343  	s.st.CheckCallNames(c, "Application")
   344  }
   345  
   346  func (s *CAASProvisionerSuite) TestDeploymentMode(c *gc.C) {
   347  	s.st.application.charm = &mockCharm{
   348  		meta: charm.Meta{
   349  			Deployment: &charm.Deployment{
   350  				DeploymentMode: charm.ModeWorkload,
   351  			},
   352  		},
   353  	}
   354  	results, err := s.facade.DeploymentMode(params.Entities{
   355  		Entities: []params.Entity{
   356  			{Tag: "application-gitlab"},
   357  			{Tag: "unit-gitlab-0"},
   358  		},
   359  	})
   360  	c.Assert(err, jc.ErrorIsNil)
   361  	c.Assert(results, jc.DeepEquals, params.StringResults{
   362  		Results: []params.StringResult{{
   363  			Result: "workload",
   364  		}, {
   365  			Error: &params.Error{
   366  				Message: `"unit-gitlab-0" is not a valid application tag`,
   367  			},
   368  		}},
   369  	})
   370  	s.st.CheckCallNames(c, "Application")
   371  }
   372  
   373  func (s *CAASProvisionerSuite) TestLife(c *gc.C) {
   374  	results, err := s.facade.Life(params.Entities{
   375  		Entities: []params.Entity{
   376  			{Tag: "unit-gitlab-0"},
   377  			{Tag: "application-gitlab"},
   378  			{Tag: "machine-0"},
   379  		},
   380  	})
   381  	c.Assert(err, jc.ErrorIsNil)
   382  	c.Assert(results, jc.DeepEquals, params.LifeResults{
   383  		Results: []params.LifeResult{{
   384  			Life: life.Dying,
   385  		}, {
   386  			Life: life.Alive,
   387  		}, {
   388  			Error: &params.Error{
   389  				Code:    "unauthorized access",
   390  				Message: "permission denied",
   391  			},
   392  		}},
   393  	})
   394  }
   395  
   396  func (s *CAASProvisionerSuite) TestApplicationConfig(c *gc.C) {
   397  	results, err := s.facade.ApplicationsConfig(params.Entities{
   398  		Entities: []params.Entity{
   399  			{Tag: "application-gitlab"},
   400  			{Tag: "unit-gitlab-0"},
   401  		},
   402  	})
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	c.Assert(results.Results, gc.HasLen, 2)
   405  	c.Assert(results.Results[0].Error, gc.IsNil)
   406  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   407  		Message: `"unit-gitlab-0" is not a valid application tag`,
   408  	})
   409  	c.Assert(results.Results[0].Config, jc.DeepEquals, map[string]interface{}{"foo": "bar"})
   410  }
   411  
   412  func (s *CAASProvisionerSuite) TestClearApplicationsResources(c *gc.C) {
   413  	results, err := s.facade.ClearApplicationsResources(params.Entities{
   414  		Entities: []params.Entity{
   415  			{Tag: "application-gitlab"},
   416  			{Tag: "unit-gitlab-0"},
   417  		},
   418  	})
   419  	c.Assert(err, jc.ErrorIsNil)
   420  	c.Assert(results, jc.DeepEquals, params.ErrorResults{
   421  		Results: []params.ErrorResult{
   422  			{},
   423  			{
   424  				Error: &params.Error{
   425  					Message: `"unit-gitlab-0" is not a valid application tag`,
   426  				},
   427  			}},
   428  	})
   429  	s.st.CheckCallNames(c, "Application")
   430  	s.st.application.CheckCallNames(c, "ClearResources")
   431  }
   432  
   433  func strPtr(s string) *string {
   434  	return &s
   435  }
   436  
   437  func intPtr(i int) *int {
   438  	return &i
   439  }
   440  func int64Ptr(i int64) *int64 {
   441  	return &i
   442  }
   443  
   444  func (s *CAASProvisionerSuite) TestUpdateApplicationsStatelessUnits(c *gc.C) {
   445  	s.assertUpdateApplicationsStatelessUnits(c, true)
   446  }
   447  
   448  func (s *CAASProvisionerSuite) TestUpdateApplicationsStatelessUnitsWithoutGeneration(c *gc.C) {
   449  	s.assertUpdateApplicationsStatelessUnits(c, false)
   450  }
   451  
   452  func (s *CAASProvisionerSuite) assertUpdateApplicationsStatelessUnits(c *gc.C, withGeneration bool) {
   453  	s.st.application.units = []caasunitprovisioner.Unit{
   454  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   455  		&mockUnit{name: "gitlab/1", life: state.Alive},
   456  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive},
   457  		&mockUnit{name: "gitlab/3", life: state.Alive},
   458  	}
   459  	s.st.application.scale = 4
   460  
   461  	units := []params.ApplicationUnitParams{
   462  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   463  			Status: "allocating", Info: ""},
   464  		{ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"},
   465  			Status: "allocating", Info: "another message"},
   466  		{ProviderId: "new-uuid", Address: "new-address", Ports: []string{"new-port"},
   467  			Status: "running", Info: "new message"},
   468  		{ProviderId: "really-new-uuid", Address: "really-new-address", Ports: []string{"really-new-port"},
   469  			Status: "running", Info: "really new message"},
   470  	}
   471  
   472  	args := []params.UpdateApplicationUnits{
   473  		{ApplicationTag: "application-another", Units: []params.ApplicationUnitParams{}},
   474  	}
   475  	gitlab := params.UpdateApplicationUnits{ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(4)}
   476  	if withGeneration {
   477  		gitlab.Generation = int64Ptr(1)
   478  	}
   479  	args = append(args, gitlab)
   480  
   481  	results, err := s.facade.UpdateApplicationsUnits(params.UpdateApplicationUnitArgs{Args: args})
   482  	c.Assert(err, jc.ErrorIsNil)
   483  	c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{
   484  		Results: []params.UpdateApplicationUnitResult{
   485  			{Error: &params.Error{Message: "application another not found", Code: "not found"}},
   486  			{Error: nil},
   487  		},
   488  	})
   489  	s.st.application.CheckCallNames(c, "Life", "AddOperation", "Name", "GetScale")
   490  	s.st.application.CheckCall(c, 1, "AddOperation", state.UnitUpdateProperties{
   491  		ProviderId: strPtr("really-new-uuid"),
   492  		Address:    strPtr("really-new-address"), Ports: &[]string{"really-new-port"},
   493  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "really new message"},
   494  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   495  	})
   496  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   497  	// CloudContainer message is not overwritten based on agent status
   498  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   499  		ProviderId: strPtr("uuid"),
   500  		Address:    strPtr("address"), Ports: &[]string{"port"},
   501  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""},
   502  		AgentStatus:          &status.StatusInfo{Status: status.Allocating},
   503  	})
   504  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   505  	// CloudContainer message is not overwritten based on agent status
   506  	s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   507  		ProviderId: strPtr("another-uuid"),
   508  		Address:    strPtr("another-address"), Ports: &[]string{"another-port"},
   509  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"},
   510  		AgentStatus:          &status.StatusInfo{Status: status.Allocating, Message: "another message"},
   511  	})
   512  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation")
   513  	s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{
   514  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   515  		CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"},
   516  	})
   517  	s.st.application.units[3].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   518  	s.st.application.units[3].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   519  		ProviderId: strPtr("new-uuid"),
   520  		Address:    strPtr("new-address"), Ports: &[]string{"new-port"},
   521  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "new message"},
   522  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   523  	})
   524  }
   525  
   526  func (s *CAASProvisionerSuite) TestUpdateApplicationsScaleChange(c *gc.C) {
   527  	s.st.application.units = []caasunitprovisioner.Unit{
   528  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   529  		&mockUnit{name: "gitlab/1", life: state.Alive},
   530  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive},
   531  	}
   532  	s.st.application.scale = 3
   533  
   534  	units := []params.ApplicationUnitParams{
   535  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   536  			Status: "allocating", Info: ""},
   537  		{ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"},
   538  			Status: "allocating", Info: "another message"},
   539  	}
   540  	args := params.UpdateApplicationUnitArgs{
   541  		Args: []params.UpdateApplicationUnits{
   542  			{
   543  				ApplicationTag: "application-gitlab",
   544  				Units:          units,
   545  				Scale:          intPtr(2),
   546  				Generation:     int64Ptr(1),
   547  				Status:         params.EntityStatus{Status: status.Active, Info: "working"}},
   548  		},
   549  	}
   550  	results, err := s.facade.UpdateApplicationsUnits(args)
   551  	c.Assert(err, jc.ErrorIsNil)
   552  	c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{
   553  		Results: []params.UpdateApplicationUnitResult{
   554  			{},
   555  		},
   556  	})
   557  	s.st.application.CheckCallNames(c, "SetOperatorStatus", "Life", "Name", "GetScale", "SetScale")
   558  	now := s.clock.Now()
   559  	s.st.application.CheckCall(c, 0, "SetOperatorStatus",
   560  		status.StatusInfo{Status: status.Active, Message: "working", Since: &now})
   561  	s.st.application.CheckCall(c, 4, "SetScale", 2)
   562  
   563  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   564  	// CloudContainer message is not overwritten based on agent status
   565  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   566  		ProviderId: strPtr("uuid"),
   567  		Address:    strPtr("address"), Ports: &[]string{"port"},
   568  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""},
   569  		AgentStatus:          &status.StatusInfo{Status: status.Allocating},
   570  	})
   571  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   572  	// CloudContainer message is not overwritten based on agent status
   573  	s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   574  		ProviderId: strPtr("another-uuid"),
   575  		Address:    strPtr("another-address"), Ports: &[]string{"another-port"},
   576  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"},
   577  		AgentStatus:          &status.StatusInfo{Status: status.Allocating, Message: "another message"},
   578  	})
   579  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation")
   580  	s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{
   581  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   582  		CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"},
   583  	})
   584  }
   585  
   586  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnknownScale(c *gc.C) {
   587  	s.st.application.units = []caasunitprovisioner.Unit{
   588  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   589  		&mockUnit{name: "gitlab/1", life: state.Alive},
   590  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive},
   591  	}
   592  	s.st.application.scale = 3
   593  
   594  	units := []params.ApplicationUnitParams{
   595  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   596  			Status: "allocating", Info: ""},
   597  		{ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"},
   598  			Status: "allocating", Info: "another message"},
   599  	}
   600  	args := params.UpdateApplicationUnitArgs{
   601  		Args: []params.UpdateApplicationUnits{
   602  			{ApplicationTag: "application-gitlab", Units: units, Scale: nil},
   603  		},
   604  	}
   605  	results, err := s.facade.UpdateApplicationsUnits(args)
   606  	c.Assert(err, jc.ErrorIsNil)
   607  	c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{
   608  		Results: []params.UpdateApplicationUnitResult{
   609  			{nil, nil},
   610  		},
   611  	})
   612  	s.st.application.CheckCallNames(c, "Life", "Name")
   613  
   614  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   615  	// CloudContainer message is not overwritten based on agent status
   616  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   617  		ProviderId: strPtr("uuid"),
   618  		Address:    strPtr("address"), Ports: &[]string{"port"},
   619  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""},
   620  		AgentStatus:          &status.StatusInfo{Status: status.Allocating},
   621  	})
   622  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   623  	// CloudContainer message is not overwritten based on agent status
   624  	s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   625  		ProviderId: strPtr("another-uuid"),
   626  		Address:    strPtr("another-address"), Ports: &[]string{"another-port"},
   627  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"},
   628  		AgentStatus:          &status.StatusInfo{Status: status.Allocating, Message: "another message"},
   629  	})
   630  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   631  	s.st.application.units[2].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   632  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   633  		CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"},
   634  	})
   635  }
   636  
   637  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsNotAlive(c *gc.C) {
   638  	s.st.application.units = []caasunitprovisioner.Unit{
   639  		&mockUnit{name: "gitlab/0", life: state.Alive},
   640  		&mockUnit{name: "gitlab/1", life: state.Alive},
   641  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive},
   642  	}
   643  	s.st.application.scale = 3
   644  	s.st.application.life = state.Dying
   645  
   646  	units := []params.ApplicationUnitParams{
   647  		{ProviderId: "uuid", UnitTag: "unit-gitlab-0", Address: "address", Ports: []string{"port"},
   648  			Status: "running", Info: "message"},
   649  		{ProviderId: "another-uuid", UnitTag: "unit-gitlab-1", Address: "another-address", Ports: []string{"another-port"},
   650  			Status: "error", Info: "another message"},
   651  	}
   652  	args := params.UpdateApplicationUnitArgs{
   653  		Args: []params.UpdateApplicationUnits{
   654  			{ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(3), Generation: int64Ptr(1)},
   655  		},
   656  	}
   657  	results, err := s.facade.UpdateApplicationsUnits(args)
   658  	c.Assert(err, jc.ErrorIsNil)
   659  	c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{
   660  		Results: []params.UpdateApplicationUnitResult{
   661  			{nil, nil},
   662  		},
   663  	})
   664  	s.st.application.CheckCallNames(c, "Life", "Name", "GetScale")
   665  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life")
   666  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life")
   667  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life")
   668  }
   669  
   670  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsWithStorage(c *gc.C) {
   671  	s.st.application.units = []caasunitprovisioner.Unit{
   672  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   673  		&mockUnit{name: "gitlab/1", life: state.Alive},
   674  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "gone-uuid"}, life: state.Alive},
   675  		&mockUnit{name: "gitlab/3", containerInfo: &mockContainerInfo{providerId: "gone-uuid2"}, life: state.Alive},
   676  	}
   677  	s.st.model.containers = []state.CloudContainer{
   678  		&mockContainerInfo{unitName: "gitlab/0", providerId: "uuid"},
   679  		&mockContainerInfo{unitName: "gitlab/1", providerId: "another-uuid"},
   680  	}
   681  	s.storage.storageFilesystems[names.NewStorageTag("data/0")] = names.NewFilesystemTag("gitlab/0/0")
   682  	s.storage.storageFilesystems[names.NewStorageTag("data/1")] = names.NewFilesystemTag("gitlab/1/0")
   683  	s.storage.storageFilesystems[names.NewStorageTag("data/2")] = names.NewFilesystemTag("gitlab/2/0")
   684  	s.storage.storageVolumes[names.NewStorageTag("data/0")] = names.NewVolumeTag("0")
   685  	s.storage.storageVolumes[names.NewStorageTag("data/1")] = names.NewVolumeTag("1")
   686  	s.storage.storageAttachments[names.NewUnitTag("gitlab/0")] = names.NewStorageTag("data/0")
   687  	s.storage.storageAttachments[names.NewUnitTag("gitlab/1")] = names.NewStorageTag("data/1")
   688  	s.storage.storageAttachments[names.NewUnitTag("gitlab/2")] = names.NewStorageTag("data/2")
   689  
   690  	units := []params.ApplicationUnitParams{
   691  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   692  			Status: "running", Info: "message", Stateful: true,
   693  			FilesystemInfo: []params.KubernetesFilesystemInfo{
   694  				{StorageName: "data", FilesystemId: "fs-id", Size: 100, MountPoint: "/path/to/here", ReadOnly: true,
   695  					Status: "pending", Info: "not ready",
   696  					Volume: params.KubernetesVolumeInfo{
   697  						VolumeId: "vol-id", Size: 100, Persistent: true,
   698  						Status: "pending", Info: "vol not ready",
   699  					}},
   700  			},
   701  		},
   702  		{ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"},
   703  			Status: "running", Info: "another message", Stateful: true,
   704  			FilesystemInfo: []params.KubernetesFilesystemInfo{
   705  				{StorageName: "data", FilesystemId: "fs-id2", Size: 200, MountPoint: "/path/to/there", ReadOnly: true,
   706  					Status: "attached", Info: "ready",
   707  					Volume: params.KubernetesVolumeInfo{
   708  						VolumeId: "vol-id2", Size: 200, Persistent: true,
   709  						Status: "attached", Info: "vol ready",
   710  					}},
   711  			},
   712  		},
   713  	}
   714  	s.st.application.scale = 3
   715  	args := params.UpdateApplicationUnitArgs{
   716  		Args: []params.UpdateApplicationUnits{
   717  			{ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(3), Generation: int64Ptr(1)},
   718  		},
   719  	}
   720  	results, err := s.facade.UpdateApplicationsUnits(args)
   721  	c.Assert(err, jc.ErrorIsNil)
   722  	c.Assert(results.Results[0], gc.DeepEquals, params.UpdateApplicationUnitResult{
   723  		Info: &params.UpdateApplicationUnitsInfo{
   724  			Units: []params.ApplicationUnitInfo{
   725  				{ProviderId: "uuid", UnitTag: "unit-gitlab-0"},
   726  				{ProviderId: "another-uuid", UnitTag: "unit-gitlab-1"},
   727  			},
   728  		},
   729  	})
   730  	s.st.application.CheckCallNames(c, "Life", "Name", "GetScale")
   731  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   732  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   733  		ProviderId: strPtr("uuid"),
   734  		Address:    strPtr("address"), Ports: &[]string{"port"},
   735  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "message"},
   736  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   737  	})
   738  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   739  	s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   740  		ProviderId: strPtr("another-uuid"),
   741  		Address:    strPtr("another-address"), Ports: &[]string{"another-port"},
   742  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "another message"},
   743  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   744  	})
   745  	// Units with state that disappear from the cluster are deleted
   746  	// if they cause the application scale to be exceeded.
   747  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation")
   748  	s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{
   749  		CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"},
   750  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   751  	})
   752  	// Units with state that disappear from the cluster are not deleted if
   753  	// the application scale is maintained.
   754  	s.st.application.units[3].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   755  	s.st.application.units[3].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   756  		CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"},
   757  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   758  	})
   759  
   760  	s.storage.CheckCallNames(c,
   761  		"UnitStorageAttachments", "UnitStorageAttachments", "UnitStorageAttachments",
   762  		"StorageInstance", "UnitStorageAttachments", "StorageInstance", "AllFilesystems",
   763  		"Volume", "SetVolumeInfo", "SetVolumeAttachmentInfo", "Volume", "SetStatus", "Volume", "SetStatus",
   764  		"Filesystem", "SetFilesystemInfo", "SetFilesystemAttachmentInfo",
   765  		"Filesystem", "SetStatus", "Filesystem", "SetStatus", "Filesystem", "SetStatus", "Filesystem", "SetStatus")
   766  	s.storage.CheckCall(c, 0, "UnitStorageAttachments", names.NewUnitTag("gitlab/2"))
   767  	s.storage.CheckCall(c, 1, "UnitStorageAttachments", names.NewUnitTag("gitlab/3"))
   768  	s.storage.CheckCall(c, 2, "UnitStorageAttachments", names.NewUnitTag("gitlab/0"))
   769  	s.storage.CheckCall(c, 3, "StorageInstance", names.NewStorageTag("data/0"))
   770  	s.storage.CheckCall(c, 4, "UnitStorageAttachments", names.NewUnitTag("gitlab/1"))
   771  	s.storage.CheckCall(c, 5, "StorageInstance", names.NewStorageTag("data/1"))
   772  
   773  	now := s.clock.Now()
   774  	s.storage.CheckCall(c, 8, "SetVolumeInfo",
   775  		names.NewVolumeTag("1"),
   776  		state.VolumeInfo{
   777  			Size:       200,
   778  			VolumeId:   "vol-id2",
   779  			Persistent: true,
   780  		})
   781  	s.storage.CheckCall(c, 9, "SetVolumeAttachmentInfo",
   782  		names.NewUnitTag("gitlab/1"), names.NewVolumeTag("1"),
   783  		state.VolumeAttachmentInfo{
   784  			ReadOnly: true,
   785  		})
   786  	s.storage.CheckCall(c, 11, "SetStatus",
   787  		status.StatusInfo{
   788  			Status:  status.Pending,
   789  			Message: "vol not ready",
   790  			Since:   &now,
   791  		})
   792  	s.storage.CheckCall(c, 13, "SetStatus",
   793  		status.StatusInfo{
   794  			Status:  status.Attached,
   795  			Message: "vol ready",
   796  			Since:   &now,
   797  		})
   798  
   799  	s.storage.CheckCall(c, 15, "SetFilesystemInfo",
   800  		names.NewFilesystemTag("gitlab/1/0"),
   801  		state.FilesystemInfo{
   802  			Size:         200,
   803  			FilesystemId: "fs-id2",
   804  		})
   805  	s.storage.CheckCall(c, 16, "SetFilesystemAttachmentInfo",
   806  		names.NewUnitTag("gitlab/1"), names.NewFilesystemTag("gitlab/1/0"),
   807  		state.FilesystemAttachmentInfo{
   808  			MountPoint: "/path/to/there",
   809  			ReadOnly:   true,
   810  		})
   811  	s.storage.CheckCall(c, 20, "SetStatus",
   812  		status.StatusInfo{
   813  			Status:  status.Pending,
   814  			Message: "not ready",
   815  			Since:   &now,
   816  		})
   817  	s.storage.CheckCall(c, 22, "SetStatus",
   818  		status.StatusInfo{
   819  			Status:  status.Attached,
   820  			Message: "ready",
   821  			Since:   &now,
   822  		})
   823  	s.storage.CheckCall(c, 24, "SetStatus",
   824  		status.StatusInfo{
   825  			Status: status.Detached,
   826  			Since:  &now,
   827  		})
   828  
   829  	s.st.model.CheckCall(c, 0, "Containers", []string{"another-uuid"})
   830  	s.st.model.CheckCall(c, 1, "Containers", []string{"uuid", "another-uuid"})
   831  }
   832  
   833  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsWithStorageNoBackingVolume(c *gc.C) {
   834  	s.st.application.units = []caasunitprovisioner.Unit{
   835  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   836  	}
   837  	s.storage.backingVolume = names.VolumeTag{}
   838  	s.storage.storageFilesystems[names.NewStorageTag("data/0")] = names.NewFilesystemTag("gitlab/0/0")
   839  	s.storage.storageAttachments[names.NewUnitTag("gitlab/0")] = names.NewStorageTag("data/0")
   840  
   841  	units := []params.ApplicationUnitParams{
   842  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   843  			Status: "running", Info: "message", Stateful: true,
   844  			FilesystemInfo: []params.KubernetesFilesystemInfo{
   845  				{StorageName: "data", FilesystemId: "fs-id", Size: 100, MountPoint: "/path/to/here", ReadOnly: true,
   846  					Status: "attached",
   847  				},
   848  			},
   849  		},
   850  	}
   851  	s.st.application.scale = 1
   852  	args := params.UpdateApplicationUnitArgs{
   853  		Args: []params.UpdateApplicationUnits{
   854  			{ApplicationTag: "application-gitlab", Units: units, Scale: intPtr(1), Generation: int64Ptr(1)},
   855  		},
   856  	}
   857  	results, err := s.facade.UpdateApplicationsUnits(args)
   858  	c.Assert(err, jc.ErrorIsNil)
   859  	c.Assert(results, gc.DeepEquals, params.UpdateApplicationUnitResults{
   860  		Results: []params.UpdateApplicationUnitResult{
   861  			{nil, nil},
   862  		},
   863  	})
   864  	s.st.application.CheckCallNames(c, "Life", "Name", "GetScale")
   865  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   866  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   867  		ProviderId: strPtr("uuid"),
   868  		Address:    strPtr("address"), Ports: &[]string{"port"},
   869  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "message"},
   870  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   871  	})
   872  
   873  	s.storage.CheckCallNames(c,
   874  		"UnitStorageAttachments", "StorageInstance", "AllFilesystems", "Filesystem",
   875  		"SetFilesystemInfo", "SetFilesystemAttachmentInfo", "Filesystem", "SetStatus")
   876  	s.storage.CheckCall(c, 0, "UnitStorageAttachments", names.NewUnitTag("gitlab/0"))
   877  	s.storage.CheckCall(c, 1, "StorageInstance", names.NewStorageTag("data/0"))
   878  
   879  	now := s.clock.Now()
   880  	s.storage.CheckCall(c, 4, "SetFilesystemInfo",
   881  		names.NewFilesystemTag("gitlab/0/0"),
   882  		state.FilesystemInfo{
   883  			Size:         100,
   884  			FilesystemId: "fs-id",
   885  		})
   886  	s.storage.CheckCall(c, 5, "SetFilesystemAttachmentInfo",
   887  		names.NewUnitTag("gitlab/0"), names.NewFilesystemTag("gitlab/0/0"),
   888  		state.FilesystemAttachmentInfo{
   889  			MountPoint: "/path/to/here",
   890  			ReadOnly:   true,
   891  		})
   892  	s.storage.CheckCall(c, 7, "SetStatus",
   893  		status.StatusInfo{
   894  			Status: status.Attached,
   895  			Since:  &now,
   896  		})
   897  }
   898  
   899  func (s *CAASProvisionerSuite) TestUpdateApplicationsService(c *gc.C) {
   900  	addr := network.NewSpaceAddress("10.0.0.1")
   901  	results, err := s.facade.UpdateApplicationsService(params.UpdateApplicationServiceArgs{
   902  		Args: []params.UpdateApplicationServiceArg{
   903  			{
   904  				ApplicationTag: "application-gitlab",
   905  				ProviderId:     "id",
   906  				Addresses:      params.FromMachineAddresses(addr.MachineAddress),
   907  			}, {
   908  				ApplicationTag: "unit-gitlab-0",
   909  			},
   910  		},
   911  	})
   912  	c.Assert(err, jc.ErrorIsNil)
   913  	c.Assert(results.Results, gc.HasLen, 2)
   914  	c.Assert(results.Results[0].Error, gc.IsNil)
   915  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   916  		Message: `"unit-gitlab-0" is not a valid application tag`,
   917  	})
   918  	c.Assert(s.st.application.providerId, gc.Equals, "id")
   919  	c.Assert(s.st.application.addresses, jc.DeepEquals, []network.SpaceAddress{addr})
   920  }
   921  
   922  func (s *CAASProvisionerSuite) TestSetOperatorStatus(c *gc.C) {
   923  	results, err := s.facade.SetOperatorStatus(params.SetStatus{
   924  		Entities: []params.EntityStatusArgs{
   925  			{Tag: "application-gitlab", Status: "error", Info: "broken", Data: map[string]interface{}{"foo": "bar"}},
   926  			{Tag: "unit-gitlab-0"},
   927  		},
   928  	})
   929  	c.Assert(err, jc.ErrorIsNil)
   930  	c.Assert(results.Results, gc.HasLen, 2)
   931  	c.Assert(results.Results[0].Error, gc.IsNil)
   932  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   933  		Message: `"unit-gitlab-0" is not a valid application tag`,
   934  	})
   935  	now := s.clock.Now()
   936  	s.st.application.CheckCall(c, 0, "SetOperatorStatus", status.StatusInfo{
   937  		Status:  status.Error,
   938  		Message: "broken",
   939  		Data:    map[string]interface{}{"foo": "bar"},
   940  		Since:   &now,
   941  	})
   942  }