github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/clock/testclock"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	"gopkg.in/juju/names.v2"
    14  	"gopkg.in/juju/worker.v1/workertest"
    15  
    16  	"github.com/juju/juju/apiserver/common"
    17  	"github.com/juju/juju/apiserver/facades/controller/caasunitprovisioner"
    18  	"github.com/juju/juju/apiserver/params"
    19  	apiservertesting "github.com/juju/juju/apiserver/testing"
    20  	"github.com/juju/juju/caas/kubernetes/provider"
    21  	"github.com/juju/juju/core/constraints"
    22  	"github.com/juju/juju/core/status"
    23  	"github.com/juju/juju/network"
    24  	"github.com/juju/juju/state"
    25  	statetesting "github.com/juju/juju/state/testing"
    26  	coretesting "github.com/juju/juju/testing"
    27  )
    28  
    29  var _ = gc.Suite(&CAASProvisionerSuite{})
    30  
    31  type CAASProvisionerSuite struct {
    32  	coretesting.BaseSuite
    33  
    34  	clock                   clock.Clock
    35  	st                      *mockState
    36  	storage                 *mockStorage
    37  	storageProviderRegistry *mockStorageProviderRegistry
    38  	storagePoolManager      *mockStoragePoolManager
    39  	devices                 *mockDeviceBackend
    40  	applicationsChanges     chan []string
    41  	podSpecChanges          chan struct{}
    42  	scaleChanges            chan struct{}
    43  
    44  	resources  *common.Resources
    45  	authorizer *apiservertesting.FakeAuthorizer
    46  	facade     *caasunitprovisioner.Facade
    47  }
    48  
    49  func (s *CAASProvisionerSuite) SetUpTest(c *gc.C) {
    50  	s.BaseSuite.SetUpTest(c)
    51  
    52  	s.applicationsChanges = make(chan []string, 1)
    53  	s.podSpecChanges = make(chan struct{}, 1)
    54  	s.scaleChanges = make(chan struct{}, 1)
    55  	s.st = &mockState{
    56  		application: mockApplication{
    57  			tag:          names.NewApplicationTag("gitlab"),
    58  			life:         state.Alive,
    59  			scaleWatcher: statetesting.NewMockNotifyWatcher(s.scaleChanges),
    60  		},
    61  		applicationsWatcher: statetesting.NewMockStringsWatcher(s.applicationsChanges),
    62  		model: mockModel{
    63  			podSpecWatcher: statetesting.NewMockNotifyWatcher(s.podSpecChanges),
    64  		},
    65  		unit: mockUnit{
    66  			life: state.Dying,
    67  		},
    68  	}
    69  	s.storage = &mockStorage{
    70  		storageFilesystems: make(map[names.StorageTag]names.FilesystemTag),
    71  		storageVolumes:     make(map[names.StorageTag]names.VolumeTag),
    72  		storageAttachments: make(map[names.UnitTag]names.StorageTag),
    73  	}
    74  	s.storageProviderRegistry = &mockStorageProviderRegistry{}
    75  	s.storagePoolManager = &mockStoragePoolManager{}
    76  	s.devices = &mockDeviceBackend{}
    77  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.applicationsWatcher) })
    78  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.application.scaleWatcher) })
    79  	s.AddCleanup(func(c *gc.C) { workertest.DirtyKill(c, s.st.model.podSpecWatcher) })
    80  
    81  	s.resources = common.NewResources()
    82  	s.authorizer = &apiservertesting.FakeAuthorizer{
    83  		Tag:        names.NewMachineTag("0"),
    84  		Controller: true,
    85  	}
    86  	s.clock = testclock.NewClock(time.Now())
    87  
    88  	facade, err := caasunitprovisioner.NewFacade(
    89  		s.resources, s.authorizer, s.st, s.storage, s.devices, s.storageProviderRegistry, s.storagePoolManager, s.clock)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	s.facade = facade
    92  }
    93  
    94  func (s *CAASProvisionerSuite) TestPermission(c *gc.C) {
    95  	s.authorizer = &apiservertesting.FakeAuthorizer{
    96  		Tag: names.NewMachineTag("0"),
    97  	}
    98  	_, err := caasunitprovisioner.NewFacade(
    99  		s.resources, s.authorizer, s.st, s.storage, s.devices, s.storageProviderRegistry, s.storagePoolManager, s.clock)
   100  	c.Assert(err, gc.ErrorMatches, "permission denied")
   101  }
   102  
   103  func (s *CAASProvisionerSuite) TestWatchApplications(c *gc.C) {
   104  	applicationNames := []string{"db2", "hadoop"}
   105  	s.applicationsChanges <- applicationNames
   106  	result, err := s.facade.WatchApplications()
   107  	c.Assert(err, jc.ErrorIsNil)
   108  	c.Assert(result.Error, gc.IsNil)
   109  	c.Assert(result.StringsWatcherId, gc.Equals, "1")
   110  	c.Assert(result.Changes, jc.DeepEquals, applicationNames)
   111  
   112  	resource := s.resources.Get("1")
   113  	c.Assert(resource, gc.Equals, s.st.applicationsWatcher)
   114  }
   115  
   116  func (s *CAASProvisionerSuite) TestWatchPodSpec(c *gc.C) {
   117  	s.podSpecChanges <- struct{}{}
   118  
   119  	results, err := s.facade.WatchPodSpec(params.Entities{
   120  		Entities: []params.Entity{
   121  			{Tag: "application-gitlab"},
   122  			{Tag: "unit-gitlab-0"},
   123  		},
   124  	})
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(results.Results, gc.HasLen, 2)
   127  	c.Assert(results.Results[0].Error, gc.IsNil)
   128  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   129  		Message: `"unit-gitlab-0" is not a valid application tag`,
   130  	})
   131  
   132  	c.Assert(results.Results[0].NotifyWatcherId, gc.Equals, "1")
   133  	resource := s.resources.Get("1")
   134  	c.Assert(resource, gc.Equals, s.st.model.podSpecWatcher)
   135  }
   136  
   137  func (s *CAASProvisionerSuite) TestWatchApplicationsScale(c *gc.C) {
   138  	s.scaleChanges <- struct{}{}
   139  
   140  	results, err := s.facade.WatchApplicationsScale(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.application.scaleWatcher)
   156  }
   157  
   158  func (s *CAASProvisionerSuite) TestProvisioningInfo(c *gc.C) {
   159  	s.st.application.units = []caasunitprovisioner.Unit{
   160  		&mockUnit{name: "gitlab/0", life: state.Dying},
   161  		&mockUnit{name: "gitlab/1", life: state.Alive},
   162  	}
   163  	s.storage.storageFilesystems[names.NewStorageTag("data/0")] = names.NewFilesystemTag("gitlab/1/0")
   164  	s.storage.storageAttachments[names.NewUnitTag("gitlab/1")] = names.NewStorageTag("data/0")
   165  
   166  	results, err := s.facade.ProvisioningInfo(params.Entities{
   167  		Entities: []params.Entity{
   168  			{Tag: "application-gitlab"},
   169  			{Tag: "unit-gitlab-0"},
   170  		},
   171  	})
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	c.Assert(results, jc.DeepEquals, params.KubernetesProvisioningInfoResults{
   174  		Results: []params.KubernetesProvisioningInfoResult{{
   175  			Result: &params.KubernetesProvisioningInfo{
   176  				PodSpec: "spec(gitlab)",
   177  				Filesystems: []params.KubernetesFilesystemParams{{
   178  					StorageName: "data",
   179  					Provider:    string(provider.K8s_ProviderType),
   180  					Size:        100,
   181  					Attributes:  map[string]interface{}{"foo": "bar"},
   182  					Tags: map[string]string{
   183  						"juju-storage-instance": "data/0",
   184  						"juju-storage-owner":    "gitlab",
   185  						"juju-model-uuid":       coretesting.ModelTag.Id(),
   186  						"juju-controller-uuid":  coretesting.ControllerTag.Id()},
   187  					Attachment: &params.KubernetesFilesystemAttachmentParams{
   188  						Provider:   string(provider.K8s_ProviderType),
   189  						MountPoint: "/path/to/here",
   190  						ReadOnly:   true,
   191  					},
   192  				}},
   193  				Devices: []params.KubernetesDeviceParams{
   194  					{
   195  						Type:       "nvidia.com/gpu",
   196  						Count:      3,
   197  						Attributes: map[string]string{"gpu": "nvidia-tesla-p100"},
   198  					},
   199  				},
   200  				Placement:   "placement",
   201  				Constraints: constraints.MustParse("mem=64G"),
   202  				Tags: map[string]string{
   203  					"juju-model-uuid":      coretesting.ModelTag.Id(),
   204  					"juju-controller-uuid": coretesting.ControllerTag.Id()},
   205  			},
   206  		}, {
   207  			Error: &params.Error{
   208  				Message: `"unit-gitlab-0" is not a valid application tag`,
   209  			},
   210  		}},
   211  	})
   212  	s.st.CheckCallNames(c, "Model", "Application", "ControllerConfig")
   213  	s.storage.CheckCallNames(c, "UnitStorageAttachments", "StorageInstance", "FilesystemAttachment")
   214  	s.storageProviderRegistry.CheckNoCalls(c)
   215  	s.storagePoolManager.CheckCallNames(c, "Get")
   216  }
   217  
   218  func (s *CAASProvisionerSuite) TestProvisioningInfoNoUnits(c *gc.C) {
   219  	s.st.application.units = []caasunitprovisioner.Unit{}
   220  
   221  	results, err := s.facade.ProvisioningInfo(params.Entities{
   222  		Entities: []params.Entity{
   223  			{Tag: "application-gitlab"},
   224  		},
   225  	})
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	c.Assert(results, jc.DeepEquals, params.KubernetesProvisioningInfoResults{
   228  		Results: []params.KubernetesProvisioningInfoResult{{}}})
   229  	s.st.CheckCallNames(c, "Model", "Application")
   230  	s.storage.CheckNoCalls(c)
   231  	s.storageProviderRegistry.CheckNoCalls(c)
   232  	s.storagePoolManager.CheckNoCalls(c)
   233  }
   234  
   235  func (s *CAASProvisionerSuite) TestApplicationScale(c *gc.C) {
   236  	results, err := s.facade.ApplicationsScale(params.Entities{
   237  		Entities: []params.Entity{
   238  			{Tag: "application-gitlab"},
   239  			{Tag: "unit-gitlab-0"},
   240  		},
   241  	})
   242  	c.Assert(err, jc.ErrorIsNil)
   243  	c.Assert(results, jc.DeepEquals, params.IntResults{
   244  		Results: []params.IntResult{{
   245  			Result: 5,
   246  		}, {
   247  			Error: &params.Error{
   248  				Message: `"unit-gitlab-0" is not a valid application tag`,
   249  			},
   250  		}},
   251  	})
   252  	s.st.CheckCallNames(c, "Application")
   253  }
   254  
   255  func (s *CAASProvisionerSuite) TestLife(c *gc.C) {
   256  	results, err := s.facade.Life(params.Entities{
   257  		Entities: []params.Entity{
   258  			{Tag: "unit-gitlab-0"},
   259  			{Tag: "application-gitlab"},
   260  			{Tag: "machine-0"},
   261  		},
   262  	})
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	c.Assert(results, jc.DeepEquals, params.LifeResults{
   265  		Results: []params.LifeResult{{
   266  			Life: params.Dying,
   267  		}, {
   268  			Life: params.Alive,
   269  		}, {
   270  			Error: &params.Error{
   271  				Code:    "unauthorized access",
   272  				Message: "permission denied",
   273  			},
   274  		}},
   275  	})
   276  }
   277  
   278  func (s *CAASProvisionerSuite) TestApplicationConfig(c *gc.C) {
   279  	results, err := s.facade.ApplicationsConfig(params.Entities{
   280  		Entities: []params.Entity{
   281  			{Tag: "application-gitlab"},
   282  			{Tag: "unit-gitlab-0"},
   283  		},
   284  	})
   285  	c.Assert(err, jc.ErrorIsNil)
   286  	c.Assert(results.Results, gc.HasLen, 2)
   287  	c.Assert(results.Results[0].Error, gc.IsNil)
   288  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   289  		Message: `"unit-gitlab-0" is not a valid application tag`,
   290  	})
   291  	c.Assert(results.Results[0].Config, jc.DeepEquals, map[string]interface{}{"foo": "bar"})
   292  }
   293  
   294  func strPtr(s string) *string {
   295  	return &s
   296  }
   297  
   298  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnits(c *gc.C) {
   299  	s.st.application.units = []caasunitprovisioner.Unit{
   300  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   301  		&mockUnit{name: "gitlab/1", life: state.Alive},
   302  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive},
   303  		&mockUnit{name: "gitlab/3", life: state.Alive},
   304  	}
   305  
   306  	units := []params.ApplicationUnitParams{
   307  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   308  			Status: "allocating", Info: ""},
   309  		{ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"},
   310  			Status: "allocating", Info: "another message"},
   311  		{ProviderId: "new-uuid", Address: "new-address", Ports: []string{"new-port"},
   312  			Status: "running", Info: "new message"},
   313  		{ProviderId: "really-new-uuid", Address: "really-new-address", Ports: []string{"really-new-port"},
   314  			Status: "running", Info: "really new message"},
   315  	}
   316  	args := params.UpdateApplicationUnitArgs{
   317  		Args: []params.UpdateApplicationUnits{
   318  			{ApplicationTag: "application-gitlab", Units: units},
   319  			{ApplicationTag: "application-another", Units: []params.ApplicationUnitParams{}},
   320  		},
   321  	}
   322  	results, err := s.facade.UpdateApplicationsUnits(args)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   325  		Results: []params.ErrorResult{
   326  			{nil},
   327  			{&params.Error{Message: "application another not found", Code: "not found"}},
   328  		},
   329  	})
   330  	s.st.application.CheckCallNames(c, "Life", "AddOperation", "Name")
   331  	s.st.application.CheckCall(c, 1, "AddOperation", state.UnitUpdateProperties{
   332  		ProviderId: strPtr("really-new-uuid"),
   333  		Address:    strPtr("really-new-address"), Ports: &[]string{"really-new-port"},
   334  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "really new message"},
   335  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   336  	})
   337  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   338  	// CloudContainer message is not overwritten based on agent status
   339  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   340  		ProviderId: strPtr("uuid"),
   341  		Address:    strPtr("address"), Ports: &[]string{"port"},
   342  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: ""},
   343  		AgentStatus:          &status.StatusInfo{Status: status.Allocating},
   344  	})
   345  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   346  	// CloudContainer message is not overwritten based on agent status
   347  	s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   348  		ProviderId: strPtr("another-uuid"),
   349  		Address:    strPtr("another-address"), Ports: &[]string{"another-port"},
   350  		CloudContainerStatus: &status.StatusInfo{Status: status.Waiting, Message: "another message"},
   351  		AgentStatus:          &status.StatusInfo{Status: status.Allocating, Message: "another message"},
   352  	})
   353  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life", "DestroyOperation", "UpdateOperation")
   354  	s.st.application.units[2].(*mockUnit).CheckCall(c, 2, "UpdateOperation", state.UnitUpdateProperties{
   355  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   356  		CloudContainerStatus: &status.StatusInfo{Status: status.Terminated, Message: "unit stopped by the cloud"},
   357  	})
   358  	s.st.application.units[3].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   359  	s.st.application.units[3].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   360  		ProviderId: strPtr("new-uuid"),
   361  		Address:    strPtr("new-address"), Ports: &[]string{"new-port"},
   362  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "new message"},
   363  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   364  	})
   365  }
   366  
   367  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsNotAlive(c *gc.C) {
   368  	s.st.application.units = []caasunitprovisioner.Unit{
   369  		&mockUnit{name: "gitlab/0", life: state.Alive},
   370  		&mockUnit{name: "gitlab/1", life: state.Alive},
   371  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "uuid2"}, life: state.Alive},
   372  	}
   373  	s.st.application.life = state.Dying
   374  
   375  	units := []params.ApplicationUnitParams{
   376  		{ProviderId: "uuid", UnitTag: "unit-gitlab-0", Address: "address", Ports: []string{"port"},
   377  			Status: "running", Info: "message"},
   378  		{ProviderId: "another-uuid", UnitTag: "unit-gitlab-1", Address: "another-address", Ports: []string{"another-port"},
   379  			Status: "error", Info: "another message"},
   380  	}
   381  	args := params.UpdateApplicationUnitArgs{
   382  		Args: []params.UpdateApplicationUnits{
   383  			{ApplicationTag: "application-gitlab", Units: units},
   384  		},
   385  	}
   386  	results, err := s.facade.UpdateApplicationsUnits(args)
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   389  		Results: []params.ErrorResult{
   390  			{nil},
   391  		},
   392  	})
   393  	s.st.application.CheckCallNames(c, "Life", "Name")
   394  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life")
   395  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life")
   396  	s.st.application.units[2].(*mockUnit).CheckCallNames(c, "Life")
   397  }
   398  
   399  func (s *CAASProvisionerSuite) TestUpdateApplicationsUnitsWithStorage(c *gc.C) {
   400  	s.st.application.units = []caasunitprovisioner.Unit{
   401  		&mockUnit{name: "gitlab/0", containerInfo: &mockContainerInfo{providerId: "uuid"}, life: state.Alive},
   402  		&mockUnit{name: "gitlab/1", life: state.Alive},
   403  		&mockUnit{name: "gitlab/2", containerInfo: &mockContainerInfo{providerId: "gone-uuid"}, life: state.Alive},
   404  	}
   405  	s.st.model.containers = []state.CloudContainer{&mockContainerInfo{unitName: "gitlab/1", providerId: "another-uuid"}}
   406  	s.storage.storageFilesystems[names.NewStorageTag("data/0")] = names.NewFilesystemTag("gitlab/0/0")
   407  	s.storage.storageFilesystems[names.NewStorageTag("data/1")] = names.NewFilesystemTag("gitlab/1/0")
   408  	s.storage.storageFilesystems[names.NewStorageTag("data/2")] = names.NewFilesystemTag("gitlab/2/0")
   409  	s.storage.storageVolumes[names.NewStorageTag("data/0")] = names.NewVolumeTag("0")
   410  	s.storage.storageVolumes[names.NewStorageTag("data/1")] = names.NewVolumeTag("1")
   411  	s.storage.storageAttachments[names.NewUnitTag("gitlab/0")] = names.NewStorageTag("data/0")
   412  	s.storage.storageAttachments[names.NewUnitTag("gitlab/1")] = names.NewStorageTag("data/1")
   413  	s.storage.storageAttachments[names.NewUnitTag("gitlab/2")] = names.NewStorageTag("data/2")
   414  
   415  	units := []params.ApplicationUnitParams{
   416  		{ProviderId: "uuid", Address: "address", Ports: []string{"port"},
   417  			Status: "running", Info: "message",
   418  			FilesystemInfo: []params.KubernetesFilesystemInfo{
   419  				{StorageName: "data", FilesystemId: "fs-id", Size: 100, MountPoint: "/path/to/here", ReadOnly: true,
   420  					Status: "pending", Info: "not ready",
   421  					Volume: params.KubernetesVolumeInfo{
   422  						VolumeId: "vol-id", Size: 100, Persistent: true,
   423  						Status: "pending", Info: "vol not ready",
   424  					}},
   425  			},
   426  		},
   427  		{ProviderId: "another-uuid", Address: "another-address", Ports: []string{"another-port"},
   428  			Status: "running", Info: "another message",
   429  			FilesystemInfo: []params.KubernetesFilesystemInfo{
   430  				{StorageName: "data", FilesystemId: "fs-id2", Size: 200, MountPoint: "/path/to/there", ReadOnly: true,
   431  					Status: "attached", Info: "ready",
   432  					Volume: params.KubernetesVolumeInfo{
   433  						VolumeId: "vol-id2", Size: 200, Persistent: true,
   434  						Status: "attached", Info: "vol ready",
   435  					}},
   436  			},
   437  		},
   438  	}
   439  	args := params.UpdateApplicationUnitArgs{
   440  		Args: []params.UpdateApplicationUnits{
   441  			{ApplicationTag: "application-gitlab", Units: units},
   442  		},
   443  	}
   444  	results, err := s.facade.UpdateApplicationsUnits(args)
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	c.Assert(results, gc.DeepEquals, params.ErrorResults{
   447  		Results: []params.ErrorResult{
   448  			{nil},
   449  		},
   450  	})
   451  	s.st.application.CheckCallNames(c, "Life", "Name")
   452  	s.st.application.units[0].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   453  	s.st.application.units[0].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   454  		ProviderId: strPtr("uuid"),
   455  		Address:    strPtr("address"), Ports: &[]string{"port"},
   456  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "message"},
   457  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   458  	})
   459  	s.st.application.units[1].(*mockUnit).CheckCallNames(c, "Life", "UpdateOperation")
   460  	s.st.application.units[1].(*mockUnit).CheckCall(c, 1, "UpdateOperation", state.UnitUpdateProperties{
   461  		ProviderId: strPtr("another-uuid"),
   462  		Address:    strPtr("another-address"), Ports: &[]string{"another-port"},
   463  		CloudContainerStatus: &status.StatusInfo{Status: status.Running, Message: "another message"},
   464  		AgentStatus:          &status.StatusInfo{Status: status.Idle},
   465  	})
   466  	s.storage.CheckCallNames(c,
   467  		"UnitStorageAttachments", "UnitStorageAttachments", "StorageInstance",
   468  		"UnitStorageAttachments", "StorageInstance", "AllFilesystems",
   469  		"Volume", "SetVolumeInfo", "SetVolumeAttachmentInfo", "Volume", "SetStatus", "Volume", "SetStatus",
   470  		"Filesystem", "SetFilesystemInfo", "SetFilesystemAttachmentInfo",
   471  		"Filesystem", "SetStatus", "Filesystem", "SetStatus", "Filesystem", "SetStatus")
   472  	s.storage.CheckCall(c, 0, "UnitStorageAttachments", names.NewUnitTag("gitlab/2"))
   473  	s.storage.CheckCall(c, 1, "UnitStorageAttachments", names.NewUnitTag("gitlab/0"))
   474  	s.storage.CheckCall(c, 2, "StorageInstance", names.NewStorageTag("data/0"))
   475  	s.storage.CheckCall(c, 3, "UnitStorageAttachments", names.NewUnitTag("gitlab/1"))
   476  	s.storage.CheckCall(c, 4, "StorageInstance", names.NewStorageTag("data/1"))
   477  
   478  	now := s.clock.Now()
   479  	s.storage.CheckCall(c, 7, "SetVolumeInfo",
   480  		names.NewVolumeTag("1"),
   481  		state.VolumeInfo{
   482  			Size:       200,
   483  			VolumeId:   "vol-id2",
   484  			Persistent: true,
   485  		})
   486  	s.storage.CheckCall(c, 8, "SetVolumeAttachmentInfo",
   487  		names.NewUnitTag("gitlab/1"), names.NewVolumeTag("1"),
   488  		state.VolumeAttachmentInfo{
   489  			ReadOnly: true,
   490  		})
   491  	s.storage.CheckCall(c, 10, "SetStatus",
   492  		status.StatusInfo{
   493  			Status:  status.Pending,
   494  			Message: "vol not ready",
   495  			Since:   &now,
   496  		})
   497  	s.storage.CheckCall(c, 12, "SetStatus",
   498  		status.StatusInfo{
   499  			Status:  status.Attached,
   500  			Message: "vol ready",
   501  			Since:   &now,
   502  		})
   503  
   504  	s.storage.CheckCall(c, 14, "SetFilesystemInfo",
   505  		names.NewFilesystemTag("gitlab/1/0"),
   506  		state.FilesystemInfo{
   507  			Size:         200,
   508  			FilesystemId: "fs-id2",
   509  		})
   510  	s.storage.CheckCall(c, 15, "SetFilesystemAttachmentInfo",
   511  		names.NewUnitTag("gitlab/1"), names.NewFilesystemTag("gitlab/1/0"),
   512  		state.FilesystemAttachmentInfo{
   513  			MountPoint: "/path/to/there",
   514  			ReadOnly:   true,
   515  		})
   516  	s.storage.CheckCall(c, 19, "SetStatus",
   517  		status.StatusInfo{
   518  			Status:  status.Attached,
   519  			Message: "ready",
   520  			Since:   &now,
   521  		})
   522  	s.storage.CheckCall(c, 21, "SetStatus",
   523  		status.StatusInfo{
   524  			Status: status.Detached,
   525  			Since:  &now,
   526  		})
   527  }
   528  
   529  func (s *CAASProvisionerSuite) TestUpdateApplicationsService(c *gc.C) {
   530  	results, err := s.facade.UpdateApplicationsService(params.UpdateApplicationServiceArgs{
   531  		Args: []params.UpdateApplicationServiceArg{
   532  			{ApplicationTag: "application-gitlab", ProviderId: "id", Addresses: []params.Address{{Value: "10.0.0.1"}}},
   533  			{ApplicationTag: "unit-gitlab-0"},
   534  		},
   535  	})
   536  	c.Assert(err, jc.ErrorIsNil)
   537  	c.Assert(results.Results, gc.HasLen, 2)
   538  	c.Assert(results.Results[0].Error, gc.IsNil)
   539  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   540  		Message: `"unit-gitlab-0" is not a valid application tag`,
   541  	})
   542  	c.Assert(s.st.application.providerId, gc.Equals, "id")
   543  	c.Assert(s.st.application.addresses, jc.DeepEquals, []network.Address{{Value: "10.0.0.1"}})
   544  }
   545  
   546  func (s *CAASProvisionerSuite) TestSetOperatorStatus(c *gc.C) {
   547  	results, err := s.facade.SetOperatorStatus(params.SetStatus{
   548  		Entities: []params.EntityStatusArgs{
   549  			{Tag: "application-gitlab", Status: "error", Info: "broken", Data: map[string]interface{}{"foo": "bar"}},
   550  			{Tag: "unit-gitlab-0"},
   551  		},
   552  	})
   553  	c.Assert(err, jc.ErrorIsNil)
   554  	c.Assert(results.Results, gc.HasLen, 2)
   555  	c.Assert(results.Results[0].Error, gc.IsNil)
   556  	c.Assert(results.Results[1].Error, jc.DeepEquals, &params.Error{
   557  		Message: `"unit-gitlab-0" is not a valid application tag`,
   558  	})
   559  	now := s.clock.Now()
   560  	s.st.application.CheckCall(c, 0, "SetOperatorStatus", status.StatusInfo{
   561  		Status:  status.Error,
   562  		Message: "broken",
   563  		Data:    map[string]interface{}{"foo": "bar"},
   564  		Since:   &now,
   565  	})
   566  }