github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/caasmodel_test.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/names/v5"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/caas"
    15  	k8sprovider "github.com/juju/juju/caas/kubernetes/provider"
    16  	k8stesting "github.com/juju/juju/caas/kubernetes/provider/testing"
    17  	"github.com/juju/juju/cloud"
    18  	"github.com/juju/juju/core/status"
    19  	"github.com/juju/juju/environs/config"
    20  	"github.com/juju/juju/state"
    21  	stateerrors "github.com/juju/juju/state/errors"
    22  	"github.com/juju/juju/state/stateenvirons"
    23  	"github.com/juju/juju/state/testing"
    24  	"github.com/juju/juju/storage"
    25  	"github.com/juju/juju/storage/provider"
    26  	"github.com/juju/juju/testing/factory"
    27  )
    28  
    29  type CAASFixture struct {
    30  	ConnSuite
    31  }
    32  
    33  func (s *CAASFixture) SetUpTest(c *gc.C) {
    34  	s.ConnSuite.SetUpTest(c)
    35  	s.PatchValue(&k8sprovider.NewK8sClients, k8stesting.NoopFakeK8sClients)
    36  }
    37  
    38  // createTestModelConfig returns a new model config and its UUID for testing.
    39  func (s *CAASFixture) createTestModelConfig(c *gc.C) (*config.Config, string) {
    40  	return createTestModelConfig(c, s.modelTag.Id())
    41  }
    42  
    43  func (s *CAASFixture) newCAASModel(c *gc.C) (*state.CAASModel, *state.State) {
    44  	st := s.Factory.MakeCAASModel(c, nil)
    45  	s.AddCleanup(func(*gc.C) { st.Close() })
    46  	model, err := st.Model()
    47  	c.Assert(err, jc.ErrorIsNil)
    48  	caasModel, err := model.CAASModel()
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	return caasModel, st
    51  }
    52  
    53  type CAASModelSuite struct {
    54  	CAASFixture
    55  }
    56  
    57  var _ = gc.Suite(&CAASModelSuite{})
    58  
    59  func (s *CAASModelSuite) TestNewModel(c *gc.C) {
    60  	owner := s.Factory.MakeUser(c, nil)
    61  	err := s.State.AddCloud(cloud.Cloud{
    62  		Name:      "caas-cloud",
    63  		Type:      "kubernetes",
    64  		AuthTypes: []cloud.AuthType{cloud.UserPassAuthType},
    65  	}, owner.Name())
    66  	c.Assert(err, jc.ErrorIsNil)
    67  	cfg, uuid := s.createTestModelConfig(c)
    68  	modelTag := names.NewModelTag(uuid)
    69  	cred := cloud.NewCredential(cloud.UserPassAuthType, nil)
    70  	credTag := names.NewCloudCredentialTag(
    71  		fmt.Sprintf("caas-cloud/%s/dummy-credential", owner.Name()))
    72  	err = s.State.UpdateCloudCredential(credTag, cred)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  	model, st, err := s.Controller.NewModel(state.ModelArgs{
    75  		Type:                    state.ModelTypeCAAS,
    76  		CloudName:               "caas-cloud",
    77  		Config:                  cfg,
    78  		Owner:                   owner.UserTag(),
    79  		CloudCredential:         credTag,
    80  		StorageProviderRegistry: provider.CommonStorageProviders(),
    81  	})
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	defer st.Close()
    84  
    85  	c.Assert(model.Type(), gc.Equals, state.ModelTypeCAAS)
    86  	c.Assert(model.UUID(), gc.Equals, modelTag.Id())
    87  	c.Assert(model.Tag(), gc.Equals, modelTag)
    88  	c.Assert(model.ControllerTag(), gc.Equals, s.State.ControllerTag())
    89  	c.Assert(model.Owner().Name(), gc.Equals, owner.Name())
    90  	c.Assert(model.Name(), gc.Equals, "testing")
    91  	c.Assert(model.Life(), gc.Equals, state.Alive)
    92  	c.Assert(model.CloudRegion(), gc.Equals, "")
    93  }
    94  
    95  func (s *CAASModelSuite) TestDestroyEmptyModel(c *gc.C) {
    96  	model, st := s.newCAASModel(c)
    97  	c.Assert(model.Destroy(state.DestroyModelParams{}), jc.ErrorIsNil)
    98  	c.Assert(model.Refresh(), jc.ErrorIsNil)
    99  	c.Assert(model.Life(), gc.Equals, state.Dying)
   100  	c.Assert(st.RemoveDyingModel(), jc.ErrorIsNil)
   101  	c.Assert(model.Refresh(), jc.Satisfies, errors.IsNotFound)
   102  }
   103  
   104  func (s *CAASModelSuite) TestDestroyModel(c *gc.C) {
   105  	model, st := s.newCAASModel(c)
   106  
   107  	f := factory.NewFactory(st, s.StatePool)
   108  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
   109  	app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch})
   110  	unit, err := app.AddUnit(state.AddUnitParams{})
   111  	c.Assert(err, jc.ErrorIsNil)
   112  
   113  	err = model.Destroy(state.DestroyModelParams{})
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	err = model.Refresh()
   116  	c.Assert(err, jc.ErrorIsNil)
   117  	c.Assert(model.Life(), gc.Equals, state.Dying)
   118  
   119  	assertCleanupCount(c, st, 3)
   120  
   121  	// App removal requires cluster resources to be cleared.
   122  	err = app.Refresh()
   123  	c.Assert(err, jc.ErrorIsNil)
   124  	err = app.ClearResources()
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	assertCleanupCount(c, st, 2)
   127  
   128  	err = app.Refresh()
   129  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   130  	err = unit.Refresh()
   131  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   132  	assertDoesNotNeedCleanup(c, st)
   133  }
   134  
   135  func (s *CAASModelSuite) TestDestroyModelDestroyStorage(c *gc.C) {
   136  	model, st := s.newCAASModel(c)
   137  	broker, err := stateenvirons.GetNewCAASBrokerFunc(caas.New)(model)
   138  	c.Assert(err, jc.ErrorIsNil)
   139  	registry := stateenvirons.NewStorageProviderRegistry(broker)
   140  	s.policy = testing.MockPolicy{
   141  		GetStorageProviderRegistry: func() (storage.ProviderRegistry, error) {
   142  			return registry, nil
   143  		},
   144  	}
   145  
   146  	sb, err := state.NewStorageBackend(st)
   147  	c.Assert(err, jc.ErrorIsNil)
   148  
   149  	f := factory.NewFactory(st, s.StatePool)
   150  	app := f.MakeApplication(c, &factory.ApplicationParams{
   151  		Charm: state.AddTestingCharmForSeries(c, st, "kubernetes", "storage-filesystem"),
   152  		Storage: map[string]state.StorageConstraints{
   153  			"data": {Count: 1, Size: 1024},
   154  		},
   155  	})
   156  	unit := f.MakeUnit(c, &factory.UnitParams{
   157  		Application: app,
   158  	})
   159  
   160  	si, err := sb.AllStorageInstances()
   161  	c.Assert(err, jc.ErrorIsNil)
   162  	c.Assert(si, gc.HasLen, 1)
   163  	fs, err := sb.AllFilesystems()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(fs, gc.HasLen, 1)
   166  
   167  	destroyStorage := true
   168  	err = model.Destroy(state.DestroyModelParams{DestroyStorage: &destroyStorage})
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	c.Assert(model.Refresh(), jc.ErrorIsNil)
   171  	c.Assert(model.Life(), gc.Equals, state.Dying)
   172  
   173  	assertNeedsCleanup(c, st)
   174  	assertCleanupCount(c, st, 4)
   175  
   176  	c.Assert(app.Refresh(), jc.ErrorIsNil)
   177  	c.Assert(app.Life(), gc.Equals, state.Dying)
   178  	c.Assert(unit.Refresh(), jc.ErrorIsNil)
   179  	c.Assert(unit.Life(), gc.Equals, state.Dying)
   180  
   181  	// The uniter would call this when it sees it is dying.
   182  	err = unit.EnsureDead()
   183  	c.Assert(err, jc.ErrorIsNil)
   184  	// The deployer or the caasapplicationprovisioner would call this once the unit is Dead.
   185  	err = unit.Remove()
   186  	c.Assert(err, jc.ErrorIsNil)
   187  
   188  	assertNeedsCleanup(c, st)
   189  	assertCleanupCount(c, st, 2)
   190  
   191  	// The caasapplicationprovisioner would call this when the app is gone from the cloud.
   192  	err = app.ClearResources()
   193  	c.Assert(err, jc.ErrorIsNil)
   194  
   195  	assertNeedsCleanup(c, st)
   196  	assertCleanupCount(c, st, 2)
   197  
   198  	si, err = sb.AllStorageInstances()
   199  	c.Assert(err, jc.ErrorIsNil)
   200  	c.Assert(si, gc.HasLen, 0)
   201  	fs, err = sb.AllFilesystems()
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	c.Assert(fs, gc.HasLen, 0)
   204  
   205  	vols, err := sb.AllVolumes()
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(vols, gc.HasLen, 1)
   208  	c.Assert(vols[0].Life(), gc.Equals, state.Dying)
   209  	// A storage provisioner would call this.
   210  	err = sb.RemoveVolumeAttachment(unit.UnitTag(), vols[0].VolumeTag(), false)
   211  	c.Assert(err, jc.ErrorIsNil)
   212  	err = sb.RemoveVolume(vols[0].VolumeTag())
   213  	c.Assert(err, jc.ErrorIsNil)
   214  
   215  	// Undertaker would call this.
   216  	err = st.ProcessDyingModel()
   217  	c.Assert(err, jc.ErrorIsNil)
   218  	err = st.RemoveDyingModel()
   219  	c.Assert(err, jc.ErrorIsNil)
   220  }
   221  
   222  func (s *CAASModelSuite) TestCAASModelWrongCloudRegion(c *gc.C) {
   223  	cfg, _ := s.createTestModelConfig(c)
   224  	_, _, err := s.Controller.NewModel(state.ModelArgs{
   225  		Type:                    state.ModelTypeCAAS,
   226  		CloudName:               "dummy",
   227  		CloudRegion:             "fork",
   228  		Config:                  cfg,
   229  		Owner:                   names.NewUserTag("test@remote"),
   230  		StorageProviderRegistry: provider.CommonStorageProviders(),
   231  	})
   232  	c.Assert(err, gc.ErrorMatches, `region "fork" not found \(expected one of \["dotty.region" "dummy-region" "nether-region" "unused-region"\]\)`)
   233  }
   234  
   235  func (s *CAASModelSuite) TestDestroyControllerAndHostedCAASModels(c *gc.C) {
   236  	st2 := s.Factory.MakeCAASModel(c, nil)
   237  	defer st2.Close()
   238  
   239  	f := factory.NewFactory(st2, s.StatePool)
   240  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
   241  	app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch})
   242  
   243  	controllerModel, err := s.State.Model()
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	destroyStorage := true
   246  	c.Assert(controllerModel.Destroy(state.DestroyModelParams{
   247  		DestroyHostedModels: true,
   248  		DestroyStorage:      &destroyStorage,
   249  	}), jc.ErrorIsNil)
   250  
   251  	model, err := s.State.Model()
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	c.Assert(model.Life(), gc.Equals, state.Dying)
   254  
   255  	assertNeedsCleanup(c, s.State)
   256  	assertCleanupRuns(c, s.State)
   257  
   258  	// Cleanups for hosted model enqueued by controller model cleanups.
   259  	assertNeedsCleanup(c, st2)
   260  	assertCleanupRuns(c, st2)
   261  
   262  	model2, err := st2.Model()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	c.Assert(model2.Life(), gc.Equals, state.Dying)
   265  
   266  	// App removal requires cluster resources to be cleared.
   267  	err = app.Refresh()
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	err = app.ClearResources()
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	assertCleanupCount(c, st2, 2)
   272  
   273  	c.Assert(st2.ProcessDyingModel(), jc.ErrorIsNil)
   274  	c.Assert(st2.RemoveDyingModel(), jc.ErrorIsNil)
   275  
   276  	c.Assert(model2.Refresh(), jc.Satisfies, errors.IsNotFound)
   277  
   278  	c.Assert(s.State.ProcessDyingModel(), jc.ErrorIsNil)
   279  	c.Assert(s.State.RemoveDyingModel(), jc.ErrorIsNil)
   280  	c.Assert(model.Refresh(), jc.Satisfies, errors.IsNotFound)
   281  }
   282  
   283  func (s *CAASModelSuite) TestDestroyControllerAndHostedCAASModelsWithResources(c *gc.C) {
   284  	otherSt := s.Factory.MakeCAASModel(c, nil)
   285  	defer otherSt.Close()
   286  
   287  	assertModel := func(model *state.Model, st *state.State, life state.Life, expectedApps int) {
   288  		c.Assert(model.Refresh(), jc.ErrorIsNil)
   289  		c.Assert(model.Life(), gc.Equals, life)
   290  
   291  		apps, err := st.AllApplications()
   292  		c.Assert(err, jc.ErrorIsNil)
   293  		c.Assert(apps, gc.HasLen, expectedApps)
   294  	}
   295  
   296  	// add some applications
   297  	otherModel, err := otherSt.Model()
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	application := s.Factory.MakeApplication(c, &factory.ApplicationParams{Name: "gitlab"})
   300  	c.Assert(err, jc.ErrorIsNil)
   301  
   302  	f := factory.NewFactory(otherSt, s.StatePool)
   303  	ch := f.MakeCharm(c, &factory.CharmParams{Name: "gitlab", Series: "kubernetes"})
   304  	args := state.AddApplicationArgs{
   305  		Name: application.Name(),
   306  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   307  			OS:      "ubuntu",
   308  			Channel: "20.04/stable",
   309  		}},
   310  		Charm: ch,
   311  	}
   312  	application2, err := otherSt.AddApplication(args)
   313  	c.Assert(err, jc.ErrorIsNil)
   314  
   315  	controllerModel, err := s.State.Model()
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	destroyStorage := true
   318  	c.Assert(controllerModel.Destroy(state.DestroyModelParams{
   319  		DestroyHostedModels: true,
   320  		DestroyStorage:      &destroyStorage,
   321  	}), jc.ErrorIsNil)
   322  
   323  	assertCleanupCount(c, s.State, 2)
   324  	assertAllMachinesDeadAndRemove(c, s.State)
   325  	assertModel(controllerModel, s.State, state.Dying, 0)
   326  
   327  	err = s.State.ProcessDyingModel()
   328  	c.Assert(errors.Is(err, stateerrors.HasHostedModelsError), jc.IsTrue)
   329  	c.Assert(err, gc.ErrorMatches, `hosting 1 other model`)
   330  
   331  	assertCleanupCount(c, otherSt, 2)
   332  
   333  	// App removal requires cluster resources to be cleared.
   334  	err = application2.Refresh()
   335  	c.Assert(err, jc.ErrorIsNil)
   336  	err = application2.ClearResources()
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	assertCleanupCount(c, otherSt, 2)
   339  
   340  	assertModel(otherModel, otherSt, state.Dying, 0)
   341  	c.Assert(otherSt.ProcessDyingModel(), jc.ErrorIsNil)
   342  	c.Assert(otherSt.RemoveDyingModel(), jc.ErrorIsNil)
   343  
   344  	c.Assert(otherModel.Refresh(), jc.Satisfies, errors.IsNotFound)
   345  
   346  	c.Assert(s.State.ProcessDyingModel(), jc.ErrorIsNil)
   347  	c.Assert(s.State.RemoveDyingModel(), jc.ErrorIsNil)
   348  	c.Assert(controllerModel.Refresh(), jc.Satisfies, errors.IsNotFound)
   349  }
   350  
   351  func (s *CAASModelSuite) TestContainers(c *gc.C) {
   352  	m, st := s.newCAASModel(c)
   353  	f := factory.NewFactory(st, s.StatePool)
   354  	ch := f.MakeCharm(c, &factory.CharmParams{
   355  		Name:   "gitlab",
   356  		Series: "kubernetes",
   357  	})
   358  	app := f.MakeApplication(c, &factory.ApplicationParams{Charm: ch})
   359  
   360  	_, err := app.AddUnit(state.AddUnitParams{ProviderId: strPtr("provider-id1")})
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	_, err = app.AddUnit(state.AddUnitParams{ProviderId: strPtr("provider-id2")})
   363  	c.Assert(err, jc.ErrorIsNil)
   364  
   365  	containers, err := m.Containers("provider-id1", "provider-id2")
   366  	c.Assert(err, jc.ErrorIsNil)
   367  	c.Assert(containers, gc.HasLen, 2)
   368  	var unitNames []string
   369  	for _, c := range containers {
   370  		unitNames = append(unitNames, c.Unit())
   371  	}
   372  	c.Assert(unitNames, jc.SameContents, []string{app.Name() + "/0", app.Name() + "/1"})
   373  }
   374  
   375  func (s *CAASModelSuite) TestUnitStatusNoPodSpec(c *gc.C) {
   376  	m, st := s.newCAASModel(c)
   377  	f := factory.NewFactory(st, s.StatePool)
   378  	unit := f.MakeUnit(c, &factory.UnitParams{
   379  		Status: &status.StatusInfo{
   380  			Status:  status.Waiting,
   381  			Message: status.MessageInitializingAgent,
   382  		},
   383  	})
   384  
   385  	msWorkload := unitWorkloadStatus(c, m, unit.Name(), false)
   386  	c.Check(msWorkload.Message, gc.Equals, "agent initialising")
   387  	c.Check(msWorkload.Status, gc.Equals, status.Waiting)
   388  
   389  	err := unit.SetStatus(status.StatusInfo{Status: status.Active, Message: "running"})
   390  	c.Assert(err, jc.ErrorIsNil)
   391  	msWorkload = unitWorkloadStatus(c, m, unit.Name(), false)
   392  	c.Check(msWorkload.Message, gc.Equals, "running")
   393  	c.Check(msWorkload.Status, gc.Equals, status.Active)
   394  }
   395  
   396  func (s *CAASModelSuite) TestCloudContainerStatus(c *gc.C) {
   397  	m, st := s.newCAASModel(c)
   398  	f := factory.NewFactory(st, s.StatePool)
   399  	unit := f.MakeUnit(c, &factory.UnitParams{
   400  		Status: &status.StatusInfo{
   401  			Status:  status.Active,
   402  			Message: "Unit Active",
   403  		},
   404  	})
   405  
   406  	// Cloud container overrides Allocating unit
   407  	setCloudContainerStatus(c, unit, status.Allocating, "k8s allocating")
   408  	msWorkload := unitWorkloadStatus(c, m, unit.Name(), true)
   409  	c.Check(msWorkload.Message, gc.Equals, "k8s allocating")
   410  	c.Check(msWorkload.Status, gc.Equals, status.Allocating)
   411  
   412  	// Cloud container error overrides unit status
   413  	setCloudContainerStatus(c, unit, status.Error, "k8s charm error")
   414  	msWorkload = unitWorkloadStatus(c, m, unit.Name(), true)
   415  	c.Check(msWorkload.Message, gc.Equals, "k8s charm error")
   416  	c.Check(msWorkload.Status, gc.Equals, status.Error)
   417  
   418  	// Unit status must be used.
   419  	setCloudContainerStatus(c, unit, status.Running, "k8s idle")
   420  	msWorkload = unitWorkloadStatus(c, m, unit.Name(), true)
   421  	c.Check(msWorkload.Message, gc.Equals, "Unit Active")
   422  	c.Check(msWorkload.Status, gc.Equals, status.Active)
   423  
   424  	// Cloud container overrides
   425  	setCloudContainerStatus(c, unit, status.Blocked, "POD storage issue")
   426  	msWorkload = unitWorkloadStatus(c, m, unit.Name(), true)
   427  	c.Check(msWorkload.Message, gc.Equals, "POD storage issue")
   428  	c.Check(msWorkload.Status, gc.Equals, status.Blocked)
   429  
   430  	// Cloud container overrides
   431  	setCloudContainerStatus(c, unit, status.Waiting, "Building the bits")
   432  	msWorkload = unitWorkloadStatus(c, m, unit.Name(), true)
   433  	c.Check(msWorkload.Message, gc.Equals, "Building the bits")
   434  	c.Check(msWorkload.Status, gc.Equals, status.Waiting)
   435  
   436  	// Cloud container overrides
   437  	setCloudContainerStatus(c, unit, status.Running, "Bits have been built")
   438  	msWorkload = unitWorkloadStatus(c, m, unit.Name(), true)
   439  	c.Check(msWorkload.Message, gc.Equals, "Unit Active")
   440  	c.Check(msWorkload.Status, gc.Equals, status.Active)
   441  }
   442  
   443  func (s *CAASModelSuite) TestCloudContainerHistoryOverwrite(c *gc.C) {
   444  	m, st := s.newCAASModel(c)
   445  	f := factory.NewFactory(st, s.StatePool)
   446  	unit := f.MakeUnit(c, &factory.UnitParams{})
   447  
   448  	workloadStatus := unitWorkloadStatus(c, m, unit.Name(), true)
   449  	c.Assert(workloadStatus.Message, gc.Equals, status.MessageWaitForContainer)
   450  	c.Assert(workloadStatus.Status, gc.Equals, status.Waiting)
   451  	statusHistory, err := unit.StatusHistory(status.StatusHistoryFilter{Size: 10})
   452  	c.Assert(err, jc.ErrorIsNil)
   453  	c.Assert(statusHistory, gc.HasLen, 1)
   454  	c.Assert(statusHistory[0].Message, gc.Equals, status.MessageInstallingAgent)
   455  	c.Assert(statusHistory[0].Status, gc.Equals, status.Waiting)
   456  
   457  	err = unit.SetStatus(status.StatusInfo{
   458  		Status:  status.Active,
   459  		Message: "Unit Active",
   460  	})
   461  	c.Assert(err, jc.ErrorIsNil)
   462  	unitStatus, err := unit.Status()
   463  	c.Assert(err, jc.ErrorIsNil)
   464  	c.Assert(unitStatus.Message, gc.Equals, "Unit Active")
   465  	c.Assert(unitStatus.Status, gc.Equals, status.Active)
   466  
   467  	// Now that status is stored as Active, but displayed (and in history)
   468  	// as waiting for container, once we set cloud container status as active
   469  	// it must show active from the unit (incl. history)
   470  	setCloudContainerStatus(c, unit, status.Running, "Container Active")
   471  	workloadStatus = unitWorkloadStatus(c, m, unit.Name(), true)
   472  	c.Assert(workloadStatus.Message, gc.Equals, "Unit Active")
   473  	c.Assert(workloadStatus.Status, gc.Equals, status.Active)
   474  	statusHistory, err = unit.StatusHistory(status.StatusHistoryFilter{Size: 10})
   475  	c.Assert(err, jc.ErrorIsNil)
   476  	c.Assert(statusHistory, gc.HasLen, 2)
   477  	c.Assert(statusHistory[0].Message, gc.Equals, "Unit Active")
   478  	c.Assert(statusHistory[0].Status, gc.Equals, status.Active)
   479  	c.Assert(statusHistory[1].Message, gc.Equals, status.MessageInstallingAgent)
   480  	c.Assert(statusHistory[1].Status, gc.Equals, status.Waiting)
   481  
   482  	err = unit.SetStatus(status.StatusInfo{
   483  		Status:  status.Waiting,
   484  		Message: "This is a different message",
   485  	})
   486  	c.Assert(err, jc.ErrorIsNil)
   487  	workloadStatus = unitWorkloadStatus(c, m, unit.Name(), true)
   488  	c.Assert(workloadStatus.Message, gc.Equals, "This is a different message")
   489  	c.Assert(workloadStatus.Status, gc.Equals, status.Waiting)
   490  	statusHistory, err = unit.StatusHistory(status.StatusHistoryFilter{Size: 10})
   491  	c.Assert(err, jc.ErrorIsNil)
   492  	c.Assert(statusHistory, gc.HasLen, 3)
   493  	c.Assert(statusHistory[0].Message, gc.Equals, "This is a different message")
   494  	c.Assert(statusHistory[0].Status, gc.Equals, status.Waiting)
   495  	c.Assert(statusHistory[1].Message, gc.Equals, "Unit Active")
   496  	c.Assert(statusHistory[1].Status, gc.Equals, status.Active)
   497  	c.Assert(statusHistory[2].Message, gc.Equals, status.MessageInstallingAgent)
   498  	c.Assert(statusHistory[2].Status, gc.Equals, status.Waiting)
   499  }
   500  
   501  func unitWorkloadStatus(c *gc.C, model *state.CAASModel, unitName string, expectWorkload bool) status.StatusInfo {
   502  	ms, err := model.LoadModelStatus()
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	msWorkload, err := ms.UnitWorkload(unitName, expectWorkload)
   505  	c.Assert(err, jc.ErrorIsNil)
   506  	return msWorkload
   507  }
   508  
   509  func setCloudContainerStatus(c *gc.C, unit *state.Unit, statusCode status.Status, message string) {
   510  	var updateUnits state.UpdateUnitsOperation
   511  	updateUnits.Updates = []*state.UpdateUnitOperation{
   512  		unit.UpdateOperation(state.UnitUpdateProperties{
   513  			CloudContainerStatus: &status.StatusInfo{Status: statusCode, Message: message},
   514  		})}
   515  	app, err := unit.Application()
   516  	c.Assert(err, jc.ErrorIsNil)
   517  	err = app.UpdateUnits(&updateUnits)
   518  	c.Assert(err, jc.ErrorIsNil)
   519  }