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

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"time"
    11  
    12  	"github.com/juju/names/v5"
    13  	jc "github.com/juju/testing/checkers"
    14  	jujutxn "github.com/juju/txn/v3"
    15  	gc "gopkg.in/check.v1"
    16  
    17  	"github.com/juju/juju/core/constraints"
    18  	"github.com/juju/juju/core/container"
    19  	"github.com/juju/juju/core/instance"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/storage/poolmanager"
    22  	"github.com/juju/juju/storage/provider"
    23  )
    24  
    25  type AssignSuite struct {
    26  	ConnSuite
    27  	wordpress *state.Application
    28  }
    29  
    30  var _ = gc.Suite(&AssignSuite{})
    31  
    32  func (s *AssignSuite) SetUpTest(c *gc.C) {
    33  	s.ConnSuite.SetUpTest(c)
    34  	wordpress := s.AddTestingApplication(
    35  		c,
    36  		"wordpress",
    37  		s.AddTestingCharm(c, "wordpress"),
    38  	)
    39  	s.wordpress = wordpress
    40  }
    41  
    42  func (s *AssignSuite) addSubordinate(c *gc.C, principal *state.Unit) *state.Unit {
    43  	s.AddTestingApplication(c, "logging", s.AddTestingCharm(c, "logging"))
    44  	eps, err := s.State.InferEndpoints("logging", "wordpress")
    45  	c.Assert(err, jc.ErrorIsNil)
    46  	rel, err := s.State.AddRelation(eps...)
    47  	c.Assert(err, jc.ErrorIsNil)
    48  	ru, err := rel.Unit(principal)
    49  	c.Assert(err, jc.ErrorIsNil)
    50  	err = ru.EnterScope(nil)
    51  	c.Assert(err, jc.ErrorIsNil)
    52  	subUnit, err := s.State.Unit("logging/0")
    53  	c.Assert(err, jc.ErrorIsNil)
    54  	return subUnit
    55  }
    56  
    57  func (s *AssignSuite) TestUnassignUnitFromMachineWithoutBeingAssigned(c *gc.C) {
    58  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
    59  	c.Assert(err, jc.ErrorIsNil)
    60  	// When unassigning a machine from a unit, it is possible that
    61  	// the machine has not been previously assigned, or that it
    62  	// was assigned but the state changed beneath us.  In either
    63  	// case, the end state is the intended state, so we simply
    64  	// move forward without any errors here, to avoid having to
    65  	// handle the extra complexity of dealing with the concurrency
    66  	// problems.
    67  	err = unit.UnassignFromMachine()
    68  	c.Assert(err, jc.ErrorIsNil)
    69  
    70  	// Check that the unit has no machine assigned.
    71  	_, err = unit.AssignedMachineId()
    72  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is not assigned to a machine`)
    73  }
    74  
    75  func (s *AssignSuite) TestAssignUnitToMachineAgainFails(c *gc.C) {
    76  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
    77  	c.Assert(err, jc.ErrorIsNil)
    78  	// Check that assigning an already assigned unit to
    79  	// a machine fails if it isn't precisely the same
    80  	// machine.
    81  	machineOne, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
    82  	c.Assert(err, jc.ErrorIsNil)
    83  	machineTwo, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
    84  	c.Assert(err, jc.ErrorIsNil)
    85  
    86  	err = unit.AssignToMachine(machineOne)
    87  	c.Assert(err, jc.ErrorIsNil)
    88  
    89  	// Assigning the unit to the same machine should return no error.
    90  	err = unit.AssignToMachine(machineOne)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  
    93  	// Assigning the unit to a different machine should fail.
    94  	err = unit.AssignToMachine(machineTwo)
    95  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/0" to machine 1: unit is already assigned to a machine`)
    96  
    97  	machineId, err := unit.AssignedMachineId()
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	c.Assert(machineId, gc.Equals, "0")
   100  }
   101  
   102  func (s *AssignSuite) TestAssignedMachineIdWhenNotAlive(c *gc.C) {
   103  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   104  	c.Assert(err, jc.ErrorIsNil)
   105  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   106  	c.Assert(err, jc.ErrorIsNil)
   107  
   108  	err = unit.AssignToMachine(machine)
   109  	c.Assert(err, jc.ErrorIsNil)
   110  
   111  	testWhenDying(c, unit, noErr, noErr,
   112  		func() error {
   113  			_, err = unit.AssignedMachineId()
   114  			return err
   115  		})
   116  }
   117  
   118  func (s *AssignSuite) TestAssignedMachineIdWhenPrincipalNotAlive(c *gc.C) {
   119  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   120  	c.Assert(err, jc.ErrorIsNil)
   121  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   122  	c.Assert(err, jc.ErrorIsNil)
   123  	err = unit.AssignToMachine(machine)
   124  	c.Assert(err, jc.ErrorIsNil)
   125  
   126  	subUnit := s.addSubordinate(c, unit)
   127  	err = unit.Destroy()
   128  	c.Assert(err, jc.ErrorIsNil)
   129  	mid, err := subUnit.AssignedMachineId()
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	c.Assert(mid, gc.Equals, machine.Id())
   132  }
   133  
   134  func (s *AssignSuite) TestUnassignUnitFromMachineWithChangingState(c *gc.C) {
   135  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   136  	c.Assert(err, jc.ErrorIsNil)
   137  	// Check that unassigning while the state changes fails nicely.
   138  	// Remove the unit for the tests.
   139  	err = unit.EnsureDead()
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	err = unit.Remove()
   142  	c.Assert(err, jc.ErrorIsNil)
   143  
   144  	err = unit.UnassignFromMachine()
   145  	c.Assert(err, gc.ErrorMatches, `cannot unassign unit "wordpress/0" from machine: .*`)
   146  	_, err = unit.AssignedMachineId()
   147  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is not assigned to a machine`)
   148  
   149  	err = s.wordpress.Destroy()
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	err = unit.UnassignFromMachine()
   152  	c.Assert(err, gc.ErrorMatches, `cannot unassign unit "wordpress/0" from machine: .*`)
   153  	_, err = unit.AssignedMachineId()
   154  	c.Assert(err, gc.ErrorMatches, `unit "wordpress/0" is not assigned to a machine`)
   155  }
   156  
   157  func (s *AssignSuite) TestAssignSubordinatesToMachine(c *gc.C) {
   158  	// Check that assigning a principal unit assigns its subordinates too.
   159  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	// Units need to be assigned to a machine before the subordinates
   162  	// are created in order for the subordinate to get the machine ID.
   163  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	err = unit.AssignToMachine(machine)
   166  	c.Assert(err, jc.ErrorIsNil)
   167  
   168  	subUnit := s.addSubordinate(c, unit)
   169  
   170  	// None of the direct unit assign methods work on subordinates.
   171  	err = subUnit.AssignToMachine(machine)
   172  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "logging/0" to machine 0: unit is a subordinate`)
   173  	_, err = subUnit.AssignToCleanMachine()
   174  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "logging/0" to clean machine: unit is a subordinate`)
   175  	_, err = subUnit.AssignToCleanEmptyMachine()
   176  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "logging/0" to clean, empty machine: unit is a subordinate`)
   177  	err = subUnit.AssignToNewMachine()
   178  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "logging/0" to new machine: unit is a subordinate`)
   179  
   180  	// Subordinates know the machine they're indirectly assigned to.
   181  	id, err := subUnit.AssignedMachineId()
   182  	c.Assert(err, jc.ErrorIsNil)
   183  	c.Check(id, gc.Equals, machine.Id())
   184  }
   185  
   186  func (s *AssignSuite) TestDirectAssignIgnoresConstraints(c *gc.C) {
   187  	// Set up constraints.
   188  	scons := constraints.MustParse("mem=2G cpu-power=400")
   189  	err := s.wordpress.SetConstraints(scons)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	econs := constraints.MustParse("mem=4G cores=2")
   192  	err = s.State.SetModelConstraints(econs)
   193  	c.Assert(err, jc.ErrorIsNil)
   194  
   195  	// Machine will take model constraints on creation.
   196  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  
   199  	// Unit will take combined application/model constraints on creation.
   200  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   201  	c.Assert(err, jc.ErrorIsNil)
   202  
   203  	// Machine keeps its original constraints on direct assignment.
   204  	err = unit.AssignToMachine(machine)
   205  	c.Assert(err, jc.ErrorIsNil)
   206  	mcons, err := machine.Constraints()
   207  	c.Assert(err, jc.ErrorIsNil)
   208  	c.Assert(mcons, gc.DeepEquals, econs)
   209  }
   210  
   211  func (s *AssignSuite) TestAssignBadSeries(c *gc.C) {
   212  	machine, err := s.State.AddMachine(state.UbuntuBase("22.10"), state.JobHostUnits)
   213  	c.Assert(err, jc.ErrorIsNil)
   214  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   215  	c.Assert(err, jc.ErrorIsNil)
   216  	err = unit.AssignToMachine(machine)
   217  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/0" to machine 0: base does not match.*`)
   218  }
   219  
   220  func (s *AssignSuite) TestAssignMachineWhenDying(c *gc.C) {
   221  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  
   224  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   225  	c.Assert(err, jc.ErrorIsNil)
   226  	subUnit := s.addSubordinate(c, unit)
   227  	assignTest := func() error {
   228  		err := unit.AssignToMachine(machine)
   229  		c.Assert(unit.UnassignFromMachine(), gc.IsNil)
   230  		if subUnit != nil {
   231  			err := subUnit.EnsureDead()
   232  			c.Assert(err, jc.ErrorIsNil)
   233  			err = subUnit.Remove()
   234  			c.Assert(err, jc.ErrorIsNil)
   235  			subUnit = nil
   236  		}
   237  		return err
   238  	}
   239  	expect := ".*: unit is not found or not alive"
   240  	testWhenDying(c, unit, expect, expect, assignTest)
   241  
   242  	expect = ".*: machine is not found or not alive"
   243  	unit, err = s.wordpress.AddUnit(state.AddUnitParams{})
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	testWhenDying(c, machine, expect, expect, assignTest)
   246  }
   247  
   248  func (s *AssignSuite) TestAssignMachineDifferentSeries(c *gc.C) {
   249  	machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   252  	c.Assert(err, jc.ErrorIsNil)
   253  	err = unit.AssignToMachine(machine)
   254  	c.Assert(err, gc.ErrorMatches,
   255  		`cannot assign unit "wordpress/0" to machine 0: base does not match.*`)
   256  }
   257  
   258  func (s *AssignSuite) TestPrincipals(c *gc.C) {
   259  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   260  	c.Assert(err, jc.ErrorIsNil)
   261  	principals := machine.Principals()
   262  	c.Assert(principals, jc.DeepEquals, []string{})
   263  
   264  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   265  	c.Assert(err, jc.ErrorIsNil)
   266  	err = unit.AssignToMachine(machine)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  
   269  	err = machine.Refresh()
   270  	c.Assert(err, jc.ErrorIsNil)
   271  	principals = machine.Principals()
   272  	c.Assert(principals, jc.DeepEquals, []string{"wordpress/0"})
   273  }
   274  
   275  func (s *AssignSuite) TestAssignMachinePrincipalsChange(c *gc.C) {
   276  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   277  	c.Assert(err, jc.ErrorIsNil)
   278  	err = machine.SetProvisioned("inst-id", "", "fake_nonce", nil)
   279  	c.Assert(err, jc.ErrorIsNil)
   280  
   281  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   282  	c.Assert(err, jc.ErrorIsNil)
   283  	err = unit.AssignToMachine(machine)
   284  	c.Assert(err, jc.ErrorIsNil)
   285  	unit, err = s.wordpress.AddUnit(state.AddUnitParams{})
   286  	c.Assert(err, jc.ErrorIsNil)
   287  	err = unit.AssignToMachine(machine)
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	subUnit := s.addSubordinate(c, unit)
   290  
   291  	checkPrincipals := func() []string {
   292  		err := machine.Refresh()
   293  		c.Assert(err, jc.ErrorIsNil)
   294  		return machine.Principals()
   295  	}
   296  	c.Assert(checkPrincipals(), gc.DeepEquals, []string{"wordpress/0", "wordpress/1"})
   297  
   298  	err = subUnit.EnsureDead()
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	err = subUnit.Remove()
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	err = unit.EnsureDead()
   303  	c.Assert(err, jc.ErrorIsNil)
   304  	err = unit.Remove()
   305  	c.Assert(err, jc.ErrorIsNil)
   306  	c.Assert(checkPrincipals(), gc.DeepEquals, []string{"wordpress/0"})
   307  }
   308  
   309  func (s *AssignSuite) assertAssignedUnit(c *gc.C, unit *state.Unit) string {
   310  	// Check the machine on the unit is set.
   311  	machineId, err := unit.AssignedMachineId()
   312  	c.Assert(err, jc.ErrorIsNil)
   313  	// Check that the principal is set on the machine.
   314  	machine, err := s.State.Machine(machineId)
   315  	c.Assert(err, jc.ErrorIsNil)
   316  	machineUnits, err := machine.Units()
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	c.Assert(machineUnits, gc.HasLen, 1)
   319  	// Make sure it is the right unit.
   320  	c.Assert(machineUnits[0].Name(), gc.Equals, unit.Name())
   321  	return machineId
   322  }
   323  
   324  func (s *AssignSuite) TestAssignUnitToNewMachine(c *gc.C) {
   325  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   326  	c.Assert(err, jc.ErrorIsNil)
   327  
   328  	err = unit.AssignToNewMachine()
   329  	c.Assert(err, jc.ErrorIsNil)
   330  	s.assertAssignedUnit(c, unit)
   331  }
   332  
   333  func (s *AssignSuite) assertAssignUnitToNewMachineContainerConstraint(c *gc.C) {
   334  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   335  	c.Assert(err, jc.ErrorIsNil)
   336  	err = unit.AssignToNewMachine()
   337  	c.Assert(err, jc.ErrorIsNil)
   338  	machineId := s.assertAssignedUnit(c, unit)
   339  	c.Assert(container.ParentId(machineId), gc.Not(gc.Equals), "")
   340  	c.Assert(container.ContainerTypeFromId(machineId), gc.Equals, instance.LXD)
   341  }
   342  
   343  func (s *AssignSuite) TestAssignUnitToNewMachineContainerConstraint(c *gc.C) {
   344  	// Set up application constraints.
   345  	scons := constraints.MustParse("container=lxd")
   346  	err := s.wordpress.SetConstraints(scons)
   347  	c.Assert(err, jc.ErrorIsNil)
   348  	s.assertAssignUnitToNewMachineContainerConstraint(c)
   349  }
   350  
   351  func (s *AssignSuite) TestAssignUnitToNewMachineDefaultContainerConstraint(c *gc.C) {
   352  	// Set up model constraints.
   353  	econs := constraints.MustParse("container=lxd")
   354  	err := s.State.SetModelConstraints(econs)
   355  	c.Assert(err, jc.ErrorIsNil)
   356  	s.assertAssignUnitToNewMachineContainerConstraint(c)
   357  }
   358  
   359  func (s *AssignSuite) TestAssignToNewMachineMakesDirty(c *gc.C) {
   360  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   361  	c.Assert(err, jc.ErrorIsNil)
   362  
   363  	err = unit.AssignToNewMachine()
   364  	c.Assert(err, jc.ErrorIsNil)
   365  	mid, err := unit.AssignedMachineId()
   366  	c.Assert(err, jc.ErrorIsNil)
   367  	machine, err := s.State.Machine(mid)
   368  	c.Assert(err, jc.ErrorIsNil)
   369  	c.Assert(machine.Clean(), jc.IsFalse)
   370  }
   371  
   372  func (s *AssignSuite) TestAssignUnitToNewMachineSetsConstraints(c *gc.C) {
   373  	// Set up constraints.
   374  	scons := constraints.MustParse("mem=2G cpu-power=400")
   375  	err := s.wordpress.SetConstraints(scons)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	econs := constraints.MustParse("mem=4G cores=2")
   378  	err = s.State.SetModelConstraints(econs)
   379  	c.Assert(err, jc.ErrorIsNil)
   380  
   381  	// Unit will take combined application/model constraints on creation.
   382  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   383  	c.Assert(err, jc.ErrorIsNil)
   384  
   385  	// Change application/model constraints before assigning, to verify this.
   386  	scons = constraints.MustParse("mem=6G cpu-power=800")
   387  	err = s.wordpress.SetConstraints(scons)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	econs = constraints.MustParse("cores=4")
   390  	err = s.State.SetModelConstraints(econs)
   391  	c.Assert(err, jc.ErrorIsNil)
   392  
   393  	// The new machine takes the original combined unit constraints.
   394  	err = unit.AssignToNewMachine()
   395  	c.Assert(err, jc.ErrorIsNil)
   396  	err = unit.Refresh()
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	mid, err := unit.AssignedMachineId()
   399  	c.Assert(err, jc.ErrorIsNil)
   400  	machine, err := s.State.Machine(mid)
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	mcons, err := machine.Constraints()
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	expect := constraints.MustParse("arch=amd64 mem=2G cores=2 cpu-power=400")
   405  	c.Assert(mcons, gc.DeepEquals, expect)
   406  }
   407  
   408  func (s *AssignSuite) TestAssignUnitToNewMachineCleanAvailable(c *gc.C) {
   409  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   410  	c.Assert(err, jc.ErrorIsNil)
   411  
   412  	// Add a clean machine.
   413  	clean, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   414  	c.Assert(err, jc.ErrorIsNil)
   415  
   416  	err = unit.AssignToNewMachine()
   417  	c.Assert(err, jc.ErrorIsNil)
   418  	// Check the machine on the unit is set.
   419  	machineId, err := unit.AssignedMachineId()
   420  	c.Assert(err, jc.ErrorIsNil)
   421  	// Check that the machine isn't our clean one.
   422  	machine, err := s.State.Machine(machineId)
   423  	c.Assert(err, jc.ErrorIsNil)
   424  	c.Assert(machine.Id(), gc.Not(gc.Equals), clean.Id())
   425  }
   426  
   427  func (s *AssignSuite) TestAssignUnitToNewMachineAlreadyAssigned(c *gc.C) {
   428  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   429  	c.Assert(err, jc.ErrorIsNil)
   430  	// Make the unit assigned
   431  	err = unit.AssignToNewMachine()
   432  	c.Assert(err, jc.ErrorIsNil)
   433  	// Try to assign it again
   434  	err = unit.AssignToNewMachine()
   435  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/0" to new machine: unit is already assigned to a machine`)
   436  }
   437  
   438  func (s *AssignSuite) TestAssignUnitToNewMachineUnitNotAlive(c *gc.C) {
   439  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   440  	c.Assert(err, jc.ErrorIsNil)
   441  	subUnit := s.addSubordinate(c, unit)
   442  
   443  	// Try to assign a dying unit...
   444  	err = unit.Destroy()
   445  	c.Assert(err, jc.ErrorIsNil)
   446  	err = unit.AssignToNewMachine()
   447  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/0" to new machine: unit is not found or not alive`)
   448  
   449  	// ...and a dead one.
   450  	err = subUnit.EnsureDead()
   451  	c.Assert(err, jc.ErrorIsNil)
   452  	err = subUnit.Remove()
   453  	c.Assert(err, jc.ErrorIsNil)
   454  	err = unit.EnsureDead()
   455  	c.Assert(err, jc.ErrorIsNil)
   456  	err = unit.AssignToNewMachine()
   457  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/0" to new machine: unit is not found or not alive`)
   458  }
   459  
   460  func (s *AssignSuite) TestAssignUnitToNewMachineUnitRemoved(c *gc.C) {
   461  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   462  	c.Assert(err, jc.ErrorIsNil)
   463  	err = unit.Destroy()
   464  	c.Assert(err, jc.ErrorIsNil)
   465  	err = unit.AssignToNewMachine()
   466  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/0" to new machine: unit not found`)
   467  }
   468  
   469  func (s *AssignSuite) TestAssignUnitToNewMachineBecomesDirty(c *gc.C) {
   470  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
   471  	c.Assert(err, jc.ErrorIsNil)
   472  
   473  	// Set up constraints to specify we want to install into a container.
   474  	econs := constraints.MustParse("container=lxd")
   475  	err = s.State.SetModelConstraints(econs)
   476  	c.Assert(err, jc.ErrorIsNil)
   477  
   478  	// Create some units and a clean machine.
   479  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   480  	c.Assert(err, jc.ErrorIsNil)
   481  	anotherUnit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   482  	c.Assert(err, jc.ErrorIsNil)
   483  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   484  	c.Assert(err, jc.ErrorIsNil)
   485  
   486  	makeDirty := jujutxn.TestHook{
   487  		Before: func() { c.Assert(unit.AssignToMachine(machine), gc.IsNil) },
   488  	}
   489  	defer state.SetTestHooks(c, s.State, makeDirty).Check()
   490  
   491  	err = anotherUnit.AssignToNewMachineOrContainer()
   492  	c.Assert(err, jc.ErrorIsNil)
   493  	mid, err := unit.AssignedMachineId()
   494  	c.Assert(err, jc.ErrorIsNil)
   495  	c.Assert(mid, gc.Equals, "1")
   496  
   497  	mid, err = anotherUnit.AssignedMachineId()
   498  	c.Assert(err, jc.ErrorIsNil)
   499  	c.Assert(mid, gc.Equals, "2/lxd/0")
   500  }
   501  
   502  func (s *AssignSuite) TestAssignUnitToNewMachineBecomesHost(c *gc.C) {
   503  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
   504  	c.Assert(err, jc.ErrorIsNil)
   505  
   506  	// Set up constraints to specify we want to install into a container.
   507  	econs := constraints.MustParse("container=lxd")
   508  	err = s.State.SetModelConstraints(econs)
   509  	c.Assert(err, jc.ErrorIsNil)
   510  
   511  	// Create a unit and a clean machine.
   512  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   513  	c.Assert(err, jc.ErrorIsNil)
   514  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   515  	c.Assert(err, jc.ErrorIsNil)
   516  
   517  	addContainer := jujutxn.TestHook{
   518  		Before: func() {
   519  			_, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
   520  				Base: state.UbuntuBase("12.10"),
   521  				Jobs: []state.MachineJob{state.JobHostUnits},
   522  			}, machine.Id(), instance.LXD)
   523  			c.Assert(err, jc.ErrorIsNil)
   524  		},
   525  	}
   526  	defer state.SetTestHooks(c, s.State, addContainer).Check()
   527  
   528  	err = unit.AssignToNewMachineOrContainer()
   529  	c.Assert(err, jc.ErrorIsNil)
   530  
   531  	mid, err := unit.AssignedMachineId()
   532  	c.Assert(err, jc.ErrorIsNil)
   533  	c.Assert(mid, gc.Equals, "2/lxd/0")
   534  }
   535  
   536  func (s *AssignSuite) TestAssignUnitBadPolicy(c *gc.C) {
   537  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   538  	c.Assert(err, jc.ErrorIsNil)
   539  	// Check nonsensical policy
   540  	err = s.State.AssignUnit(unit, state.AssignmentPolicy("random"))
   541  	c.Assert(err, gc.ErrorMatches, `.*unknown unit assignment policy: "random"`)
   542  	_, err = unit.AssignedMachineId()
   543  	c.Assert(err, gc.NotNil)
   544  	assertMachineCount(c, s.State, 0)
   545  }
   546  
   547  func (s *AssignSuite) TestAssignUnitLocalPolicy(c *gc.C) {
   548  	m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel, state.JobHostUnits) // bootstrap machine
   549  	c.Assert(err, jc.ErrorIsNil)
   550  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   551  	c.Assert(err, jc.ErrorIsNil)
   552  
   553  	for i := 0; i < 2; i++ {
   554  		err = s.State.AssignUnit(unit, state.AssignLocal)
   555  		c.Assert(err, jc.ErrorIsNil)
   556  		mid, err := unit.AssignedMachineId()
   557  		c.Assert(err, jc.ErrorIsNil)
   558  		c.Assert(mid, gc.Equals, m.Id())
   559  		assertMachineCount(c, s.State, 1)
   560  	}
   561  }
   562  
   563  func (s *AssignSuite) assertAssignUnitNewPolicyNoContainer(c *gc.C) {
   564  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) // available machine
   565  	c.Assert(err, jc.ErrorIsNil)
   566  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   567  	c.Assert(err, jc.ErrorIsNil)
   568  
   569  	err = s.State.AssignUnit(unit, state.AssignNew)
   570  	c.Assert(err, jc.ErrorIsNil)
   571  	assertMachineCount(c, s.State, 2)
   572  	id, err := unit.AssignedMachineId()
   573  	c.Assert(err, jc.ErrorIsNil)
   574  	c.Assert(container.ParentId(id), gc.Equals, "")
   575  }
   576  
   577  func (s *AssignSuite) TestAssignUnitNewPolicy(c *gc.C) {
   578  	s.assertAssignUnitNewPolicyNoContainer(c)
   579  }
   580  
   581  func (s *AssignSuite) TestAssignUnitNewPolicyWithContainerConstraintIgnoresNone(c *gc.C) {
   582  	scons := constraints.MustParse("container=none")
   583  	err := s.wordpress.SetConstraints(scons)
   584  	c.Assert(err, jc.ErrorIsNil)
   585  	s.assertAssignUnitNewPolicyNoContainer(c)
   586  }
   587  
   588  func (s *AssignSuite) assertAssignUnitNewPolicyWithContainerConstraint(c *gc.C) {
   589  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   590  	c.Assert(err, jc.ErrorIsNil)
   591  	err = s.State.AssignUnit(unit, state.AssignNew)
   592  	c.Assert(err, jc.ErrorIsNil)
   593  	assertMachineCount(c, s.State, 3)
   594  	id, err := unit.AssignedMachineId()
   595  	c.Assert(err, jc.ErrorIsNil)
   596  	c.Assert(id, gc.Equals, "1/lxd/0")
   597  }
   598  
   599  func (s *AssignSuite) TestAssignUnitNewPolicyWithContainerConstraint(c *gc.C) {
   600  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   601  	c.Assert(err, jc.ErrorIsNil)
   602  	// Set up application constraints.
   603  	scons := constraints.MustParse("container=lxd")
   604  	err = s.wordpress.SetConstraints(scons)
   605  	c.Assert(err, jc.ErrorIsNil)
   606  	s.assertAssignUnitNewPolicyWithContainerConstraint(c)
   607  }
   608  
   609  func (s *AssignSuite) TestAssignUnitNewPolicyWithDefaultContainerConstraint(c *gc.C) {
   610  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   611  	c.Assert(err, jc.ErrorIsNil)
   612  	// Set up model constraints.
   613  	econs := constraints.MustParse("container=lxd")
   614  	err = s.State.SetModelConstraints(econs)
   615  	c.Assert(err, jc.ErrorIsNil)
   616  	s.assertAssignUnitNewPolicyWithContainerConstraint(c)
   617  }
   618  
   619  func (s *AssignSuite) TestAssignUnitWithSubordinate(c *gc.C) {
   620  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
   621  	c.Assert(err, jc.ErrorIsNil)
   622  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   623  	c.Assert(err, jc.ErrorIsNil)
   624  
   625  	// Check cannot assign subordinates to machines
   626  	subUnit := s.addSubordinate(c, unit)
   627  	for _, policy := range []state.AssignmentPolicy{
   628  		state.AssignLocal, state.AssignNew, state.AssignClean, state.AssignCleanEmpty,
   629  	} {
   630  		err = s.State.AssignUnit(subUnit, policy)
   631  		c.Assert(err, gc.ErrorMatches, `subordinate unit "logging/0" cannot be assigned directly to a machine`)
   632  	}
   633  }
   634  
   635  func assertMachineCount(c *gc.C, st *state.State, expect int) {
   636  	ms, err := st.AllMachines()
   637  	c.Assert(err, jc.ErrorIsNil)
   638  	c.Assert(ms, gc.HasLen, expect, gc.Commentf("%v", ms))
   639  }
   640  
   641  // assignCleanSuite has tests for assigning units to 1. clean, and 2. clean&empty machines.
   642  type assignCleanSuite struct {
   643  	ConnSuite
   644  	policy    state.AssignmentPolicy
   645  	wordpress *state.Application
   646  }
   647  
   648  var _ = gc.Suite(&assignCleanSuite{ConnSuite{}, state.AssignCleanEmpty, nil})
   649  var _ = gc.Suite(&assignCleanSuite{ConnSuite{}, state.AssignClean, nil})
   650  
   651  func (s *assignCleanSuite) SetUpTest(c *gc.C) {
   652  	c.Logf("assignment policy for this test: %q", s.policy)
   653  	s.ConnSuite.SetUpTest(c)
   654  	wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   655  	s.wordpress = wordpress
   656  	pm := poolmanager.New(state.NewStateSettings(s.State), provider.CommonStorageProviders())
   657  	_, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{})
   658  	c.Assert(err, jc.ErrorIsNil)
   659  }
   660  
   661  func (s *assignCleanSuite) errorMessage(msg string) string {
   662  	context := "clean"
   663  	if s.policy == state.AssignCleanEmpty {
   664  		context += ", empty"
   665  	}
   666  	return fmt.Sprintf(msg, context)
   667  }
   668  
   669  func (s *assignCleanSuite) assignUnit(unit *state.Unit) (*state.Machine, error) {
   670  	if s.policy == state.AssignCleanEmpty {
   671  		return unit.AssignToCleanEmptyMachine()
   672  	}
   673  	return unit.AssignToCleanMachine()
   674  }
   675  
   676  func (s *assignCleanSuite) assertMachineEmpty(c *gc.C, machine *state.Machine) {
   677  	containers, err := machine.Containers()
   678  	c.Assert(err, jc.ErrorIsNil)
   679  	c.Assert(len(containers), gc.Equals, 0)
   680  }
   681  
   682  func (s *assignCleanSuite) assertMachineNotEmpty(c *gc.C, machine *state.Machine) {
   683  	containers, err := machine.Containers()
   684  	c.Assert(err, jc.ErrorIsNil)
   685  	c.Assert(len(containers), gc.Not(gc.Equals), 0)
   686  }
   687  
   688  // setupMachines creates a combination of machines with which to test.
   689  func (s *assignCleanSuite) setupMachines(c *gc.C) (hostMachine *state.Machine, container *state.Machine, cleanEmptyMachine *state.Machine) {
   690  	amdArch := "amd64"
   691  	hwChar := &instance.HardwareCharacteristics{
   692  		Arch: &amdArch,
   693  	}
   694  
   695  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
   696  	c.Assert(err, jc.ErrorIsNil)
   697  
   698  	// Add some units to another application and allocate them to machines
   699  	app1 := s.AddTestingApplication(c, "mysql", s.AddTestingCharm(c, "mysql"))
   700  	units := make([]*state.Unit, 3)
   701  	for i := range units {
   702  		u, err := app1.AddUnit(state.AddUnitParams{})
   703  		c.Assert(err, jc.ErrorIsNil)
   704  		m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   705  		c.Assert(err, jc.ErrorIsNil)
   706  		err = u.AssignToMachine(m)
   707  		c.Assert(err, jc.ErrorIsNil)
   708  		units[i] = u
   709  	}
   710  
   711  	// Create a new, clean machine but add containers so it is not empty.
   712  	hostMachine, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   713  	c.Assert(err, jc.ErrorIsNil)
   714  
   715  	instId := instance.Id("i-host-machine")
   716  	err = hostMachine.SetProvisioned(instId, "", "fake-nonce", hwChar)
   717  	c.Assert(err, jc.ErrorIsNil)
   718  
   719  	container, err = s.State.AddMachineInsideMachine(state.MachineTemplate{
   720  		Base: state.UbuntuBase("12.10"),
   721  		Jobs: []state.MachineJob{state.JobHostUnits},
   722  	}, hostMachine.Id(), instance.LXD)
   723  	c.Assert(err, jc.ErrorIsNil)
   724  	c.Assert(hostMachine.Clean(), jc.IsTrue)
   725  	s.assertMachineNotEmpty(c, hostMachine)
   726  
   727  	instId = instance.Id("i-container")
   728  	err = container.SetProvisioned(instId, "", "fake-nonce", hwChar)
   729  	c.Assert(err, jc.ErrorIsNil)
   730  
   731  	// Create a new, clean, empty machine.
   732  	cleanEmptyMachine, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   733  	c.Assert(err, jc.ErrorIsNil)
   734  	c.Assert(cleanEmptyMachine.Clean(), jc.IsTrue)
   735  	s.assertMachineEmpty(c, cleanEmptyMachine)
   736  
   737  	instId = instance.Id("i-clean-empty-machine")
   738  	err = cleanEmptyMachine.SetProvisioned(instId, "", "fake-nonce", hwChar)
   739  	c.Assert(err, jc.ErrorIsNil)
   740  
   741  	return hostMachine, container, cleanEmptyMachine
   742  }
   743  
   744  func (s *assignCleanSuite) assertAssignUnit(c *gc.C, expectedMachine *state.Machine) {
   745  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   746  	c.Assert(err, jc.ErrorIsNil)
   747  	reusedMachine, err := s.assignUnit(unit)
   748  	c.Assert(err, jc.ErrorIsNil)
   749  	c.Assert(reusedMachine.Id(), gc.Equals, expectedMachine.Id())
   750  	c.Assert(reusedMachine.Clean(), jc.IsFalse)
   751  }
   752  
   753  func (s *assignCleanSuite) TestAssignUnit(c *gc.C) {
   754  	hostMachine, container, cleanEmptyMachine := s.setupMachines(c)
   755  	// Check that AssignToClean(Empty)Machine finds a newly created, clean (maybe empty) machine.
   756  	if s.policy == state.AssignCleanEmpty {
   757  		// The first clean, empty machine is the container.
   758  		s.assertAssignUnit(c, container)
   759  		// The next deployment will use the remaining clean, empty machine.
   760  		s.assertAssignUnit(c, cleanEmptyMachine)
   761  	} else {
   762  		s.assertAssignUnit(c, hostMachine)
   763  	}
   764  }
   765  
   766  func (s *assignCleanSuite) TestAssignUnitTwiceFails(c *gc.C) {
   767  	s.setupMachines(c)
   768  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   769  	c.Assert(err, jc.ErrorIsNil)
   770  	// Assign the first time.
   771  	_, err = s.assignUnit(unit)
   772  	c.Assert(err, jc.ErrorIsNil)
   773  
   774  	// Check that it fails when called again, even when there's an available machine
   775  	m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   776  	c.Assert(err, jc.ErrorIsNil)
   777  	_, err = s.assignUnit(unit)
   778  	c.Assert(err, gc.ErrorMatches, s.errorMessage(`cannot assign unit "wordpress/0" to %s machine: unit is already assigned to a machine`))
   779  	c.Assert(m.EnsureDead(), gc.IsNil)
   780  	c.Assert(m.Remove(), gc.IsNil)
   781  }
   782  
   783  const eligibleMachinesInUse = ".*: all eligible machines in use"
   784  
   785  func (s *assignCleanSuite) TestAssignToMachineNoneAvailable(c *gc.C) {
   786  	// Try to assign a unit to a clean (maybe empty) machine and check that we can't.
   787  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   788  	c.Assert(err, jc.ErrorIsNil)
   789  
   790  	m, err := s.assignUnit(unit)
   791  	c.Assert(m, gc.IsNil)
   792  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   793  
   794  	// Add a state management machine which can host units and check it is not chosen.
   795  	// Note that this must the first machine added, as AddMachine can only
   796  	// be used to add state-manager machines for the bootstrap machine.
   797  	_, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel, state.JobHostUnits)
   798  	c.Assert(err, jc.ErrorIsNil)
   799  	m, err = s.assignUnit(unit)
   800  	c.Assert(m, gc.IsNil)
   801  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   802  
   803  	// Add a dying machine and check that it is not chosen.
   804  	m, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
   805  	c.Assert(err, jc.ErrorIsNil)
   806  	err = m.Destroy()
   807  	c.Assert(err, jc.ErrorIsNil)
   808  	m, err = s.assignUnit(unit)
   809  	c.Assert(m, gc.IsNil)
   810  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   811  
   812  	node, err := s.State.ControllerNode("0")
   813  	c.Assert(err, jc.ErrorIsNil)
   814  	err = node.SetHasVote(true)
   815  	c.Assert(err, jc.ErrorIsNil)
   816  
   817  	// Add two controller machines and check they are not chosen.
   818  	changes, err := s.State.EnableHA(3, constraints.Value{}, state.UbuntuBase("12.10"), nil)
   819  	c.Assert(err, jc.ErrorIsNil)
   820  	c.Assert(changes.Added, gc.HasLen, 2)
   821  	c.Assert(changes.Maintained, gc.HasLen, 1)
   822  
   823  	m, err = s.assignUnit(unit)
   824  	c.Assert(m, gc.IsNil)
   825  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   826  
   827  	// Add a machine with the wrong series and check it is not chosen.
   828  	m, err = s.State.AddMachine(state.UbuntuBase("22.10"), state.JobHostUnits)
   829  	c.Assert(err, jc.ErrorIsNil)
   830  	m, err = s.assignUnit(unit)
   831  	c.Assert(m, gc.IsNil)
   832  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   833  }
   834  
   835  var assignUsingConstraintsTests = []struct {
   836  	unitConstraints         string
   837  	hardwareCharacteristics string
   838  	assignOk                bool
   839  }{
   840  	{
   841  		// 0
   842  		unitConstraints:         "",
   843  		hardwareCharacteristics: "arch=amd64",
   844  		assignOk:                true,
   845  	}, {
   846  		// 1
   847  		unitConstraints:         "arch=amd64",
   848  		hardwareCharacteristics: "none",
   849  		assignOk:                false,
   850  	}, {
   851  		// 2
   852  		unitConstraints:         "arch=amd64",
   853  		hardwareCharacteristics: "cores=1",
   854  		assignOk:                false,
   855  	}, {
   856  		// 3
   857  		unitConstraints:         "",
   858  		hardwareCharacteristics: "arch=s390x",
   859  		assignOk:                false,
   860  	}, {
   861  		// 4
   862  		unitConstraints:         "mem=4G",
   863  		hardwareCharacteristics: "none",
   864  		assignOk:                false,
   865  	}, {
   866  		// 5
   867  		unitConstraints:         "mem=4G",
   868  		hardwareCharacteristics: "cores=1",
   869  		assignOk:                false,
   870  	}, {
   871  		// 6
   872  		unitConstraints:         "arch=amd64 mem=4G",
   873  		hardwareCharacteristics: "arch=amd64 mem=4G",
   874  		assignOk:                true,
   875  	}, {
   876  		// 7
   877  		unitConstraints:         "mem=4G",
   878  		hardwareCharacteristics: "arch=amd64 mem=4G",
   879  		assignOk:                true,
   880  	}, {
   881  		// 8
   882  		unitConstraints:         "arch=amd64 mem=4G",
   883  		hardwareCharacteristics: "arch=amd64 mem=2G",
   884  		assignOk:                false,
   885  	}, {
   886  		// 9
   887  		unitConstraints:         "mem=4G",
   888  		hardwareCharacteristics: "mem=2G",
   889  		assignOk:                false,
   890  	}, {
   891  		// 10
   892  		unitConstraints:         "arch=amd64 cores=2",
   893  		hardwareCharacteristics: "arch=amd64 cores=2",
   894  		assignOk:                true,
   895  	}, {
   896  		// 11
   897  		unitConstraints:         "cores=2",
   898  		hardwareCharacteristics: "arch=amd64 cores=2",
   899  		assignOk:                true,
   900  	}, {
   901  		// 12
   902  		unitConstraints:         "arch=amd64 cores=2",
   903  		hardwareCharacteristics: "arch=amd64 cores=1",
   904  		assignOk:                false,
   905  	}, {
   906  		// 13
   907  		unitConstraints:         "cores=2",
   908  		hardwareCharacteristics: "cores=1",
   909  		assignOk:                false,
   910  	}, {
   911  		// 14
   912  		unitConstraints:         "arch=amd64 cores=2",
   913  		hardwareCharacteristics: "arch=amd64 mem=4G",
   914  		assignOk:                false,
   915  	}, {
   916  		// 15
   917  		unitConstraints:         "cores=2",
   918  		hardwareCharacteristics: "mem=4G",
   919  		assignOk:                false,
   920  	}, {
   921  		// 16
   922  		unitConstraints:         "arch=amd64 cpu-power=50",
   923  		hardwareCharacteristics: "arch=amd64 cpu-power=50",
   924  		assignOk:                true,
   925  	}, {
   926  		// 17
   927  		unitConstraints:         "cpu-power=50",
   928  		hardwareCharacteristics: "arch=amd64 cpu-power=50",
   929  		assignOk:                true,
   930  	}, {
   931  		// 18
   932  		unitConstraints:         "arch=amd64 cpu-power=100",
   933  		hardwareCharacteristics: "arch=amd64 cpu-power=50",
   934  		assignOk:                false,
   935  	}, {
   936  		// 19
   937  		unitConstraints:         "cpu-power=100",
   938  		hardwareCharacteristics: "cpu-power=50",
   939  		assignOk:                false,
   940  	}, {
   941  		// 20
   942  		unitConstraints:         "arch=amd64 cpu-power=50",
   943  		hardwareCharacteristics: "arch=amd64 mem=4G",
   944  		assignOk:                false,
   945  	}, {
   946  		// 21
   947  		unitConstraints:         "cpu-power=50",
   948  		hardwareCharacteristics: "mem=4G",
   949  		assignOk:                false,
   950  	}, {
   951  		// 22
   952  		unitConstraints:         "arch=amd64 root-disk=8192",
   953  		hardwareCharacteristics: "arch=amd64 cpu-power=50",
   954  		assignOk:                false,
   955  	}, {
   956  		// 23
   957  		unitConstraints:         "root-disk=8192",
   958  		hardwareCharacteristics: "cpu-power=50",
   959  		assignOk:                false,
   960  	}, {
   961  		// 24
   962  		unitConstraints:         "arch=amd64 root-disk=8192",
   963  		hardwareCharacteristics: "arch=amd64 root-disk=4096",
   964  		assignOk:                false,
   965  	}, {
   966  		// 25
   967  		unitConstraints:         "root-disk=8192",
   968  		hardwareCharacteristics: "root-disk=4096",
   969  		assignOk:                false,
   970  	}, {
   971  		// 26
   972  		unitConstraints:         "arch=amd64 root-disk=8192",
   973  		hardwareCharacteristics: "arch=amd64 root-disk=8192",
   974  		assignOk:                true,
   975  	}, {
   976  		// 27
   977  		unitConstraints:         "root-disk=8192",
   978  		hardwareCharacteristics: "arch=amd64 root-disk=8192",
   979  		assignOk:                true,
   980  	}, {
   981  		// 28
   982  		unitConstraints:         "root-disk-source=place1",
   983  		hardwareCharacteristics: "root-disk-source=place2",
   984  		assignOk:                false,
   985  	}, {
   986  		// 29
   987  		unitConstraints:         "arch=amd64 root-disk-source=place1",
   988  		hardwareCharacteristics: "arch=amd64 root-disk-source=place1",
   989  		assignOk:                true,
   990  	}, {
   991  		// 30
   992  		unitConstraints:         "arch=amd64 mem=4G cores=2 root-disk=8192",
   993  		hardwareCharacteristics: "arch=amd64 mem=8G cores=2 root-disk=8192 root-disk-source=donk cpu-power=50",
   994  		assignOk:                true,
   995  	}, {
   996  		// 31
   997  		unitConstraints:         "arch=amd64 mem=4G cores=2 root-disk=8192 root-disk-source=donk",
   998  		hardwareCharacteristics: "arch=amd64 mem=8G cores=1 root-disk=4096 root-disk-source=donk cpu-power=50",
   999  		assignOk:                false,
  1000  	},
  1001  }
  1002  
  1003  func (s *assignCleanSuite) TestAssignUsingConstraintsToMachine(c *gc.C) {
  1004  	for i, t := range assignUsingConstraintsTests {
  1005  		c.Logf("test %d", i)
  1006  		cons := constraints.MustParse(t.unitConstraints)
  1007  		err := s.State.SetModelConstraints(cons)
  1008  		c.Assert(err, jc.ErrorIsNil)
  1009  
  1010  		unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1011  		c.Assert(err, jc.ErrorIsNil)
  1012  
  1013  		m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1014  		c.Assert(err, jc.ErrorIsNil)
  1015  		if t.hardwareCharacteristics != "none" {
  1016  			hc := instance.MustParseHardware(t.hardwareCharacteristics)
  1017  			err = m.SetProvisioned("inst-id", "", "fake_nonce", &hc)
  1018  			c.Assert(err, jc.ErrorIsNil)
  1019  		}
  1020  
  1021  		um, err := s.assignUnit(unit)
  1022  		if t.assignOk {
  1023  			c.Assert(err, jc.ErrorIsNil)
  1024  			c.Assert(um.Id(), gc.Equals, m.Id())
  1025  		} else {
  1026  			c.Assert(um, gc.IsNil)
  1027  			c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
  1028  			// Destroy the machine so it can't be used for the next test.
  1029  			err = m.Destroy()
  1030  			c.Assert(err, jc.ErrorIsNil)
  1031  		}
  1032  	}
  1033  }
  1034  
  1035  func (s *assignCleanSuite) TestAssignUnitWithRemovedApplication(c *gc.C) {
  1036  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
  1037  	c.Assert(err, jc.ErrorIsNil)
  1038  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1039  	c.Assert(err, jc.ErrorIsNil)
  1040  
  1041  	// Fail if application is removed.
  1042  	removeAllUnits(c, s.wordpress)
  1043  	err = s.wordpress.Destroy()
  1044  	c.Assert(err, jc.ErrorIsNil)
  1045  	_, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1046  	c.Assert(err, jc.ErrorIsNil)
  1047  	_, err = s.assignUnit(unit)
  1048  	c.Assert(err, gc.ErrorMatches, s.errorMessage(`cannot assign unit "wordpress/0" to %s machine.* not found`))
  1049  }
  1050  
  1051  func (s *assignCleanSuite) TestAssignUnitToMachineWithRemovedUnit(c *gc.C) {
  1052  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
  1053  	c.Assert(err, jc.ErrorIsNil)
  1054  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1055  	c.Assert(err, jc.ErrorIsNil)
  1056  	// Fail if unit is removed.
  1057  	err = unit.EnsureDead()
  1058  	c.Assert(err, jc.ErrorIsNil)
  1059  	err = unit.Remove()
  1060  	c.Assert(err, jc.ErrorIsNil)
  1061  	_, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1062  	c.Assert(err, jc.ErrorIsNil)
  1063  
  1064  	_, err = s.assignUnit(unit)
  1065  	c.Assert(err, gc.ErrorMatches, s.errorMessage(`cannot assign unit "wordpress/0" to %s machine.*: unit not found`))
  1066  }
  1067  
  1068  func (s *assignCleanSuite) TestAssignUnitToMachineWorksWithMachine0(c *gc.C) {
  1069  	amdArch := "amd64"
  1070  	hwChar := &instance.HardwareCharacteristics{
  1071  		Arch: &amdArch,
  1072  	}
  1073  
  1074  	m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1075  	c.Assert(err, jc.ErrorIsNil)
  1076  	c.Assert(m.Id(), gc.Equals, "0")
  1077  
  1078  	instId := instance.Id("i-host-machine")
  1079  	err = m.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1080  	c.Assert(err, jc.ErrorIsNil)
  1081  
  1082  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1083  	c.Assert(err, jc.ErrorIsNil)
  1084  	assignedTo, err := s.assignUnit(unit)
  1085  	c.Assert(err, jc.ErrorIsNil)
  1086  	c.Assert(assignedTo.Id(), gc.Equals, "0")
  1087  }
  1088  
  1089  func (s *assignCleanSuite) setupSingleStorage(c *gc.C, kind, pool string) (*state.Application, *state.Unit, names.StorageTag) {
  1090  	// There are test charms called "storage-block" and
  1091  	// "storage-filesystem" which are what you'd expect.
  1092  	ch := s.AddTestingCharm(c, "storage-"+kind)
  1093  	storage := map[string]state.StorageConstraints{
  1094  		"data": makeStorageCons(pool, 1024, 1),
  1095  	}
  1096  	application := s.AddTestingApplicationWithStorage(c, "storage-"+kind, ch, storage)
  1097  	unit, err := application.AddUnit(state.AddUnitParams{})
  1098  	c.Assert(err, jc.ErrorIsNil)
  1099  	storageTag := names.NewStorageTag("data/0")
  1100  	return application, unit, storageTag
  1101  }
  1102  
  1103  func (s *assignCleanSuite) TestAssignToMachine(c *gc.C) {
  1104  	_, unit, _ := s.setupSingleStorage(c, "filesystem", "loop-pool")
  1105  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1106  	c.Assert(err, jc.ErrorIsNil)
  1107  	err = unit.AssignToMachine(machine)
  1108  	c.Assert(err, jc.ErrorIsNil)
  1109  
  1110  	sb, err := state.NewStorageBackend(s.State)
  1111  	c.Assert(err, jc.ErrorIsNil)
  1112  	filesystemAttachments, err := sb.MachineFilesystemAttachments(machine.MachineTag())
  1113  	c.Assert(err, jc.ErrorIsNil)
  1114  	c.Assert(filesystemAttachments, gc.HasLen, 1)
  1115  }
  1116  
  1117  func (s *assignCleanSuite) TestAssignToMachineErrors(c *gc.C) {
  1118  	_, unit, _ := s.setupSingleStorage(c, "filesystem", "static")
  1119  	machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1120  	c.Assert(err, jc.ErrorIsNil)
  1121  	err = unit.AssignToMachine(machine)
  1122  	c.Assert(
  1123  		err, gc.ErrorMatches,
  1124  		`cannot assign unit "storage-filesystem/0" to machine 0: "static" storage provider does not support dynamic storage`,
  1125  	)
  1126  
  1127  	container, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
  1128  		Base: state.UbuntuBase("12.10"),
  1129  		Jobs: []state.MachineJob{state.JobHostUnits},
  1130  	}, machine.Id(), instance.LXD)
  1131  	c.Assert(err, jc.ErrorIsNil)
  1132  	err = unit.AssignToMachine(container)
  1133  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "storage-filesystem/0" to machine 0/lxd/0: adding storage to lxd container not supported`)
  1134  }
  1135  
  1136  func (s *assignCleanSuite) TestAssignUnitWithNonDynamicStorageCleanAvailable(c *gc.C) {
  1137  	_, unit, _ := s.setupSingleStorage(c, "filesystem", "static")
  1138  	sb, err := state.NewStorageBackend(s.State)
  1139  	c.Assert(err, jc.ErrorIsNil)
  1140  	storageAttachments, err := sb.UnitStorageAttachments(unit.UnitTag())
  1141  	c.Assert(err, jc.ErrorIsNil)
  1142  	c.Assert(storageAttachments, gc.HasLen, 1)
  1143  
  1144  	// Add a clean machine.
  1145  	clean, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1146  	c.Assert(err, jc.ErrorIsNil)
  1147  
  1148  	// assign the unit to a machine, requesting clean/empty. Since
  1149  	// the unit has non dynamic storage instances associated,
  1150  	// it will be forced onto a new machine.
  1151  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
  1152  	c.Assert(err, jc.ErrorIsNil)
  1153  
  1154  	// Check the machine on the unit is set.
  1155  	machineId, err := unit.AssignedMachineId()
  1156  	c.Assert(err, jc.ErrorIsNil)
  1157  	// Check that the machine isn't our clean one.
  1158  	c.Assert(machineId, gc.Not(gc.Equals), clean.Id())
  1159  }
  1160  
  1161  func (s *assignCleanSuite) TestAssignUnitWithNonDynamicStorageAndMachinePlacementDirective(c *gc.C) {
  1162  	_, unit, _ := s.setupSingleStorage(c, "filesystem", "static")
  1163  	sb, err := state.NewStorageBackend(s.State)
  1164  	c.Assert(err, jc.ErrorIsNil)
  1165  	storageAttachments, err := sb.UnitStorageAttachments(unit.UnitTag())
  1166  	c.Assert(err, jc.ErrorIsNil)
  1167  	c.Assert(storageAttachments, gc.HasLen, 1)
  1168  
  1169  	// Add a clean machine.
  1170  	clean, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1171  	c.Assert(err, jc.ErrorIsNil)
  1172  
  1173  	// assign the unit to a machine, requesting clean/empty. Since
  1174  	// the unit has non dynamic storage instances associated,
  1175  	// it will be forced onto a new machine.
  1176  	placement := &instance.Placement{
  1177  		instance.MachineScope, clean.Id(),
  1178  	}
  1179  	err = s.State.AssignUnitWithPlacement(unit, placement)
  1180  	c.Assert(
  1181  		err, gc.ErrorMatches,
  1182  		`cannot assign unit "storage-filesystem/0" to machine 0: "static" storage provider does not support dynamic storage`,
  1183  	)
  1184  }
  1185  
  1186  func (s *assignCleanSuite) TestAssignUnitWithNonDynamicStorageAndZonePlacementDirective(c *gc.C) {
  1187  	_, unit, _ := s.setupSingleStorage(c, "filesystem", "static")
  1188  	sb, err := state.NewStorageBackend(s.State)
  1189  	c.Assert(err, jc.ErrorIsNil)
  1190  	storageAttachments, err := sb.UnitStorageAttachments(unit.UnitTag())
  1191  	c.Assert(err, jc.ErrorIsNil)
  1192  	c.Assert(storageAttachments, gc.HasLen, 1)
  1193  
  1194  	// Add a clean machine.
  1195  	clean, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1196  	c.Assert(err, jc.ErrorIsNil)
  1197  
  1198  	// assign the unit to a machine, requesting clean/empty. Since
  1199  	// the unit has non dynamic storage instances associated,
  1200  	// it will be forced onto a new machine.
  1201  	placement := &instance.Placement{
  1202  		s.State.ModelUUID(), "zone=test",
  1203  	}
  1204  	err = s.State.AssignUnitWithPlacement(unit, placement)
  1205  	c.Assert(err, jc.ErrorIsNil)
  1206  
  1207  	// Check the machine on the unit is set.
  1208  	machineId, err := unit.AssignedMachineId()
  1209  	c.Assert(err, jc.ErrorIsNil)
  1210  	// Check that the machine isn't our clean one.
  1211  	c.Assert(machineId, gc.Not(gc.Equals), clean.Id())
  1212  }
  1213  
  1214  func (s *assignCleanSuite) TestAssignUnitWithDynamicStorageCleanAvailable(c *gc.C) {
  1215  	amdArch := "amd64"
  1216  	hwChar := &instance.HardwareCharacteristics{
  1217  		Arch: &amdArch,
  1218  	}
  1219  
  1220  	_, unit, _ := s.setupSingleStorage(c, "filesystem", "loop-pool")
  1221  	sb, err := state.NewStorageBackend(s.State)
  1222  	c.Assert(err, jc.ErrorIsNil)
  1223  	storageAttachments, err := sb.UnitStorageAttachments(unit.UnitTag())
  1224  	c.Assert(err, jc.ErrorIsNil)
  1225  	c.Assert(storageAttachments, gc.HasLen, 1)
  1226  
  1227  	// Add a clean machine.
  1228  	clean, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1229  	c.Assert(err, jc.ErrorIsNil)
  1230  
  1231  	instId := instance.Id("i-host-machine")
  1232  	err = clean.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1233  	c.Assert(err, jc.ErrorIsNil)
  1234  
  1235  	// assign the unit to a machine, requesting clean/empty
  1236  	err = s.State.AssignUnit(unit, state.AssignCleanEmpty)
  1237  	c.Assert(err, jc.ErrorIsNil)
  1238  
  1239  	// Check the machine on the unit is set.
  1240  	machineId, err := unit.AssignedMachineId()
  1241  	c.Assert(err, jc.ErrorIsNil)
  1242  	// Check that the machine isn't our clean one.
  1243  	c.Assert(machineId, gc.Equals, clean.Id())
  1244  
  1245  	// Check that a volume attachments were added to the machine.
  1246  	machine, err := s.State.Machine(machineId)
  1247  	c.Assert(err, jc.ErrorIsNil)
  1248  	volumeAttachments, err := sb.MachineVolumeAttachments(machine.MachineTag())
  1249  	c.Assert(err, jc.ErrorIsNil)
  1250  	c.Assert(volumeAttachments, gc.HasLen, 1)
  1251  
  1252  	volume, err := sb.Volume(volumeAttachments[0].Volume())
  1253  	c.Assert(err, jc.ErrorIsNil)
  1254  	volumeStorageInstance, err := volume.StorageInstance()
  1255  	c.Assert(err, jc.ErrorIsNil)
  1256  	c.Assert(volumeStorageInstance, gc.Equals, storageAttachments[0].StorageInstance())
  1257  }
  1258  
  1259  func (s *assignCleanSuite) TestAssignUnitPolicy(c *gc.C) {
  1260  	amdArch := "amd64"
  1261  	hwChar := &instance.HardwareCharacteristics{
  1262  		Arch: &amdArch,
  1263  	}
  1264  
  1265  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
  1266  	c.Assert(err, jc.ErrorIsNil)
  1267  
  1268  	// Check unassigned placements with no clean and/or empty machines.
  1269  	for i := 0; i < 10; i++ {
  1270  		unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1271  		c.Assert(err, jc.ErrorIsNil)
  1272  		err = s.State.AssignUnit(unit, s.policy)
  1273  		c.Assert(err, jc.ErrorIsNil)
  1274  		mid, err := unit.AssignedMachineId()
  1275  		c.Assert(err, jc.ErrorIsNil)
  1276  		c.Assert(mid, gc.Equals, strconv.Itoa(1+i))
  1277  		assertMachineCount(c, s.State, i+2)
  1278  
  1279  		// Sanity check that the machine knows about its assigned unit and was
  1280  		// created with the appropriate series.
  1281  		m, err := s.State.Machine(mid)
  1282  		c.Assert(err, jc.ErrorIsNil)
  1283  		units, err := m.Units()
  1284  		c.Assert(err, jc.ErrorIsNil)
  1285  		c.Assert(units, gc.HasLen, 1)
  1286  		c.Assert(units[0].Name(), gc.Equals, unit.Name())
  1287  		c.Assert(m.Base().String(), gc.Equals, "ubuntu@12.10/stable")
  1288  	}
  1289  
  1290  	// Remove units from alternate machines. These machines will still be
  1291  	// considered as dirty so will continue to be ignored by the policy.
  1292  	for i := 1; i < 11; i += 2 {
  1293  		mid := strconv.Itoa(i)
  1294  		m, err := s.State.Machine(mid)
  1295  		c.Assert(err, jc.ErrorIsNil)
  1296  		units, err := m.Units()
  1297  		c.Assert(err, jc.ErrorIsNil)
  1298  		c.Assert(units, gc.HasLen, 1)
  1299  		unit := units[0]
  1300  		err = unit.UnassignFromMachine()
  1301  		c.Assert(err, jc.ErrorIsNil)
  1302  		err = unit.Destroy()
  1303  		c.Assert(err, jc.ErrorIsNil)
  1304  	}
  1305  
  1306  	var expectedMachines []string
  1307  	// Create a new, clean machine but add containers so it is not empty.
  1308  	hostMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1309  	c.Assert(err, jc.ErrorIsNil)
  1310  
  1311  	instId := instance.Id("i-host-machine")
  1312  	err = hostMachine.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1313  	c.Assert(err, jc.ErrorIsNil)
  1314  
  1315  	container, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
  1316  		Base: state.UbuntuBase("12.10"),
  1317  		Jobs: []state.MachineJob{state.JobHostUnits},
  1318  	}, hostMachine.Id(), instance.LXD)
  1319  	c.Assert(err, jc.ErrorIsNil)
  1320  	c.Assert(hostMachine.Clean(), jc.IsTrue)
  1321  	s.assertMachineNotEmpty(c, hostMachine)
  1322  
  1323  	instId = instance.Id("i-container")
  1324  	err = container.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1325  	c.Assert(err, jc.ErrorIsNil)
  1326  
  1327  	if s.policy == state.AssignClean {
  1328  		expectedMachines = append(expectedMachines, hostMachine.Id())
  1329  	}
  1330  	expectedMachines = append(expectedMachines, container.Id())
  1331  
  1332  	// Add some more clean machines
  1333  	for i := 0; i < 4; i++ {
  1334  		m, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1335  		c.Assert(err, jc.ErrorIsNil)
  1336  
  1337  		instId = instance.Id(fmt.Sprintf("i-machine-%d", i))
  1338  		err = m.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1339  		c.Assert(err, jc.ErrorIsNil)
  1340  
  1341  		expectedMachines = append(expectedMachines, m.Id())
  1342  	}
  1343  
  1344  	// Assign units to all the expectedMachines machines.
  1345  	var got []string
  1346  	for range expectedMachines {
  1347  		unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1348  		c.Assert(err, jc.ErrorIsNil)
  1349  		err = s.State.AssignUnit(unit, s.policy)
  1350  		c.Assert(err, jc.ErrorIsNil)
  1351  		mid, err := unit.AssignedMachineId()
  1352  		c.Assert(err, jc.ErrorIsNil)
  1353  		got = append(got, mid)
  1354  	}
  1355  	sort.Strings(expectedMachines)
  1356  	sort.Strings(got)
  1357  	c.Assert(got, gc.DeepEquals, expectedMachines)
  1358  }
  1359  
  1360  func (s *assignCleanSuite) TestAssignUnitPolicyWithContainers(c *gc.C) {
  1361  	amdArch := "amd64"
  1362  	hwChar := &instance.HardwareCharacteristics{
  1363  		Arch: &amdArch,
  1364  	}
  1365  
  1366  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
  1367  	c.Assert(err, jc.ErrorIsNil)
  1368  
  1369  	// Create a machine and add a new container.
  1370  	hostMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1371  	c.Assert(err, jc.ErrorIsNil)
  1372  
  1373  	instId := instance.Id("i-host-machine")
  1374  	err = hostMachine.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1375  	c.Assert(err, jc.ErrorIsNil)
  1376  
  1377  	container, err := s.State.AddMachineInsideMachine(state.MachineTemplate{
  1378  		Base: state.UbuntuBase("12.10"),
  1379  		Jobs: []state.MachineJob{state.JobHostUnits},
  1380  	}, hostMachine.Id(), instance.LXD)
  1381  	c.Assert(err, jc.ErrorIsNil)
  1382  	err = hostMachine.Refresh()
  1383  	c.Assert(err, jc.ErrorIsNil)
  1384  	c.Assert(hostMachine.Clean(), jc.IsTrue)
  1385  	s.assertMachineNotEmpty(c, hostMachine)
  1386  
  1387  	instId = instance.Id("i-container")
  1388  	err = container.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1389  	c.Assert(err, jc.ErrorIsNil)
  1390  
  1391  	// Set up constraints to specify we want to install into a container.
  1392  	econs := constraints.MustParse("container=lxd")
  1393  	err = s.State.SetModelConstraints(econs)
  1394  	c.Assert(err, jc.ErrorIsNil)
  1395  
  1396  	// Check the first placement goes into the newly created, clean container above.
  1397  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1398  	c.Assert(err, jc.ErrorIsNil)
  1399  	err = s.State.AssignUnit(unit, s.policy)
  1400  	c.Assert(err, jc.ErrorIsNil)
  1401  	mid, err := unit.AssignedMachineId()
  1402  	c.Assert(err, jc.ErrorIsNil)
  1403  	c.Assert(mid, gc.Equals, container.Id())
  1404  
  1405  	assertContainerPlacement := func(expectedNumUnits int) {
  1406  		unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
  1407  		c.Assert(err, jc.ErrorIsNil)
  1408  		err = s.State.AssignUnit(unit, s.policy)
  1409  		c.Assert(err, jc.ErrorIsNil)
  1410  		mid, err := unit.AssignedMachineId()
  1411  		c.Assert(err, jc.ErrorIsNil)
  1412  		c.Assert(mid, gc.Equals, fmt.Sprintf("%d/lxd/0", expectedNumUnits+1))
  1413  		assertMachineCount(c, s.State, 2*expectedNumUnits+3)
  1414  
  1415  		// Sanity check that the machine knows about its assigned unit and was
  1416  		// created with the appropriate series.
  1417  		m, err := s.State.Machine(mid)
  1418  		c.Assert(err, jc.ErrorIsNil)
  1419  		units, err := m.Units()
  1420  		c.Assert(err, jc.ErrorIsNil)
  1421  		c.Assert(units, gc.HasLen, 1)
  1422  		c.Assert(units[0].Name(), gc.Equals, unit.Name())
  1423  		c.Assert(m.Base().String(), gc.Equals, "ubuntu@12.10/stable")
  1424  	}
  1425  
  1426  	// Check unassigned placements with no clean and/or empty machines cause a new container to be created.
  1427  	assertContainerPlacement(1)
  1428  	assertContainerPlacement(2)
  1429  
  1430  	// Create a new, clean instance and check that the next container creation uses it.
  1431  	hostMachine, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits)
  1432  	c.Assert(err, jc.ErrorIsNil)
  1433  	instId = instance.Id("i-host-machine")
  1434  	err = hostMachine.SetProvisioned(instId, "", "fake-nonce", hwChar)
  1435  	c.Assert(err, jc.ErrorIsNil)
  1436  
  1437  	unit, err = s.wordpress.AddUnit(state.AddUnitParams{})
  1438  	c.Assert(err, jc.ErrorIsNil)
  1439  	err = s.State.AssignUnit(unit, s.policy)
  1440  	c.Assert(err, jc.ErrorIsNil)
  1441  	mid, err = unit.AssignedMachineId()
  1442  	c.Assert(err, jc.ErrorIsNil)
  1443  	c.Assert(mid, gc.Equals, hostMachine.Id()+"/lxd/0")
  1444  }
  1445  
  1446  func (s *assignCleanSuite) TestAssignUnitPolicyConcurrently(c *gc.C) {
  1447  	_, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) // bootstrap machine
  1448  	c.Assert(err, jc.ErrorIsNil)
  1449  	unitCount := 50
  1450  	if raceDetector {
  1451  		unitCount = 10
  1452  	}
  1453  	us := make([]*state.Unit, unitCount)
  1454  	for i := range us {
  1455  		us[i], err = s.wordpress.AddUnit(state.AddUnitParams{})
  1456  		c.Assert(err, jc.ErrorIsNil)
  1457  	}
  1458  	type result struct {
  1459  		u   *state.Unit
  1460  		err error
  1461  	}
  1462  	done := make(chan result)
  1463  	for i, u := range us {
  1464  		i, u := i, u
  1465  		go func() {
  1466  			// Start the AssignUnit at different times
  1467  			// to increase the likeliness of a race.
  1468  			time.Sleep(time.Duration(i) * time.Millisecond / 2)
  1469  			err := s.State.AssignUnit(u, s.policy)
  1470  			done <- result{u, err}
  1471  		}()
  1472  	}
  1473  	assignments := make(map[string][]*state.Unit)
  1474  	for range us {
  1475  		r := <-done
  1476  		if !c.Check(r.err, gc.IsNil) {
  1477  			continue
  1478  		}
  1479  		id, err := r.u.AssignedMachineId()
  1480  		c.Assert(err, jc.ErrorIsNil)
  1481  		assignments[id] = append(assignments[id], r.u)
  1482  	}
  1483  	for id, us := range assignments {
  1484  		if len(us) != 1 {
  1485  			c.Errorf("machine %s expected one unit, got %q", id, us)
  1486  		}
  1487  	}
  1488  	c.Assert(assignments, gc.HasLen, len(us))
  1489  }