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 }