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

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	jc "github.com/juju/testing/checkers"
    11  	gc "gopkg.in/check.v1"
    12  
    13  	"github.com/juju/juju/core/arch"
    14  	"github.com/juju/juju/core/constraints"
    15  	"github.com/juju/juju/core/instance"
    16  	"github.com/juju/juju/environs/context"
    17  	"github.com/juju/juju/state"
    18  )
    19  
    20  type InstanceDistributorSuite struct {
    21  	ConnSuite
    22  	distributor mockInstanceDistributor
    23  	wordpress   *state.Application
    24  	machines    []*state.Machine
    25  	hwChar      *instance.HardwareCharacteristics
    26  }
    27  
    28  var _ = gc.Suite(&InstanceDistributorSuite{})
    29  
    30  type mockInstanceDistributor struct {
    31  	candidates        []instance.Id
    32  	distributionGroup []instance.Id
    33  	result            []instance.Id
    34  	err               error
    35  }
    36  
    37  func (p *mockInstanceDistributor) DistributeInstances(
    38  	ctx context.ProviderCallContext, candidates, distributionGroup []instance.Id, limitZones []string,
    39  ) ([]instance.Id, error) {
    40  	p.candidates = candidates
    41  	p.distributionGroup = distributionGroup
    42  	result := p.result
    43  	if result == nil {
    44  		result = candidates
    45  	}
    46  	return result, p.err
    47  }
    48  
    49  func (s *InstanceDistributorSuite) SetUpTest(c *gc.C) {
    50  	s.ConnSuite.SetUpTest(c)
    51  
    52  	s.distributor = mockInstanceDistributor{}
    53  	s.policy.GetInstanceDistributor = func() (context.Distributor, error) {
    54  		return &s.distributor, nil
    55  	}
    56  
    57  	a := arch.DefaultArchitecture
    58  	s.hwChar = &instance.HardwareCharacteristics{
    59  		Arch: &a,
    60  	}
    61  
    62  	s.wordpress = s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
    63  
    64  	s.machines = make([]*state.Machine, 3)
    65  	for i := range s.machines {
    66  		m, err := s.State.AddOneMachine(state.MachineTemplate{
    67  			Base:        state.UbuntuBase("12.10"),
    68  			Jobs:        []state.MachineJob{state.JobHostUnits},
    69  			Constraints: constraints.MustParse("arch=amd64"),
    70  		})
    71  		c.Assert(err, jc.ErrorIsNil)
    72  
    73  		hwChar := *s.hwChar
    74  		if i <= 1 {
    75  			az := "az1"
    76  			hwChar.AvailabilityZone = &az
    77  		}
    78  
    79  		instId := instance.Id(fmt.Sprintf("i-blah-%d", i))
    80  		err = m.SetProvisioned(instId, "", "fake-nonce", &hwChar)
    81  		c.Assert(err, jc.ErrorIsNil)
    82  
    83  		s.machines[i] = m
    84  	}
    85  }
    86  
    87  func (s *InstanceDistributorSuite) setupScenario(c *gc.C) {
    88  	// Assign a unit so we have a non-empty distribution group, and
    89  	// provision all instances so we have candidates.
    90  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
    91  	c.Assert(err, jc.ErrorIsNil)
    92  	err = unit.AssignToMachine(s.machines[0])
    93  	c.Assert(err, jc.ErrorIsNil)
    94  }
    95  
    96  func (s *InstanceDistributorSuite) TestDistributeInstances(c *gc.C) {
    97  	s.setupScenario(c)
    98  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
    99  	c.Assert(err, jc.ErrorIsNil)
   100  	_, err = unit.AssignToCleanMachine()
   101  	c.Assert(err, jc.ErrorIsNil)
   102  	c.Assert(s.distributor.candidates, jc.SameContents, []instance.Id{"i-blah-1", "i-blah-2"})
   103  	c.Assert(s.distributor.distributionGroup, jc.SameContents, []instance.Id{"i-blah-0"})
   104  	s.distributor.result = []instance.Id{}
   105  	_, err = unit.AssignToCleanMachine()
   106  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   107  }
   108  
   109  func (s *InstanceDistributorSuite) TestDistributeInstancesInvalidInstances(c *gc.C) {
   110  	s.setupScenario(c)
   111  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   112  	c.Assert(err, jc.ErrorIsNil)
   113  	s.distributor.result = []instance.Id{"notthere"}
   114  	_, err = unit.AssignToCleanMachine()
   115  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/1" to clean machine: invalid instance returned: notthere`)
   116  }
   117  
   118  func (s *InstanceDistributorSuite) TestDistributeInstancesNoEmptyMachines(c *gc.C) {
   119  	for range s.machines {
   120  		// Assign a unit so we have a non-empty distribution group.
   121  		unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   122  		c.Assert(err, jc.ErrorIsNil)
   123  		_, err = unit.AssignToCleanMachine()
   124  		c.Assert(err, jc.ErrorIsNil)
   125  	}
   126  
   127  	// InstanceDistributor is not called if there are no empty instances.
   128  	s.distributor.err = fmt.Errorf("no assignment for you")
   129  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   130  	c.Assert(err, jc.ErrorIsNil)
   131  	_, err = unit.AssignToCleanMachine()
   132  	c.Assert(err, gc.ErrorMatches, eligibleMachinesInUse)
   133  }
   134  
   135  func (s *InstanceDistributorSuite) TestDistributeInstancesErrors(c *gc.C) {
   136  	s.setupScenario(c)
   137  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   138  	c.Assert(err, jc.ErrorIsNil)
   139  
   140  	// Ensure that assignment fails when DistributeInstances returns an error.
   141  	s.distributor.err = fmt.Errorf("no assignment for you")
   142  	_, err = unit.AssignToCleanMachine()
   143  	c.Assert(err, gc.ErrorMatches, ".*no assignment for you")
   144  	_, err = unit.AssignToCleanEmptyMachine()
   145  	c.Assert(err, gc.ErrorMatches, ".*no assignment for you")
   146  	// If the policy's InstanceDistributor method fails, that will be returned first.
   147  	s.policy.GetInstanceDistributor = func() (context.Distributor, error) {
   148  		return nil, fmt.Errorf("incapable of InstanceDistributor")
   149  	}
   150  	_, err = unit.AssignToCleanMachine()
   151  	c.Assert(err, gc.ErrorMatches, ".*incapable of InstanceDistributor")
   152  }
   153  
   154  func (s *InstanceDistributorSuite) TestDistributeInstancesDistributionGroup(c *gc.C) {
   155  	unit0, err := s.wordpress.AddUnit(state.AddUnitParams{})
   156  	c.Assert(err, jc.ErrorIsNil)
   157  	_, err = unit0.AssignToCleanMachine()
   158  	c.Assert(err, jc.ErrorIsNil)
   159  
   160  	// Distribution group is not empty, because the machine assigned.
   161  	unit1, err := s.wordpress.AddUnit(state.AddUnitParams{})
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	_, err = unit1.AssignToCleanMachine()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  }
   166  
   167  func (s *InstanceDistributorSuite) TestDistributeInstancesEmptyDistributionGroup(c *gc.C) {
   168  	s.distributor.err = fmt.Errorf("no assignment for you")
   169  
   170  	// InstanceDistributor is not called if the distribution group is empty.
   171  	unit0, err := s.wordpress.AddUnit(state.AddUnitParams{})
   172  	c.Assert(err, jc.ErrorIsNil)
   173  	_, err = unit0.AssignToCleanMachine()
   174  	c.Assert(err, jc.ErrorIsNil)
   175  }
   176  
   177  func (s *InstanceDistributorSuite) TestDistributeInstancesEmptyDistributionGroupAfterAssignWithNonProvision(c *gc.C) {
   178  	s.distributor.err = fmt.Errorf("no assignment for you")
   179  
   180  	// InstanceDistributor is not called if the distribution group is empty.
   181  	m, err := s.State.AddOneMachine(state.MachineTemplate{
   182  		Base:                    state.UbuntuBase("12.10"),
   183  		Jobs:                    []state.MachineJob{state.JobHostUnits},
   184  		Constraints:             constraints.MustParse("arch=amd64"),
   185  		HardwareCharacteristics: *s.hwChar,
   186  	})
   187  	c.Assert(err, jc.ErrorIsNil)
   188  
   189  	unit0, err := s.wordpress.AddUnit(state.AddUnitParams{})
   190  	c.Assert(err, jc.ErrorIsNil)
   191  	err = unit0.AssignToMachine(m)
   192  	c.Assert(err, jc.ErrorIsNil)
   193  
   194  	// Distribution group is still empty, because the machine assigned to has
   195  	// not been provisioned.
   196  	unit1, err := s.wordpress.AddUnit(state.AddUnitParams{})
   197  	c.Assert(err, jc.ErrorIsNil)
   198  	_, err = unit1.AssignToCleanMachine()
   199  	c.Assert(err, jc.ErrorIsNil)
   200  }
   201  
   202  func (s *InstanceDistributorSuite) TestInstanceDistributorUnimplemented(c *gc.C) {
   203  	s.setupScenario(c)
   204  
   205  	var distributorErr error
   206  	s.policy.GetInstanceDistributor = func() (context.Distributor, error) {
   207  		return nil, distributorErr
   208  	}
   209  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   210  	c.Assert(err, jc.ErrorIsNil)
   211  	_, err = unit.AssignToCleanMachine()
   212  	c.Assert(err, gc.ErrorMatches, `cannot assign unit "wordpress/1" to clean machine: policy returned nil instance distributor without an error`)
   213  
   214  	distributorErr = errors.NotImplementedf("InstanceDistributor")
   215  	_, err = unit.AssignToCleanMachine()
   216  	c.Assert(err, jc.ErrorIsNil)
   217  }
   218  
   219  func (s *InstanceDistributorSuite) TestDistributeInstancesNoPolicy(c *gc.C) {
   220  	s.policy.GetInstanceDistributor = func() (context.Distributor, error) {
   221  		c.Errorf("should not have been invoked")
   222  		return nil, nil
   223  	}
   224  	state.SetPolicy(s.State, nil)
   225  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	_, err = unit.AssignToCleanMachine()
   228  	c.Assert(err, jc.ErrorIsNil)
   229  }
   230  
   231  func (s *InstanceDistributorSuite) TestDistributeInstancesWithZoneConstraints(c *gc.C) {
   232  	err := s.wordpress.SetConstraints(constraints.MustParse("zones=az1"))
   233  	c.Assert(err, jc.ErrorIsNil)
   234  
   235  	// Initial unit, assigned to machine 0, to get a distribution group.
   236  	unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	err = unit.AssignToMachine(s.machines[0])
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	unit, err = s.wordpress.AddUnit(state.AddUnitParams{})
   242  	c.Assert(err, jc.ErrorIsNil)
   243  
   244  	// Only machine 1 is empty, and in the desired AZ.
   245  	s.distributor.result = []instance.Id{"i-blah-1"}
   246  	_, err = unit.AssignToCleanMachine()
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	// Machine 2 filtered by zone constraint.
   250  	c.Check(s.distributor.candidates, jc.SameContents, []instance.Id{"i-blah-1"})
   251  	c.Check(s.distributor.distributionGroup, jc.SameContents, []instance.Id{"i-blah-0"})
   252  }
   253  
   254  type ApplicationMachinesSuite struct {
   255  	ConnSuite
   256  	wordpress *state.Application
   257  	mysql     *state.Application
   258  	machines  []*state.Machine
   259  }
   260  
   261  var _ = gc.Suite(&ApplicationMachinesSuite{})
   262  
   263  func (s *ApplicationMachinesSuite) SetUpTest(c *gc.C) {
   264  	s.ConnSuite.SetUpTest(c)
   265  
   266  	s.wordpress = s.AddTestingApplication(
   267  		c,
   268  		"wordpress",
   269  		s.AddTestingCharm(c, "wordpress"),
   270  	)
   271  	s.mysql = s.AddTestingApplication(
   272  		c,
   273  		"mysql",
   274  		s.AddTestingCharm(c, "mysql"),
   275  	)
   276  
   277  	s.machines = make([]*state.Machine, 5)
   278  	for i := range s.machines {
   279  		var err error
   280  		s.machines[i], err = s.State.AddOneMachine(state.MachineTemplate{
   281  			Base: state.UbuntuBase("12.10"),
   282  			Jobs: []state.MachineJob{state.JobHostUnits},
   283  		})
   284  		c.Assert(err, jc.ErrorIsNil)
   285  	}
   286  
   287  	for _, i := range []int{0, 1, 4} {
   288  		unit, err := s.wordpress.AddUnit(state.AddUnitParams{})
   289  		c.Assert(err, jc.ErrorIsNil)
   290  		err = unit.AssignToMachine(s.machines[i])
   291  		c.Assert(err, jc.ErrorIsNil)
   292  	}
   293  	for _, i := range []int{2, 3} {
   294  		unit, err := s.mysql.AddUnit(state.AddUnitParams{})
   295  		c.Assert(err, jc.ErrorIsNil)
   296  		err = unit.AssignToMachine(s.machines[i])
   297  		c.Assert(err, jc.ErrorIsNil)
   298  	}
   299  }
   300  
   301  func (s *ApplicationMachinesSuite) TestApplicationMachines(c *gc.C) {
   302  	machines, err := state.ApplicationMachines(s.State, "mysql")
   303  	c.Assert(err, jc.ErrorIsNil)
   304  	c.Assert(machines, gc.DeepEquals, []string{"2", "3"})
   305  
   306  	machines, err = state.ApplicationMachines(s.State, "wordpress")
   307  	c.Assert(err, jc.ErrorIsNil)
   308  	c.Assert(machines, gc.DeepEquals, []string{"0", "1", "4"})
   309  
   310  	machines, err = state.ApplicationMachines(s.State, "fred")
   311  	c.Assert(err, jc.ErrorIsNil)
   312  	c.Assert(len(machines), gc.Equals, 0)
   313  }