github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/machinemanager/machinemanager_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package machinemanager_test
     5  
     6  import (
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/os/series"
    12  	jtesting "github.com/juju/testing"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  	"gopkg.in/juju/names.v2"
    16  
    17  	"github.com/juju/juju/apiserver/common"
    18  	"github.com/juju/juju/apiserver/common/storagecommon"
    19  	"github.com/juju/juju/apiserver/facades/client/machinemanager"
    20  	"github.com/juju/juju/apiserver/params"
    21  	apiservertesting "github.com/juju/juju/apiserver/testing"
    22  	"github.com/juju/juju/cloud"
    23  	"github.com/juju/juju/core/status"
    24  	"github.com/juju/juju/environs/context"
    25  	"github.com/juju/juju/state"
    26  	"github.com/juju/juju/state/multiwatcher"
    27  	"github.com/juju/juju/storage"
    28  	coretesting "github.com/juju/juju/testing"
    29  )
    30  
    31  var _ = gc.Suite(&MachineManagerSuite{})
    32  
    33  type MachineManagerSuite struct {
    34  	coretesting.BaseSuite
    35  	authorizer *apiservertesting.FakeAuthorizer
    36  	st         *mockState
    37  	pool       *mockPool
    38  	api        *machinemanager.MachineManagerAPI
    39  
    40  	callContext context.ProviderCallContext
    41  }
    42  
    43  func (s *MachineManagerSuite) setAPIUser(c *gc.C, user names.UserTag) {
    44  	s.authorizer.Tag = user
    45  	mm, err := machinemanager.NewMachineManagerAPI(s.st, s.st, s.pool, s.authorizer, s.st.ModelTag(), s.callContext, common.NewResources())
    46  	c.Assert(err, jc.ErrorIsNil)
    47  	s.api = mm
    48  }
    49  
    50  func (s *MachineManagerSuite) SetUpTest(c *gc.C) {
    51  	s.BaseSuite.SetUpTest(c)
    52  	s.st = &mockState{machines: make(map[string]*mockMachine)}
    53  	s.pool = &mockPool{}
    54  	s.authorizer = &apiservertesting.FakeAuthorizer{Tag: names.NewUserTag("admin")}
    55  	s.callContext = context.NewCloudCallContext()
    56  	var err error
    57  	s.api, err = machinemanager.NewMachineManagerAPI(s.st, s.st, s.pool, s.authorizer, s.st.ModelTag(), s.callContext, common.NewResources())
    58  	c.Assert(err, jc.ErrorIsNil)
    59  }
    60  
    61  func (s *MachineManagerSuite) TestAddMachines(c *gc.C) {
    62  	apiParams := make([]params.AddMachineParams, 2)
    63  	for i := range apiParams {
    64  		apiParams[i] = params.AddMachineParams{
    65  			Series: "trusty",
    66  			Jobs:   []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
    67  		}
    68  	}
    69  	apiParams[0].Disks = []storage.Constraints{{Size: 1, Count: 2}, {Size: 2, Count: 1}}
    70  	apiParams[1].Disks = []storage.Constraints{{Size: 1, Count: 2, Pool: "three"}}
    71  	machines, err := s.api.AddMachines(params.AddMachines{MachineParams: apiParams})
    72  	c.Assert(err, jc.ErrorIsNil)
    73  	c.Assert(machines.Machines, gc.HasLen, 2)
    74  	c.Assert(s.st.calls, gc.Equals, 2)
    75  	c.Assert(s.st.machineTemplates, jc.DeepEquals, []state.MachineTemplate{
    76  		{
    77  			Series: "trusty",
    78  			Jobs:   []state.MachineJob{state.JobHostUnits},
    79  			Volumes: []state.HostVolumeParams{
    80  				{
    81  					Volume:     state.VolumeParams{Pool: "", Size: 1},
    82  					Attachment: state.VolumeAttachmentParams{ReadOnly: false},
    83  				},
    84  				{
    85  					Volume:     state.VolumeParams{Pool: "", Size: 1},
    86  					Attachment: state.VolumeAttachmentParams{ReadOnly: false},
    87  				},
    88  				{
    89  					Volume:     state.VolumeParams{Pool: "", Size: 2},
    90  					Attachment: state.VolumeAttachmentParams{ReadOnly: false},
    91  				},
    92  			},
    93  		},
    94  		{
    95  			Series: "trusty",
    96  			Jobs:   []state.MachineJob{state.JobHostUnits},
    97  			Volumes: []state.HostVolumeParams{
    98  				{
    99  					Volume:     state.VolumeParams{Pool: "three", Size: 1},
   100  					Attachment: state.VolumeAttachmentParams{ReadOnly: false},
   101  				},
   102  				{
   103  					Volume:     state.VolumeParams{Pool: "three", Size: 1},
   104  					Attachment: state.VolumeAttachmentParams{ReadOnly: false},
   105  				},
   106  			},
   107  		},
   108  	})
   109  }
   110  
   111  func (s *MachineManagerSuite) TestNewMachineManagerAPINonClient(c *gc.C) {
   112  	tag := names.NewUnitTag("mysql/0")
   113  	s.authorizer = &apiservertesting.FakeAuthorizer{Tag: tag}
   114  	_, err := machinemanager.NewMachineManagerAPI(nil, nil, nil, s.authorizer, names.ModelTag{}, s.callContext, common.NewResources())
   115  	c.Assert(err, gc.ErrorMatches, "permission denied")
   116  }
   117  
   118  func (s *MachineManagerSuite) TestAddMachinesStateError(c *gc.C) {
   119  	s.st.err = errors.New("boom")
   120  	results, err := s.api.AddMachines(params.AddMachines{
   121  		MachineParams: []params.AddMachineParams{{
   122  			Series: "trusty",
   123  		}},
   124  	})
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	c.Assert(results, gc.DeepEquals, params.AddMachinesResults{
   127  		Machines: []params.AddMachinesResult{{
   128  			Error: &params.Error{Message: "boom", Code: ""},
   129  		}},
   130  	})
   131  	c.Assert(s.st.calls, gc.Equals, 1)
   132  }
   133  
   134  func (s *MachineManagerSuite) TestDestroyMachine(c *gc.C) {
   135  	s.st.machines["0"] = &mockMachine{}
   136  	results, err := s.api.DestroyMachine(params.Entities{
   137  		Entities: []params.Entity{{Tag: "machine-0"}},
   138  	})
   139  	c.Assert(err, jc.ErrorIsNil)
   140  	c.Assert(results, jc.DeepEquals, params.DestroyMachineResults{
   141  		Results: []params.DestroyMachineResult{{
   142  			Info: &params.DestroyMachineInfo{
   143  				DestroyedUnits: []params.Entity{
   144  					{"unit-foo-0"},
   145  					{"unit-foo-1"},
   146  					{"unit-foo-2"},
   147  				},
   148  				DetachedStorage: []params.Entity{
   149  					{"storage-disks-0"},
   150  				},
   151  				DestroyedStorage: []params.Entity{
   152  					{"storage-disks-1"},
   153  				},
   154  			},
   155  		}},
   156  	})
   157  }
   158  
   159  func (s *MachineManagerSuite) TestDestroyMachineWithParams(c *gc.C) {
   160  	apiV4 := s.machineManagerAPIV4()
   161  	s.st.machines["0"] = &mockMachine{}
   162  	results, err := apiV4.DestroyMachineWithParams(params.DestroyMachinesParams{
   163  		Keep:        true,
   164  		Force:       true,
   165  		MachineTags: []string{"machine-0"},
   166  	})
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	m, err := s.st.Machine("0")
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	c.Assert(m.(*mockMachine).keep, jc.IsTrue)
   171  	c.Assert(results, jc.DeepEquals, params.DestroyMachineResults{
   172  		Results: []params.DestroyMachineResult{{
   173  			Info: &params.DestroyMachineInfo{
   174  				DestroyedUnits: []params.Entity{
   175  					{"unit-foo-0"},
   176  					{"unit-foo-1"},
   177  					{"unit-foo-2"},
   178  				},
   179  				DetachedStorage: []params.Entity{
   180  					{"storage-disks-0"},
   181  				},
   182  				DestroyedStorage: []params.Entity{
   183  					{"storage-disks-1"},
   184  				},
   185  			},
   186  		}},
   187  	})
   188  }
   189  
   190  func (s *MachineManagerSuite) setupUpgradeSeries(c *gc.C) {
   191  	s.st.machines = map[string]*mockMachine{
   192  		"0": {series: "trusty", units: []string{"foo/0", "test/0"}},
   193  		"1": {series: "trusty", units: []string{"foo/1", "test/1"}},
   194  		"2": {series: "centos7", units: []string{"foo/1", "test/1"}},
   195  		"3": {series: "bionic", isManager: true},
   196  	}
   197  }
   198  
   199  func (s *MachineManagerSuite) TestUpgradeSeriesValidateOK(c *gc.C) {
   200  	s.setupUpgradeSeries(c)
   201  	s.st.machines["0"].unitAgentState = status.Idle
   202  
   203  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   204  	args := params.UpdateSeriesArgs{
   205  		Args: []params.UpdateSeriesArg{{
   206  			Entity: params.Entity{Tag: names.NewMachineTag("0").String()},
   207  			Series: "xenial",
   208  		}},
   209  	}
   210  	results, err := apiV5.UpgradeSeriesValidate(args)
   211  	c.Assert(err, jc.ErrorIsNil)
   212  
   213  	result := results.Results[0]
   214  	c.Assert(result.Error, gc.IsNil)
   215  
   216  	var expectedUnitNames []string
   217  	for _, unit := range s.st.machines["0"].Principals() {
   218  		expectedUnitNames = append(expectedUnitNames, unit)
   219  	}
   220  	c.Assert(result.UnitNames, gc.DeepEquals, expectedUnitNames)
   221  }
   222  
   223  func (s *MachineManagerSuite) TestUpgradeSeriesValidateIsControllerError(c *gc.C) {
   224  	s.setupUpgradeSeries(c)
   225  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   226  	args := params.UpdateSeriesArgs{
   227  		Args: []params.UpdateSeriesArg{{
   228  			Entity: params.Entity{Tag: names.NewMachineTag("3").String()},
   229  		}},
   230  	}
   231  	results, err := apiV5.UpgradeSeriesValidate(args)
   232  	c.Assert(err, jc.ErrorIsNil)
   233  
   234  	c.Assert(results.Results[0].Error, gc.ErrorMatches,
   235  		"machine-3 is a controller and cannot be targeted for series upgrade")
   236  }
   237  
   238  func (s *MachineManagerSuite) TestUpgradeSeriesValidateNoSeriesError(c *gc.C) {
   239  	s.setupUpgradeSeries(c)
   240  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   241  	args := params.UpdateSeriesArgs{
   242  		Args: []params.UpdateSeriesArg{{
   243  			Entity: params.Entity{Tag: names.NewMachineTag("1").String()},
   244  		}},
   245  	}
   246  	results, err := apiV5.UpgradeSeriesValidate(args)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	c.Assert(results.Results[0].Error, gc.ErrorMatches, "series missing from args")
   250  }
   251  
   252  func (s *MachineManagerSuite) TestUpgradeSeriesValidateNotFromUbuntuError(c *gc.C) {
   253  	s.setupUpgradeSeries(c)
   254  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   255  	args := params.UpdateSeriesArgs{
   256  		Args: []params.UpdateSeriesArg{{
   257  			Entity: params.Entity{Tag: names.NewMachineTag("2").String()},
   258  			Series: "bionic",
   259  		}},
   260  	}
   261  
   262  	results, err := apiV5.UpgradeSeriesValidate(args)
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	c.Assert(results.Results[0].Error, gc.ErrorMatches,
   265  		"machine-2 is running CentOS and is not valid for Ubuntu series upgrade")
   266  }
   267  
   268  func (s *MachineManagerSuite) TestUpgradeSeriesValidateNotToUbuntuError(c *gc.C) {
   269  	s.setupUpgradeSeries(c)
   270  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   271  	args := params.UpdateSeriesArgs{
   272  		Args: []params.UpdateSeriesArg{{
   273  			Entity: params.Entity{Tag: names.NewMachineTag("1").String()},
   274  			Series: "centos7",
   275  		}},
   276  	}
   277  
   278  	results, err := apiV5.UpgradeSeriesValidate(args)
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	c.Assert(results.Results[0].Error, gc.ErrorMatches,
   281  		`series "centos7" is from OS "CentOS" and is not a valid upgrade target`)
   282  }
   283  
   284  func (s *MachineManagerSuite) TestUpgradeSeriesValidateAlreadyRunningSeriesError(c *gc.C) {
   285  	s.setupUpgradeSeries(c)
   286  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   287  	args := params.UpdateSeriesArgs{
   288  		Args: []params.UpdateSeriesArg{{
   289  			Entity: params.Entity{Tag: names.NewMachineTag("1").String()},
   290  			Series: "trusty",
   291  		}},
   292  	}
   293  
   294  	results, err := apiV5.UpgradeSeriesValidate(args)
   295  	c.Assert(err, jc.ErrorIsNil)
   296  	c.Assert(results.Results[0].Error, gc.ErrorMatches, "machine-1 is already running series trusty")
   297  }
   298  
   299  func (s *MachineManagerSuite) TestUpgradeSeriesValidateOlderSeriesError(c *gc.C) {
   300  	s.setupUpgradeSeries(c)
   301  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   302  	args := params.UpdateSeriesArgs{
   303  		Args: []params.UpdateSeriesArg{{
   304  			Entity: params.Entity{Tag: names.NewMachineTag("1").String()},
   305  			Series: "precise",
   306  		}},
   307  	}
   308  
   309  	results, err := apiV5.UpgradeSeriesValidate(args)
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	c.Assert(results.Results[0].Error, gc.ErrorMatches,
   312  		"machine machine-1 is running trusty which is a newer series than precise.")
   313  }
   314  
   315  func (s *MachineManagerSuite) TestUpgradeSeriesValidateUnitNotIdleError(c *gc.C) {
   316  	s.setupUpgradeSeries(c)
   317  	s.st.machines["0"].unitAgentState = status.Executing
   318  	s.st.machines["0"].unitState = status.Active
   319  
   320  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   321  	args := params.UpdateSeriesArgs{
   322  		Args: []params.UpdateSeriesArg{{
   323  			Entity: params.Entity{Tag: names.NewMachineTag("0").String()},
   324  			Series: "xenial",
   325  		}},
   326  	}
   327  	results, err := apiV5.UpgradeSeriesValidate(args)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Assert(results.Results[0].Error, gc.ErrorMatches,
   330  		"unit unit-foo-[0-2] is not ready to start a series upgrade; its agent status is: \"executing\" ")
   331  }
   332  
   333  func (s *MachineManagerSuite) TestUpgradeSeriesValidateUnitStatusError(c *gc.C) {
   334  	s.setupUpgradeSeries(c)
   335  	s.st.machines["0"].unitAgentState = status.Idle
   336  	s.st.machines["0"].unitState = status.Error
   337  
   338  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   339  	args := params.UpdateSeriesArgs{
   340  		Args: []params.UpdateSeriesArg{{
   341  			Entity: params.Entity{Tag: names.NewMachineTag("0").String()},
   342  			Series: "xenial",
   343  		}},
   344  	}
   345  	results, err := apiV5.UpgradeSeriesValidate(args)
   346  	c.Assert(err, jc.ErrorIsNil)
   347  	c.Assert(results.Results[0].Error, gc.ErrorMatches,
   348  		"unit unit-foo-[0-2] is not ready to start a series upgrade; its status is: \"error\" ")
   349  }
   350  
   351  func (s *MachineManagerSuite) TestUpgradeSeriesPrepare(c *gc.C) {
   352  	s.setupUpgradeSeries(c)
   353  	s.st.machines["0"].unitAgentState = status.Idle
   354  
   355  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   356  	machineTag := names.NewMachineTag("0")
   357  	result, err := apiV5.UpgradeSeriesPrepare(
   358  		params.UpdateSeriesArg{
   359  			Entity: params.Entity{
   360  				Tag: machineTag.String()},
   361  			Series: "xenial",
   362  		},
   363  	)
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	c.Assert(result.Error, gc.IsNil)
   366  
   367  	mach := s.st.machines["0"]
   368  	c.Assert(len(mach.Calls()), gc.Equals, 3)
   369  	mach.CheckCallNames(c, "Principals", "VerifyUnitsSeries", "CreateUpgradeSeriesLock")
   370  	mach.CheckCall(c, 2, "CreateUpgradeSeriesLock", []string{"foo/0", "test/0"}, "xenial")
   371  }
   372  
   373  func (s *MachineManagerSuite) TestUpgradeSeriesPrepareMachineNotFound(c *gc.C) {
   374  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   375  	machineTag := names.NewMachineTag("76")
   376  	result, err := apiV5.UpgradeSeriesPrepare(
   377  		params.UpdateSeriesArg{
   378  			Entity: params.Entity{
   379  				Tag: machineTag.String()},
   380  			Series: "trusty",
   381  		},
   382  	)
   383  	c.Assert(err, jc.ErrorIsNil)
   384  	c.Assert(result.Error, gc.ErrorMatches, "machine 76 not found")
   385  }
   386  
   387  func (s *MachineManagerSuite) TestUpgradeSeriesPrepareNotMachineTag(c *gc.C) {
   388  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   389  	unitTag := names.NewUnitTag("mysql/0")
   390  	result, err := apiV5.UpgradeSeriesPrepare(
   391  		params.UpdateSeriesArg{
   392  			Entity: params.Entity{
   393  				Tag: unitTag.String()},
   394  			Series: "trusty",
   395  		},
   396  	)
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Assert(result.Error, gc.ErrorMatches, "\"unit-mysql-0\" is not a valid machine tag")
   399  }
   400  
   401  func (s *MachineManagerSuite) TestUpgradeSeriesPreparePermissionDenied(c *gc.C) {
   402  	user := names.NewUserTag("fred")
   403  	s.setAPIUser(c, user)
   404  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   405  	machineTag := names.NewMachineTag("0")
   406  	_, err := apiV5.UpgradeSeriesPrepare(
   407  		params.UpdateSeriesArg{
   408  			Entity: params.Entity{
   409  				Tag: machineTag.String()},
   410  			Series: "xenial",
   411  		},
   412  	)
   413  	c.Assert(err, gc.ErrorMatches, "permission denied")
   414  }
   415  
   416  func (s *MachineManagerSuite) TestUpgradeSeriesPrepareBlockedChanges(c *gc.C) {
   417  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   418  	s.st.blockMsg = "TestUpgradeSeriesPrepareBlockedChanges"
   419  	s.st.block = state.ChangeBlock
   420  	_, err := apiV5.UpgradeSeriesPrepare(
   421  		params.UpdateSeriesArg{
   422  			Entity: params.Entity{
   423  				Tag: names.NewMachineTag("0").String()},
   424  			Series: "xenial",
   425  		},
   426  	)
   427  	c.Assert(params.IsCodeOperationBlocked(err), jc.IsTrue, gc.Commentf("error: %#v", err))
   428  	c.Assert(errors.Cause(err), jc.DeepEquals, &params.Error{
   429  		Message: "TestUpgradeSeriesPrepareBlockedChanges",
   430  		Code:    "operation is blocked",
   431  	})
   432  }
   433  
   434  func (s *MachineManagerSuite) TestUpgradeSeriesPrepareNoSeries(c *gc.C) {
   435  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   436  	result, err := apiV5.UpgradeSeriesPrepare(
   437  		params.UpdateSeriesArg{
   438  			Entity: params.Entity{Tag: names.NewMachineTag("0").String()},
   439  		},
   440  	)
   441  	c.Assert(err, jc.ErrorIsNil)
   442  	c.Assert(result, jc.DeepEquals, params.ErrorResult{
   443  		Error: &params.Error{
   444  			Code:    params.CodeBadRequest,
   445  			Message: `series missing from args`,
   446  		},
   447  	})
   448  }
   449  
   450  func (s *MachineManagerSuite) TestUpgradeSeriesPrepareIncompatibleSeries(c *gc.C) {
   451  	s.setupUpgradeSeries(c)
   452  	s.st.machines["0"].SetErrors(&state.ErrIncompatibleSeries{
   453  		SeriesList: []string{"yakkety", "zesty"},
   454  		Series:     "xenial",
   455  		CharmName:  "TestCharm",
   456  	})
   457  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   458  	result, err := apiV5.UpgradeSeriesPrepare(
   459  		params.UpdateSeriesArg{
   460  			Entity: params.Entity{Tag: names.NewMachineTag("0").String()},
   461  			Series: "xenial",
   462  			Force:  false,
   463  		},
   464  	)
   465  	c.Assert(err, jc.ErrorIsNil)
   466  	c.Assert(result, jc.DeepEquals, params.ErrorResult{
   467  		Error: &params.Error{
   468  			Code:    params.CodeIncompatibleSeries,
   469  			Message: "series \"xenial\" not supported by charm \"TestCharm\", supported series are: yakkety, zesty",
   470  		},
   471  	})
   472  }
   473  
   474  func (s *MachineManagerSuite) TestUpgradeSeriesPrepareRemoveLockAfterFail(c *gc.C) {
   475  	// TODO managed upgrade series
   476  }
   477  
   478  func (s *MachineManagerSuite) TestUpgradeSeriesComplete(c *gc.C) {
   479  	s.setupUpgradeSeries(c)
   480  	apiV5 := machinemanager.MachineManagerAPIV5{MachineManagerAPI: s.api}
   481  	_, err := apiV5.UpgradeSeriesComplete(
   482  		params.UpdateSeriesArg{
   483  			Entity: params.Entity{Tag: names.NewMachineTag("0").String()},
   484  		},
   485  	)
   486  	c.Assert(err, jc.ErrorIsNil)
   487  }
   488  
   489  // TestIsSeriesLessThan tests a validation method which is not very complicated
   490  // but complex enough to warrant being exported from an export test package for
   491  // testing.
   492  func (s *MachineManagerSuite) TestIsSeriesLessThan(c *gc.C) {
   493  	ss := series.SupportedSeries()
   494  
   495  	// get the series versions
   496  	vs := make([]string, 0, len(ss))
   497  	for _, ser := range ss {
   498  		ver, err := series.SeriesVersion(ser)
   499  		c.Assert(err, jc.ErrorIsNil)
   500  		vs = append(vs, ver)
   501  	}
   502  
   503  	// sort the values, so the lexicographical order is determined
   504  	sort.Strings(vs)
   505  
   506  	// check that the IsSeriesLessThan works for all supported series
   507  	for i := range vs {
   508  
   509  		// We need both the series and the next series in the list. So
   510  		// we provide a check here to prevent going out of bounds.
   511  		if i+1 > len(vs)-1 {
   512  			break
   513  		}
   514  
   515  		// get the series for the specified version
   516  		s1, err := series.VersionSeries(vs[i])
   517  		c.Assert(err, jc.ErrorIsNil)
   518  		s2, err := series.VersionSeries(vs[i+1])
   519  		c.Assert(err, jc.ErrorIsNil)
   520  
   521  		isLessThan, err := machinemanager.IsSeriesLessThan(s1, s2)
   522  		c.Assert(err, jc.ErrorIsNil)
   523  		c.Assert(isLessThan, jc.IsTrue)
   524  	}
   525  }
   526  
   527  type mockState struct {
   528  	machinemanager.Backend
   529  	calls            int
   530  	machineTemplates []state.MachineTemplate
   531  	machines         map[string]*mockMachine
   532  	err              error
   533  	blockMsg         string
   534  	block            state.BlockType
   535  }
   536  
   537  type mockVolumeAccess struct {
   538  	storagecommon.VolumeAccess
   539  	*mockState
   540  }
   541  
   542  func (st *mockVolumeAccess) StorageInstanceVolume(tag names.StorageTag) (state.Volume, error) {
   543  	return &mockVolume{
   544  		detachable: tag.Id() == "disks/0",
   545  	}, nil
   546  }
   547  
   548  type mockFilesystemAccess struct {
   549  	storagecommon.FilesystemAccess
   550  	*mockState
   551  }
   552  
   553  func (st *mockState) VolumeAccess() storagecommon.VolumeAccess {
   554  	return &mockVolumeAccess{mockState: st}
   555  }
   556  
   557  func (st *mockState) FilesystemAccess() storagecommon.FilesystemAccess {
   558  	return &mockFilesystemAccess{mockState: st}
   559  }
   560  
   561  func (st *mockState) AddOneMachine(template state.MachineTemplate) (*state.Machine, error) {
   562  	st.calls++
   563  	st.machineTemplates = append(st.machineTemplates, template)
   564  	m := state.Machine{}
   565  	return &m, st.err
   566  }
   567  
   568  func (st *mockState) GetBlockForType(t state.BlockType) (state.Block, bool, error) {
   569  	if st.block == t {
   570  		return &mockBlock{t: t, m: st.blockMsg}, true, nil
   571  	} else {
   572  		return nil, false, nil
   573  	}
   574  }
   575  
   576  func (st *mockState) ModelTag() names.ModelTag {
   577  	return names.NewModelTag("deadbeef-2f18-4fd2-967d-db9663db7bea")
   578  }
   579  
   580  func (st *mockState) Model() (machinemanager.Model, error) {
   581  	return &mockModel{}, nil
   582  }
   583  
   584  func (st *mockState) CloudCredential(tag names.CloudCredentialTag) (state.Credential, error) {
   585  	return state.Credential{}, nil
   586  }
   587  
   588  func (st *mockState) Cloud(string) (cloud.Cloud, error) {
   589  	return cloud.Cloud{}, nil
   590  }
   591  
   592  func (st *mockState) Machine(id string) (machinemanager.Machine, error) {
   593  	if m, ok := st.machines[id]; !ok {
   594  		return nil, errors.NotFoundf("machine %v", id)
   595  	} else {
   596  		return m, nil
   597  	}
   598  }
   599  
   600  func (st *mockState) StorageInstance(tag names.StorageTag) (state.StorageInstance, error) {
   601  	return &mockStorage{
   602  		tag:  tag,
   603  		kind: state.StorageKindBlock,
   604  	}, nil
   605  }
   606  
   607  func (st *mockState) UnitStorageAttachments(tag names.UnitTag) ([]state.StorageAttachment, error) {
   608  	if tag.Id() == "foo/0" {
   609  		return []state.StorageAttachment{
   610  			&mockStorageAttachment{unit: tag, storage: names.NewStorageTag("disks/0")},
   611  			&mockStorageAttachment{unit: tag, storage: names.NewStorageTag("disks/1")},
   612  		}, nil
   613  	}
   614  	return nil, nil
   615  }
   616  
   617  type mockBlock struct {
   618  	state.Block
   619  	t state.BlockType
   620  	m string
   621  }
   622  
   623  func (st *mockBlock) Id() string {
   624  	return "id"
   625  }
   626  
   627  func (st *mockBlock) Tag() (names.Tag, error) {
   628  	return names.ParseTag("machine-1")
   629  }
   630  
   631  func (st *mockBlock) Type() state.BlockType {
   632  	return state.ChangeBlock
   633  }
   634  
   635  func (st *mockBlock) Message() string {
   636  	return st.m
   637  }
   638  
   639  func (st *mockBlock) ModelUUID() string {
   640  	return "uuid"
   641  }
   642  
   643  type mockMachine struct {
   644  	jtesting.Stub
   645  	machinemanager.Machine
   646  
   647  	keep           bool
   648  	series         string
   649  	units          []string
   650  	unitAgentState status.Status
   651  	unitState      status.Status
   652  	isManager      bool
   653  }
   654  
   655  func (m *mockMachine) Destroy() error {
   656  	return nil
   657  }
   658  
   659  func (m *mockMachine) ForceDestroy() error {
   660  	return nil
   661  }
   662  
   663  func (m *mockMachine) Principals() []string {
   664  	m.MethodCall(m, "Principals")
   665  	return m.units
   666  }
   667  
   668  func (m *mockMachine) SetKeepInstance(keep bool) error {
   669  	m.keep = keep
   670  	return nil
   671  }
   672  
   673  func (m *mockMachine) Series() string {
   674  	m.MethodCall(m, "Series")
   675  	return m.series
   676  }
   677  
   678  func (m *mockMachine) Units() ([]machinemanager.Unit, error) {
   679  	return []machinemanager.Unit{
   680  		&mockUnit{tag: names.NewUnitTag("foo/0")},
   681  		&mockUnit{tag: names.NewUnitTag("foo/1")},
   682  		&mockUnit{tag: names.NewUnitTag("foo/2")},
   683  	}, nil
   684  }
   685  
   686  func (m *mockMachine) VerifyUnitsSeries(units []string, series string, force bool) ([]machinemanager.Unit, error) {
   687  	m.MethodCall(m, "VerifyUnitsSeries", units, series, force)
   688  	out := make([]machinemanager.Unit, len(m.units))
   689  	for i, name := range m.units {
   690  		out[i] = &mockUnit{
   691  			tag:         names.NewUnitTag(name),
   692  			agentStatus: m.unitAgentState,
   693  			unitStatus:  m.unitState,
   694  		}
   695  	}
   696  	return out, m.NextErr()
   697  }
   698  
   699  func (m *mockMachine) CreateUpgradeSeriesLock(unitTags []string, series string) error {
   700  	m.MethodCall(m, "CreateUpgradeSeriesLock", unitTags, series)
   701  	return m.NextErr()
   702  }
   703  
   704  func (m *mockMachine) RemoveUpgradeSeriesLock() error {
   705  	m.MethodCall(m, "RemoveUpgradeSeriesLock")
   706  	return m.NextErr()
   707  }
   708  
   709  func (m *mockMachine) CompleteUpgradeSeries() error {
   710  	m.MethodCall(m, "CompleteUpgradeSeries")
   711  	return m.NextErr()
   712  }
   713  
   714  func (m *mockMachine) IsManager() bool {
   715  	return m.isManager
   716  }
   717  
   718  type mockUnit struct {
   719  	tag         names.UnitTag
   720  	agentStatus status.Status
   721  	unitStatus  status.Status
   722  }
   723  
   724  func (u *mockUnit) UnitTag() names.UnitTag {
   725  	return u.tag
   726  }
   727  
   728  func (u *mockUnit) Name() string {
   729  	return u.tag.String()
   730  }
   731  
   732  func (u *mockUnit) AgentStatus() (status.StatusInfo, error) {
   733  	return status.StatusInfo{Status: u.agentStatus}, nil
   734  }
   735  
   736  func (u *mockUnit) Status() (status.StatusInfo, error) {
   737  	return status.StatusInfo{Status: u.unitStatus}, nil
   738  }
   739  
   740  func (u *mockUnit) ApplicationName() string {
   741  	return strings.Split(u.tag.String(), "-")[1]
   742  }
   743  
   744  type mockStorage struct {
   745  	state.StorageInstance
   746  	tag  names.StorageTag
   747  	kind state.StorageKind
   748  }
   749  
   750  func (a *mockStorage) StorageTag() names.StorageTag {
   751  	return a.tag
   752  }
   753  
   754  func (a *mockStorage) Kind() state.StorageKind {
   755  	return a.kind
   756  }
   757  
   758  type mockStorageAttachment struct {
   759  	state.StorageAttachment
   760  	unit    names.UnitTag
   761  	storage names.StorageTag
   762  }
   763  
   764  func (a *mockStorageAttachment) Unit() names.UnitTag {
   765  	return a.unit
   766  }
   767  
   768  func (a *mockStorageAttachment) StorageInstance() names.StorageTag {
   769  	return a.storage
   770  }
   771  
   772  type mockVolume struct {
   773  	state.Volume
   774  	detachable bool
   775  }
   776  
   777  func (v *mockVolume) Detachable() bool {
   778  	return v.detachable
   779  }
   780  
   781  func (s *MachineManagerSuite) machineManagerAPIV4() machinemanager.MachineManagerAPIV4 {
   782  	managerV5 := &machinemanager.MachineManagerAPIV5{s.api}
   783  	return machinemanager.MachineManagerAPIV4{managerV5}
   784  }