github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/api/agent/provisioner/provisioner_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner_test 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/names/v5" 12 jc "github.com/juju/testing/checkers" 13 "github.com/juju/utils/v3" 14 "go.uber.org/mock/gomock" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/api" 18 "github.com/juju/juju/api/agent/provisioner" 19 apimocks "github.com/juju/juju/api/base/mocks" 20 apitesting "github.com/juju/juju/api/testing" 21 apiservererrors "github.com/juju/juju/apiserver/errors" 22 "github.com/juju/juju/container" 23 "github.com/juju/juju/core/arch" 24 "github.com/juju/juju/core/constraints" 25 "github.com/juju/juju/core/instance" 26 "github.com/juju/juju/core/life" 27 corenetwork "github.com/juju/juju/core/network" 28 "github.com/juju/juju/core/status" 29 "github.com/juju/juju/core/watcher/watchertest" 30 "github.com/juju/juju/environs/config" 31 "github.com/juju/juju/juju/testing" 32 "github.com/juju/juju/network" 33 "github.com/juju/juju/rpc/params" 34 "github.com/juju/juju/state" 35 "github.com/juju/juju/storage/poolmanager" 36 "github.com/juju/juju/storage/provider" 37 coretesting "github.com/juju/juju/testing" 38 coretools "github.com/juju/juju/tools" 39 jujuversion "github.com/juju/juju/version" 40 ) 41 42 type provisionerSuite struct { 43 testing.JujuConnSuite 44 *apitesting.ModelWatcherTests 45 *apitesting.APIAddresserTests 46 47 st api.Connection 48 machine *state.Machine 49 50 provisioner *provisioner.State 51 } 52 53 var _ = gc.Suite(&provisionerSuite{}) 54 55 func (s *provisionerSuite) SetUpTest(c *gc.C) { 56 s.JujuConnSuite.SetUpTest(c) 57 58 var err error 59 s.machine, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobManageModel) 60 c.Assert(err, jc.ErrorIsNil) 61 password, err := utils.RandomPassword() 62 c.Assert(err, jc.ErrorIsNil) 63 err = s.machine.SetPassword(password) 64 c.Assert(err, jc.ErrorIsNil) 65 err = s.machine.SetInstanceInfo("i-manager", "", "fake_nonce", nil, nil, nil, nil, nil, nil) 66 c.Assert(err, jc.ErrorIsNil) 67 s.st = s.OpenAPIAsMachine(c, s.machine.Tag(), password, "fake_nonce") 68 c.Assert(s.st, gc.NotNil) 69 err = s.machine.SetProviderAddresses(corenetwork.NewSpaceAddress("0.1.2.3")) 70 c.Assert(err, jc.ErrorIsNil) 71 72 // Create the provisioner API facade. 73 s.provisioner = provisioner.NewState(s.st) 74 c.Assert(s.provisioner, gc.NotNil) 75 76 s.ModelWatcherTests = apitesting.NewModelWatcherTests(s.provisioner, s.BackingState, s.Model) 77 waitForModelWatchersIdle := func(c *gc.C) { 78 s.JujuConnSuite.WaitForModelWatchersIdle(c, s.BackingState.ModelUUID()) 79 } 80 systemState, err := s.StatePool.SystemState() 81 c.Assert(err, jc.ErrorIsNil) 82 s.APIAddresserTests = apitesting.NewAPIAddresserTests(s.provisioner, systemState, s.BackingState, waitForModelWatchersIdle) 83 } 84 85 func (s *provisionerSuite) assertGetOneMachine(c *gc.C, tag names.MachineTag) provisioner.MachineProvisioner { 86 result, err := s.provisioner.Machines(tag) 87 c.Assert(err, jc.ErrorIsNil) 88 c.Assert(len(result), gc.Equals, 1) 89 c.Assert(result[0].Err, gc.IsNil) 90 return result[0].Machine 91 } 92 93 func (s *provisionerSuite) TestMachinesTagAndId(c *gc.C) { 94 result, err := s.provisioner.Machines(names.NewMachineTag("42"), s.machine.MachineTag()) 95 c.Assert(err, jc.ErrorIsNil) 96 c.Assert(len(result), gc.Equals, 2) 97 98 c.Assert(result[0].Err, gc.ErrorMatches, "machine 42 not found") 99 c.Assert(result[0].Err, jc.Satisfies, params.IsCodeNotFound) 100 c.Assert(result[0].Machine, gc.IsNil) 101 102 c.Assert(result[1].Err, gc.IsNil) 103 c.Assert(result[1].Machine.Tag(), gc.Equals, s.machine.Tag()) 104 c.Assert(result[1].Machine.Id(), gc.Equals, s.machine.Id()) 105 } 106 107 func (s *provisionerSuite) TestGetSetStatus(c *gc.C) { 108 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 109 110 machineStatus, info, err := apiMachine.Status() 111 c.Assert(err, jc.ErrorIsNil) 112 c.Assert(machineStatus, gc.Equals, status.Pending) 113 c.Assert(info, gc.Equals, "") 114 115 err = apiMachine.SetStatus(status.Started, "blah", nil) 116 c.Assert(err, jc.ErrorIsNil) 117 118 machineStatus, info, err = apiMachine.Status() 119 c.Assert(err, jc.ErrorIsNil) 120 c.Assert(machineStatus, gc.Equals, status.Started) 121 c.Assert(info, gc.Equals, "blah") 122 statusInfo, err := s.machine.Status() 123 c.Assert(err, jc.ErrorIsNil) 124 c.Assert(statusInfo.Data, gc.HasLen, 0) 125 } 126 127 func (s *provisionerSuite) TestGetSetInstanceStatus(c *gc.C) { 128 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 129 instanceStatus, info, err := apiMachine.InstanceStatus() 130 c.Assert(err, jc.ErrorIsNil) 131 c.Assert(instanceStatus, gc.Equals, status.Pending) 132 c.Assert(info, gc.Equals, "") 133 err = apiMachine.SetInstanceStatus(status.Running, "blah", nil) 134 c.Assert(err, jc.ErrorIsNil) 135 instanceStatus, info, err = apiMachine.InstanceStatus() 136 c.Assert(err, jc.ErrorIsNil) 137 c.Assert(instanceStatus, gc.Equals, status.Running) 138 c.Assert(info, gc.Equals, "blah") 139 statusInfo, err := s.machine.InstanceStatus() 140 c.Assert(err, jc.ErrorIsNil) 141 c.Assert(statusInfo.Data, gc.HasLen, 0) 142 } 143 144 func (s *provisionerSuite) TestGetSetStatusWithData(c *gc.C) { 145 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 146 err := apiMachine.SetStatus(status.Error, "blah", map[string]interface{}{"foo": "bar"}) 147 c.Assert(err, jc.ErrorIsNil) 148 149 machineStatus, info, err := apiMachine.Status() 150 c.Assert(err, jc.ErrorIsNil) 151 c.Assert(machineStatus, gc.Equals, status.Error) 152 c.Assert(info, gc.Equals, "blah") 153 statusInfo, err := s.machine.Status() 154 c.Assert(err, jc.ErrorIsNil) 155 c.Assert(statusInfo.Data, gc.DeepEquals, map[string]interface{}{"foo": "bar"}) 156 } 157 158 func (s *provisionerSuite) TestMachinesWithTransientErrors(c *gc.C) { 159 machine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 160 c.Assert(err, jc.ErrorIsNil) 161 now := time.Now() 162 sInfo := status.StatusInfo{ 163 Status: status.ProvisioningError, 164 Message: "blah", 165 Data: map[string]interface{}{"transient": true}, 166 Since: &now, 167 } 168 err = machine.SetInstanceStatus(sInfo) 169 c.Assert(err, jc.ErrorIsNil) 170 result, err := s.provisioner.MachinesWithTransientErrors() 171 c.Assert(err, jc.ErrorIsNil) 172 c.Assert(result, gc.HasLen, 1) 173 174 c.Assert(result[0].Machine.Id(), gc.Equals, "1") 175 c.Assert(result[0].Status, gc.DeepEquals, params.StatusResult{ 176 Id: "1", 177 Life: "alive", 178 Status: "provisioning error", 179 Info: "blah", 180 Data: map[string]interface{}{"transient": true}, 181 }) 182 } 183 184 func (s *provisionerSuite) TestEnsureDeadAndRemove(c *gc.C) { 185 // Create a fresh machine to test the complete scenario. 186 otherMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 187 c.Assert(err, jc.ErrorIsNil) 188 c.Assert(otherMachine.Life(), gc.Equals, state.Alive) 189 190 apiMachine := s.assertGetOneMachine(c, otherMachine.MachineTag()) 191 c.Assert(err, jc.ErrorIsNil) 192 193 err = apiMachine.Remove() 194 c.Assert(err, gc.ErrorMatches, `cannot remove entity "machine-1": still alive`) 195 err = apiMachine.EnsureDead() 196 c.Assert(err, jc.ErrorIsNil) 197 198 err = otherMachine.Refresh() 199 c.Assert(err, jc.ErrorIsNil) 200 c.Assert(otherMachine.Life(), gc.Equals, state.Dead) 201 202 err = apiMachine.EnsureDead() 203 c.Assert(err, jc.ErrorIsNil) 204 err = otherMachine.Refresh() 205 c.Assert(err, jc.ErrorIsNil) 206 c.Assert(otherMachine.Life(), gc.Equals, state.Dead) 207 208 err = apiMachine.Remove() 209 c.Assert(err, jc.ErrorIsNil) 210 err = otherMachine.Refresh() 211 c.Assert(err, jc.Satisfies, errors.IsNotFound) 212 213 err = apiMachine.EnsureDead() 214 c.Assert(err, gc.ErrorMatches, "machine 1 not found") 215 c.Assert(err, jc.Satisfies, params.IsCodeNotFound) 216 217 // Now try to EnsureDead machine 0 - should fail. 218 apiMachine = s.assertGetOneMachine(c, s.machine.MachineTag()) 219 err = apiMachine.EnsureDead() 220 c.Assert(err, gc.ErrorMatches, "machine 0 is still a non-voting controller member") 221 } 222 223 func (s *provisionerSuite) TestMarkForRemoval(c *gc.C) { 224 machine, err := s.State.AddMachine(state.UbuntuBase("22.04"), state.JobHostUnits) 225 c.Assert(err, jc.ErrorIsNil) 226 227 apiMachine := s.assertGetOneMachine(c, machine.MachineTag()) 228 229 err = apiMachine.MarkForRemoval() 230 c.Assert(err, gc.ErrorMatches, "cannot remove machine 1: machine is not dead") 231 232 err = machine.EnsureDead() 233 c.Assert(err, jc.ErrorIsNil) 234 235 err = apiMachine.MarkForRemoval() 236 c.Assert(err, jc.ErrorIsNil) 237 238 removals, err := s.State.AllMachineRemovals() 239 c.Assert(err, jc.ErrorIsNil) 240 c.Assert(removals, jc.SameContents, []string{"1"}) 241 } 242 243 func (s *provisionerSuite) TestRefreshAndLife(c *gc.C) { 244 // Create a fresh machine to test the complete scenario. 245 otherMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 246 c.Assert(err, jc.ErrorIsNil) 247 c.Assert(otherMachine.Life(), gc.Equals, state.Alive) 248 249 apiMachine := s.assertGetOneMachine(c, otherMachine.MachineTag()) 250 c.Assert(apiMachine.Life(), gc.Equals, life.Alive) 251 252 err = apiMachine.EnsureDead() 253 c.Assert(err, jc.ErrorIsNil) 254 c.Assert(apiMachine.Life(), gc.Equals, life.Alive) 255 256 err = apiMachine.Refresh() 257 c.Assert(err, jc.ErrorIsNil) 258 c.Assert(apiMachine.Life(), gc.Equals, life.Dead) 259 } 260 261 func (s *provisionerSuite) TestSetInstanceInfo(c *gc.C) { 262 pm := poolmanager.New(state.NewStateSettings(s.State), provider.CommonStorageProviders()) 263 _, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"}) 264 c.Assert(err, jc.ErrorIsNil) 265 266 // Create a fresh machine, since machine 0 is already provisioned. 267 template := state.MachineTemplate{ 268 Base: state.UbuntuBase("12.10"), 269 Jobs: []state.MachineJob{state.JobHostUnits}, 270 Volumes: []state.HostVolumeParams{{ 271 Volume: state.VolumeParams{ 272 Pool: "loop-pool", 273 Size: 123, 274 }}, 275 }, 276 } 277 notProvisionedMachine, err := s.State.AddOneMachine(template) 278 c.Assert(err, jc.ErrorIsNil) 279 280 apiMachine := s.assertGetOneMachine(c, notProvisionedMachine.MachineTag()) 281 282 instanceId, err := apiMachine.InstanceId() 283 c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned) 284 c.Assert(err, gc.ErrorMatches, "machine 1 not provisioned") 285 c.Assert(instanceId, gc.Equals, instance.Id("")) 286 287 hwChars := instance.MustParseHardware("cores=123", "mem=4G") 288 289 volumes := []params.Volume{{ 290 VolumeTag: "volume-1-0", 291 Info: params.VolumeInfo{ 292 VolumeId: "vol-123", 293 Size: 124, 294 }, 295 }} 296 volumeAttachments := map[string]params.VolumeAttachmentInfo{ 297 "volume-1-0": { 298 DeviceName: "xvdf1", 299 }, 300 } 301 302 err = apiMachine.SetInstanceInfo( 303 "i-will", "", "fake_nonce", &hwChars, nil, volumes, volumeAttachments, nil, 304 ) 305 c.Assert(err, jc.ErrorIsNil) 306 307 instanceId, err = apiMachine.InstanceId() 308 c.Assert(err, jc.ErrorIsNil) 309 c.Assert(instanceId, gc.Equals, instance.Id("i-will")) 310 311 // Try it again - should fail. 312 err = apiMachine.SetInstanceInfo("i-wont", "", "fake", nil, nil, nil, nil, nil) 313 c.Assert(err, gc.ErrorMatches, `cannot record provisioning info for "i-wont": cannot set instance data for machine "1": already set`) 314 315 // Now try to get machine 0's instance id. 316 apiMachine = s.assertGetOneMachine(c, s.machine.MachineTag()) 317 instanceId, err = apiMachine.InstanceId() 318 c.Assert(err, jc.ErrorIsNil) 319 c.Assert(instanceId, gc.Equals, instance.Id("i-manager")) 320 321 // Now check volumes and volume attachments. 322 sb, err := state.NewStorageBackend(s.State) 323 c.Assert(err, jc.ErrorIsNil) 324 volume, err := sb.Volume(names.NewVolumeTag("1/0")) 325 c.Assert(err, jc.ErrorIsNil) 326 volumeInfo, err := volume.Info() 327 c.Assert(err, jc.ErrorIsNil) 328 c.Assert(volumeInfo, gc.Equals, state.VolumeInfo{ 329 VolumeId: "vol-123", 330 Pool: "loop-pool", 331 Size: 124, 332 }) 333 stateVolumeAttachments, err := sb.MachineVolumeAttachments(names.NewMachineTag("1")) 334 c.Assert(err, jc.ErrorIsNil) 335 c.Assert(stateVolumeAttachments, gc.HasLen, 1) 336 volumeAttachmentInfo, err := stateVolumeAttachments[0].Info() 337 c.Assert(err, jc.ErrorIsNil) 338 c.Assert(volumeAttachmentInfo, gc.Equals, state.VolumeAttachmentInfo{ 339 DeviceName: "xvdf1", 340 }) 341 } 342 343 func (s *provisionerSuite) TestAvailabilityZone(c *gc.C) { 344 // Create a fresh machine, since machine 0 is already provisioned. 345 template := state.MachineTemplate{ 346 Base: state.UbuntuBase("16.04"), 347 Jobs: []state.MachineJob{state.JobHostUnits}, 348 } 349 notProvisionedMachine, err := s.State.AddOneMachine(template) 350 c.Assert(err, jc.ErrorIsNil) 351 352 apiMachine := s.assertGetOneMachine(c, notProvisionedMachine.MachineTag()) 353 354 instanceId, err := apiMachine.InstanceId() 355 c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned) 356 c.Assert(err, gc.ErrorMatches, "machine 1 not provisioned") 357 c.Assert(instanceId, gc.Equals, instance.Id("")) 358 359 availabilityZone := "ru-north-siberia" 360 hwChars := instance.MustParseHardware(fmt.Sprintf("availability-zone=%s", availabilityZone)) 361 362 err = apiMachine.SetInstanceInfo( 363 "azinst", "", "nonce", &hwChars, nil, nil, nil, nil, 364 ) 365 c.Assert(err, jc.ErrorIsNil) 366 367 retAvailabilityZone, err := apiMachine.AvailabilityZone() 368 c.Assert(err, jc.ErrorIsNil) 369 c.Assert(availabilityZone, gc.Equals, retAvailabilityZone) 370 } 371 372 func (s *provisionerSuite) TestSetInstanceInfoProfiles(c *gc.C) { 373 // Create a fresh machine, since machine 0 is already provisioned. 374 template := state.MachineTemplate{ 375 Base: state.UbuntuBase("16.04"), 376 Jobs: []state.MachineJob{state.JobHostUnits}, 377 } 378 notProvisionedMachine, err := s.State.AddOneMachine(template) 379 c.Assert(err, jc.ErrorIsNil) 380 381 apiMachine := s.assertGetOneMachine(c, notProvisionedMachine.MachineTag()) 382 383 instanceId, err := apiMachine.InstanceId() 384 c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned) 385 c.Assert(err, gc.ErrorMatches, "machine 1 not provisioned") 386 c.Assert(instanceId, gc.Equals, instance.Id("")) 387 388 hwChars := instance.MustParseHardware("cores=123", "mem=4G") 389 390 profiles := []string{"juju-default-profile-0", "juju-default-lxd-2"} 391 err = apiMachine.SetInstanceInfo( 392 "profileinst", "", "nonce", &hwChars, nil, nil, nil, profiles, 393 ) 394 c.Assert(err, jc.ErrorIsNil) 395 396 mach, err := s.State.Machine(apiMachine.Id()) 397 c.Assert(err, jc.ErrorIsNil) 398 obtainedProfiles, err := mach.CharmProfiles() 399 c.Assert(err, jc.ErrorIsNil) 400 c.Assert(profiles, jc.SameContents, obtainedProfiles) 401 } 402 403 func (s *provisionerSuite) TestSetCharmProfiles(c *gc.C) { 404 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 405 406 profiles := []string{"juju-default-profile-0", "juju-default-lxd-2"} 407 err := apiMachine.SetCharmProfiles(profiles) 408 c.Assert(err, jc.ErrorIsNil) 409 410 mach, err := s.State.Machine(apiMachine.Id()) 411 c.Assert(err, jc.ErrorIsNil) 412 obtainedProfiles, err := mach.CharmProfiles() 413 c.Assert(err, jc.ErrorIsNil) 414 c.Assert(profiles, jc.SameContents, obtainedProfiles) 415 } 416 417 func (s *provisionerSuite) TestKeepInstance(c *gc.C) { 418 err := s.machine.SetKeepInstance(true) 419 c.Assert(err, jc.ErrorIsNil) 420 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 421 keep, err := apiMachine.KeepInstance() 422 c.Assert(err, jc.ErrorIsNil) 423 c.Assert(keep, jc.IsTrue) 424 } 425 426 func (s *provisionerSuite) TestDistributionGroup(c *gc.C) { 427 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 428 instances, err := apiMachine.DistributionGroup() 429 c.Assert(err, jc.ErrorIsNil) 430 c.Assert(instances, gc.DeepEquals, []instance.Id{"i-manager"}) 431 432 machine1, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 433 c.Assert(err, jc.ErrorIsNil) 434 apiMachine = s.assertGetOneMachine(c, machine1.MachineTag()) 435 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 436 437 err = apiMachine.SetInstanceInfo("i-d", "", "fake", nil, nil, nil, nil, nil) 438 c.Assert(err, jc.ErrorIsNil) 439 instances, err = apiMachine.DistributionGroup() 440 c.Assert(err, jc.ErrorIsNil) 441 c.Assert(instances, gc.HasLen, 0) // no units assigned 442 443 var unitNames []string 444 for i := 0; i < 3; i++ { 445 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 446 c.Assert(err, jc.ErrorIsNil) 447 unitNames = append(unitNames, unit.Name()) 448 err = unit.AssignToMachine(machine1) 449 c.Assert(err, jc.ErrorIsNil) 450 instances, err := apiMachine.DistributionGroup() 451 c.Assert(err, jc.ErrorIsNil) 452 c.Assert(instances, gc.DeepEquals, []instance.Id{"i-d"}) 453 } 454 } 455 456 func (s *provisionerSuite) TestDistributionGroupMachineNotFound(c *gc.C) { 457 stateMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 458 c.Assert(err, jc.ErrorIsNil) 459 apiMachine := s.assertGetOneMachine(c, stateMachine.MachineTag()) 460 err = apiMachine.EnsureDead() 461 c.Assert(err, jc.ErrorIsNil) 462 err = apiMachine.Remove() 463 c.Assert(err, jc.ErrorIsNil) 464 _, err = apiMachine.DistributionGroup() 465 c.Assert(err, gc.ErrorMatches, "machine 1 not found") 466 c.Assert(err, jc.Satisfies, params.IsCodeNotFound) 467 } 468 469 func (s *provisionerSuite) TestDistributionGroupByMachineId(c *gc.C) { 470 results, err := s.provisioner.DistributionGroupByMachineId(s.machine.MachineTag()) 471 c.Assert(err, jc.ErrorIsNil) 472 c.Assert(len(results), gc.Equals, 1) 473 c.Assert(results, gc.DeepEquals, []provisioner.DistributionGroupResult{ 474 {MachineIds: nil, Err: nil}, 475 }) 476 477 machine1, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 478 c.Assert(err, jc.ErrorIsNil) 479 wordpress := s.AddTestingApplication(c, "wordpress", s.AddTestingCharm(c, "wordpress")) 480 unit, err := wordpress.AddUnit(state.AddUnitParams{}) 481 c.Assert(err, jc.ErrorIsNil) 482 err = unit.AssignToMachine(machine1) 483 c.Assert(err, jc.ErrorIsNil) 484 485 results, err = s.provisioner.DistributionGroupByMachineId( 486 s.machine.MachineTag(), 487 machine1.MachineTag(), 488 ) 489 c.Assert(err, jc.ErrorIsNil) 490 c.Assert(len(results), gc.Equals, 2) 491 c.Assert(results, gc.DeepEquals, []provisioner.DistributionGroupResult{ 492 {MachineIds: nil, Err: nil}, 493 {MachineIds: nil, Err: nil}, 494 }) 495 496 machine2, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 497 c.Assert(err, jc.ErrorIsNil) 498 unit2, err := wordpress.AddUnit(state.AddUnitParams{}) 499 c.Assert(err, jc.ErrorIsNil) 500 err = unit2.AssignToMachine(machine2) 501 c.Assert(err, jc.ErrorIsNil) 502 503 results, err = s.provisioner.DistributionGroupByMachineId( 504 s.machine.MachineTag(), 505 machine1.MachineTag(), 506 machine2.MachineTag(), 507 ) 508 c.Assert(err, jc.ErrorIsNil) 509 c.Assert(len(results), gc.Equals, 3) 510 c.Assert(results, gc.DeepEquals, []provisioner.DistributionGroupResult{ 511 {MachineIds: nil, Err: nil}, 512 {MachineIds: []string{"2"}, Err: nil}, 513 {MachineIds: []string{"1"}, Err: nil}, 514 }) 515 } 516 517 func (s *provisionerSuite) TestDistributionGroupByMachineIdNotFound(c *gc.C) { 518 stateMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 519 c.Assert(err, jc.ErrorIsNil) 520 machineTag := stateMachine.MachineTag() 521 apiMachine := s.assertGetOneMachine(c, machineTag) 522 err = apiMachine.EnsureDead() 523 c.Assert(err, jc.ErrorIsNil) 524 err = apiMachine.Remove() 525 c.Assert(err, jc.ErrorIsNil) 526 results, err := s.provisioner.DistributionGroupByMachineId(machineTag) 527 c.Assert(err, jc.ErrorIsNil) 528 c.Assert(len(results), gc.Equals, 1) 529 c.Assert(results[0].Err, gc.ErrorMatches, "machine 1 not found") 530 c.Assert(results[0].Err, jc.Satisfies, params.IsCodeNotFound) 531 } 532 533 func (s *provisionerSuite) TestProvisioningInfo(c *gc.C) { 534 // Add a couple of spaces. 535 space1, err := s.State.AddSpace("space1", "", nil, true) 536 c.Assert(err, jc.ErrorIsNil) 537 space2, err := s.State.AddSpace("space2", "", nil, false) 538 c.Assert(err, jc.ErrorIsNil) 539 // Add 2 subnets into each space. 540 // Each subnet is in a matching zone (e.g "subnet-#" in "zone#"). 541 testing.AddSubnetsWithTemplate(c, s.State, 4, corenetwork.SubnetInfo{ 542 CIDR: "10.{{.}}.0.0/16", 543 ProviderId: "subnet-{{.}}", 544 AvailabilityZones: []string{"zone{{.}}"}, 545 SpaceID: fmt.Sprintf("{{if (lt . 2)}}%s{{else}}%s{{end}}", space1.Id(), space2.Id()), 546 }) 547 548 cons := constraints.MustParse("cores=12 mem=8G spaces=^space1,space2") 549 template := state.MachineTemplate{ 550 Base: state.UbuntuBase("12.10"), 551 Jobs: []state.MachineJob{state.JobHostUnits}, 552 Placement: "valid", 553 Constraints: cons, 554 } 555 machine, err := s.State.AddOneMachine(template) 556 c.Assert(err, jc.ErrorIsNil) 557 558 res, err := s.provisioner.ProvisioningInfo([]names.MachineTag{machine.MachineTag()}) 559 c.Assert(err, jc.ErrorIsNil) 560 561 results := res.Results 562 c.Assert(results, gc.HasLen, 1) 563 564 provisioningInfo := results[0].Result 565 c.Assert(provisioningInfo.Base, jc.DeepEquals, params.Base{Name: "ubuntu", Channel: "12.10/stable"}) 566 c.Assert(provisioningInfo.Placement, gc.Equals, template.Placement) 567 c.Assert(provisioningInfo.Constraints, jc.DeepEquals, template.Constraints) 568 569 c.Assert(provisioningInfo.SubnetAZs, jc.DeepEquals, map[string][]string{ 570 "subnet-2": {"zone2"}, 571 "subnet-3": {"zone3"}, 572 }) 573 574 c.Assert(provisioningInfo.SpaceSubnets, gc.HasLen, 1) 575 c.Assert(provisioningInfo.SpaceSubnets["space2"], jc.SameContents, []string{"subnet-2", "subnet-3"}) 576 } 577 578 func (s *provisionerSuite) TestProvisioningInfoMachineNotFound(c *gc.C) { 579 res, err := s.provisioner.ProvisioningInfo([]names.MachineTag{names.NewMachineTag("1")}) 580 c.Assert(err, jc.ErrorIsNil) 581 582 results := res.Results 583 c.Assert(results, gc.HasLen, 1) 584 c.Assert(results[0].Error, gc.ErrorMatches, "machine 1 not found") 585 c.Assert(results[0].Error, jc.Satisfies, params.IsCodeNotFound) 586 } 587 588 func (s *provisionerSuite) TestWatchContainers(c *gc.C) { 589 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 590 591 // Add one LXD container. 592 template := state.MachineTemplate{ 593 Base: state.UbuntuBase("12.10"), 594 Jobs: []state.MachineJob{state.JobHostUnits}, 595 } 596 container, err := s.State.AddMachineInsideMachine(template, s.machine.Id(), instance.LXD) 597 c.Assert(err, jc.ErrorIsNil) 598 599 w, err := apiMachine.WatchContainers(instance.LXD) 600 c.Assert(err, jc.ErrorIsNil) 601 wc := watchertest.NewStringsWatcherC(c, w) 602 defer wc.AssertStops() 603 604 // Initial event. 605 wc.AssertChange(container.Id()) 606 607 // Change something other than the containers and make sure it's 608 // not detected. 609 err = apiMachine.SetStatus(status.Started, "not really", nil) 610 c.Assert(err, jc.ErrorIsNil) 611 wc.AssertNoChange() 612 613 // Add a KVM container and make sure it's not detected. 614 container, err = s.State.AddMachineInsideMachine(template, s.machine.Id(), instance.KVM) 615 c.Assert(err, jc.ErrorIsNil) 616 wc.AssertNoChange() 617 618 // Add another LXD container and make sure it's detected. 619 container, err = s.State.AddMachineInsideMachine(template, s.machine.Id(), instance.LXD) 620 c.Assert(err, jc.ErrorIsNil) 621 wc.AssertChange(container.Id()) 622 } 623 624 func (s *provisionerSuite) TestWatchContainersAcceptsSupportedContainers(c *gc.C) { 625 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 626 627 for _, ctype := range instance.ContainerTypes { 628 w, err := apiMachine.WatchContainers(ctype) 629 c.Assert(w, gc.NotNil) 630 c.Assert(err, jc.ErrorIsNil) 631 } 632 } 633 634 func (s *provisionerSuite) TestWatchContainersErrors(c *gc.C) { 635 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 636 637 _, err := apiMachine.WatchContainers(instance.NONE) 638 c.Assert(err, gc.ErrorMatches, `unsupported container type "none"`) 639 640 _, err = apiMachine.WatchContainers("") 641 c.Assert(err, gc.ErrorMatches, "container type must be specified") 642 } 643 644 func (s *provisionerSuite) TestWatchModelMachines(c *gc.C) { 645 w, err := s.provisioner.WatchModelMachines() 646 c.Assert(err, jc.ErrorIsNil) 647 wc := watchertest.NewStringsWatcherC(c, w) 648 defer wc.AssertStops() 649 650 // Initial event. 651 wc.AssertChange(s.machine.Id()) 652 653 // Add another 2 machines make sure they are detected. 654 _, err = s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 655 c.Assert(err, jc.ErrorIsNil) 656 otherMachine, err := s.State.AddMachine(state.UbuntuBase("12.10"), state.JobHostUnits) 657 c.Assert(err, jc.ErrorIsNil) 658 wc.AssertChange("1", "2") 659 660 // Change the lifecycle of last machine. 661 err = otherMachine.EnsureDead() 662 c.Assert(err, jc.ErrorIsNil) 663 wc.AssertChange("2") 664 665 // Add a container and make sure it's not detected. 666 template := state.MachineTemplate{ 667 Base: state.UbuntuBase("12.10"), 668 Jobs: []state.MachineJob{state.JobHostUnits}, 669 } 670 _, err = s.State.AddMachineInsideMachine(template, s.machine.Id(), instance.LXD) 671 c.Assert(err, jc.ErrorIsNil) 672 wc.AssertNoChange() 673 } 674 675 func (s *provisionerSuite) getManagerConfig(c *gc.C, typ instance.ContainerType) map[string]string { 676 args := params.ContainerManagerConfigParams{Type: typ} 677 result, err := s.provisioner.ContainerManagerConfig(args) 678 c.Assert(err, jc.ErrorIsNil) 679 return result.ManagerConfig 680 } 681 682 func (s *provisionerSuite) TestContainerManagerConfigKVM(c *gc.C) { 683 cfg := s.getManagerConfig(c, instance.KVM) 684 c.Assert(cfg, jc.DeepEquals, map[string]string{ 685 container.ConfigModelUUID: coretesting.ModelTag.Id(), 686 config.ContainerImageStreamKey: "released", 687 config.ContainerNetworkingMethod: config.ConfigDefaults()[config.ContainerNetworkingMethod].(string), 688 }) 689 } 690 691 func (s *provisionerSuite) TestContainerManagerConfigPermissive(c *gc.C) { 692 // ContainerManagerConfig is permissive of container types, and 693 // will just return the basic type-independent configuration. 694 cfg := s.getManagerConfig(c, "invalid") 695 c.Assert(cfg, jc.DeepEquals, map[string]string{ 696 container.ConfigModelUUID: coretesting.ModelTag.Id(), 697 config.ContainerImageStreamKey: "released", 698 config.ContainerNetworkingMethod: config.ConfigDefaults()[config.ContainerNetworkingMethod].(string), 699 }) 700 } 701 702 func (s *provisionerSuite) TestContainerConfig(c *gc.C) { 703 result, err := s.provisioner.ContainerConfig() 704 c.Assert(err, jc.ErrorIsNil) 705 c.Assert(result.ProviderType, gc.Equals, "dummy") 706 c.Assert(result.AuthorizedKeys, gc.Equals, s.Environ.Config().AuthorizedKeys()) 707 c.Assert(result.SSLHostnameVerification, jc.IsTrue) 708 } 709 710 func (s *provisionerSuite) TestSetSupportedContainers(c *gc.C) { 711 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 712 err := apiMachine.SetSupportedContainers(instance.LXD, instance.KVM) 713 c.Assert(err, jc.ErrorIsNil) 714 715 err = s.machine.Refresh() 716 c.Assert(err, jc.ErrorIsNil) 717 containers, ok := s.machine.SupportedContainers() 718 c.Assert(ok, jc.IsTrue) 719 c.Assert(containers, gc.DeepEquals, []instance.ContainerType{instance.LXD, instance.KVM}) 720 } 721 722 func (s *provisionerSuite) TestSupportedContainers(c *gc.C) { 723 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 724 err := apiMachine.SetSupportedContainers(instance.LXD, instance.KVM) 725 c.Assert(err, jc.ErrorIsNil) 726 727 containers, ok, err := apiMachine.SupportedContainers() 728 c.Assert(err, gc.IsNil) 729 c.Assert(ok, jc.IsTrue) 730 c.Assert(containers, gc.DeepEquals, []instance.ContainerType{instance.LXD, instance.KVM}) 731 } 732 733 func (s *provisionerSuite) TestSupportsNoContainers(c *gc.C) { 734 apiMachine := s.assertGetOneMachine(c, s.machine.MachineTag()) 735 err := apiMachine.SupportsNoContainers() 736 c.Assert(err, jc.ErrorIsNil) 737 738 err = s.machine.Refresh() 739 c.Assert(err, jc.ErrorIsNil) 740 containers, ok := s.machine.SupportedContainers() 741 c.Assert(ok, jc.IsTrue) 742 c.Assert(containers, gc.DeepEquals, []instance.ContainerType{}) 743 } 744 745 func (s *provisionerSuite) TestFindToolsNoArch(c *gc.C) { 746 s.testFindTools(c, false, nil, nil) 747 } 748 749 func (s *provisionerSuite) TestFindToolsArch(c *gc.C) { 750 s.testFindTools(c, true, nil, nil) 751 } 752 753 func (s *provisionerSuite) TestFindToolsAPIError(c *gc.C) { 754 apiError := errors.New("everything's broken") 755 s.testFindTools(c, false, apiError, nil) 756 } 757 758 func (s *provisionerSuite) TestFindToolsLogicError(c *gc.C) { 759 logicError := errors.NotFoundf("tools") 760 s.testFindTools(c, false, nil, logicError) 761 } 762 763 func (s *provisionerSuite) testFindTools(c *gc.C, matchArch bool, apiError, logicError error) { 764 current := coretesting.CurrentVersion() 765 var toolsList = coretools.List{&coretools.Tools{Version: current}} 766 var called bool 767 var a string 768 if matchArch { 769 // if matchArch is true, this will be overwriten with the host's arch, otherwise 770 // leave a blank. 771 a = arch.HostArch() 772 } 773 774 provisioner.PatchFacadeCall(s, s.provisioner, func(request string, args, response interface{}) error { 775 called = true 776 c.Assert(request, gc.Equals, "FindTools") 777 expected := params.FindToolsParams{ 778 Number: jujuversion.Current, 779 OSType: "ubuntu", 780 Arch: a, 781 } 782 c.Assert(args, gc.Equals, expected) 783 result := response.(*params.FindToolsResult) 784 result.List = toolsList 785 if logicError != nil { 786 result.Error = apiservererrors.ServerError(logicError) 787 } 788 return apiError 789 }) 790 apiList, err := s.provisioner.FindTools(jujuversion.Current, "ubuntu", a) 791 c.Assert(called, jc.IsTrue) 792 if apiError != nil { 793 c.Assert(err, gc.Equals, apiError) 794 } else if logicError != nil { 795 c.Assert(err.Error(), gc.Equals, logicError.Error()) 796 } else { 797 c.Assert(err, jc.ErrorIsNil) 798 c.Assert(apiList, jc.SameContents, toolsList) 799 } 800 } 801 802 func (s *provisionerSuite) TestHostChangesForContainer(c *gc.C) { 803 // Create a machine, put it in "default" space with a single NIC. Create 804 // a container that is also in the "default" space, and request the 805 // HostChangesForContainer to see that it wants to bridge that NIC 806 space, err := s.State.AddSpace("default", corenetwork.Id("default"), nil, true) 807 c.Assert(err, jc.ErrorIsNil) 808 _, err = s.State.AddSubnet(corenetwork.SubnetInfo{ 809 CIDR: "10.0.0.0/24", 810 SpaceID: space.Id(), 811 }) 812 c.Assert(err, jc.ErrorIsNil) 813 err = s.machine.SetLinkLayerDevices( 814 state.LinkLayerDeviceArgs{ 815 Name: "ens3", 816 Type: corenetwork.EthernetDevice, 817 ParentName: "", 818 IsUp: true, 819 }, 820 ) 821 c.Assert(err, jc.ErrorIsNil) 822 err = s.machine.SetDevicesAddresses( 823 state.LinkLayerDeviceAddress{ 824 DeviceName: "ens3", 825 CIDRAddress: "10.0.0.10/24", 826 ConfigMethod: corenetwork.ConfigStatic, 827 }, 828 ) 829 c.Assert(err, jc.ErrorIsNil) 830 containerTemplate := state.MachineTemplate{ 831 Base: state.UbuntuBase("12.10"), 832 Jobs: []state.MachineJob{state.JobHostUnits}, 833 } 834 machine, err := s.State.AddMachineInsideMachine(containerTemplate, s.machine.Id(), instance.LXD) 835 c.Assert(err, jc.ErrorIsNil) 836 837 changes, reconfigureDelay, err := s.provisioner.HostChangesForContainer(machine.MachineTag()) 838 c.Assert(err, gc.ErrorMatches, "dummy provider network config not supported.*") 839 c.Skip("can't test without network support") 840 c.Assert(err, jc.ErrorIsNil) 841 c.Check(changes, gc.DeepEquals, []network.DeviceToBridge{{ 842 BridgeName: "br-ens3", 843 DeviceName: "ens3", 844 }}) 845 c.Check(reconfigureDelay, gc.Equals, 0) 846 } 847 848 var _ = gc.Suite(&provisionerContainerSuite{}) 849 850 type provisionerContainerSuite struct { 851 containerTag names.MachineTag 852 } 853 854 func (s *provisionerContainerSuite) SetUpTest(c *gc.C) { 855 s.containerTag = names.NewMachineTag("0/lxd/0") 856 } 857 858 func (s *provisionerContainerSuite) TestPrepareContainerInterfaceInfoNoValues(c *gc.C) { 859 ctrl := gomock.NewController(c) 860 defer ctrl.Finish() 861 862 args := params.Entities{ 863 Entities: []params.Entity{{Tag: s.containerTag.String()}}, 864 } 865 results := params.MachineNetworkConfigResults{Results: []params.MachineNetworkConfigResult{{ 866 Config: nil, 867 Error: nil, 868 }}} 869 870 facadeCaller := apimocks.NewMockFacadeCaller(ctrl) 871 fExp := facadeCaller.EXPECT() 872 fExp.FacadeCall("PrepareContainerInterfaceInfo", args, gomock.Any()).SetArg(2, results).Return(nil) 873 874 provisionerApi := provisioner.NewStateFromFacade(facadeCaller) 875 876 networkInfo, err := provisionerApi.PrepareContainerInterfaceInfo(s.containerTag) 877 c.Assert(err, gc.IsNil) 878 c.Check(networkInfo, jc.DeepEquals, corenetwork.InterfaceInfos{}) 879 } 880 881 func (s *provisionerContainerSuite) TestPrepareContainerInterfaceInfoSingleNIC(c *gc.C) { 882 ctrl := gomock.NewController(c) 883 defer ctrl.Finish() 884 885 args := params.Entities{ 886 Entities: []params.Entity{{Tag: s.containerTag.String()}}, 887 } 888 results := params.MachineNetworkConfigResults{ 889 Results: []params.MachineNetworkConfigResult{{ 890 Config: []params.NetworkConfig{{ 891 DeviceIndex: 1, 892 MACAddress: "de:ad:be:ff:11:22", 893 CIDR: "192.168.0.5/24", 894 MTU: 9000, 895 ProviderId: "prov-id", 896 ProviderSubnetId: "prov-sub-id", 897 ProviderSpaceId: "prov-space-id", 898 ProviderAddressId: "prov-address-id", 899 ProviderVLANId: "prov-vlan-id", 900 VLANTag: 25, 901 InterfaceName: "eth5", 902 ParentInterfaceName: "parent#br-eth5", 903 InterfaceType: "ethernet", 904 Disabled: false, 905 NoAutoStart: false, 906 ConfigType: "static", 907 Address: "192.168.0.6", 908 DNSServers: []string{"8.8.8.8"}, 909 DNSSearchDomains: []string{"mydomain"}, 910 GatewayAddress: "192.168.0.1", 911 Routes: []params.NetworkRoute{{ 912 DestinationCIDR: "10.0.0.0/16", 913 GatewayIP: "192.168.0.1", 914 Metric: 55, 915 }}, 916 }}, 917 Error: nil, 918 }}, 919 } 920 921 facadeCaller := apimocks.NewMockFacadeCaller(ctrl) 922 fExp := facadeCaller.EXPECT() 923 fExp.FacadeCall("PrepareContainerInterfaceInfo", args, gomock.Any()).SetArg(2, results).Return(nil) 924 925 provisionerApi := provisioner.NewStateFromFacade(facadeCaller) 926 networkInfo, err := provisionerApi.PrepareContainerInterfaceInfo(s.containerTag) 927 c.Assert(err, gc.IsNil) 928 c.Check(networkInfo, jc.DeepEquals, corenetwork.InterfaceInfos{{ 929 DeviceIndex: 1, 930 MACAddress: "de:ad:be:ff:11:22", 931 MTU: 9000, 932 ProviderId: "prov-id", 933 ProviderSubnetId: "prov-sub-id", 934 ProviderSpaceId: "prov-space-id", 935 ProviderAddressId: "prov-address-id", 936 ProviderVLANId: "prov-vlan-id", 937 VLANTag: 25, 938 InterfaceName: "eth5", 939 ParentInterfaceName: "parent#br-eth5", 940 InterfaceType: "ethernet", 941 Disabled: false, 942 NoAutoStart: false, 943 ConfigType: "static", 944 Addresses: corenetwork.ProviderAddresses{corenetwork.NewMachineAddress( 945 "192.168.0.6", corenetwork.WithCIDR("192.168.0.5/24"), corenetwork.WithConfigType(corenetwork.ConfigStatic), 946 ).AsProviderAddress()}, 947 DNSServers: corenetwork.NewMachineAddresses([]string{"8.8.8.8"}).AsProviderAddresses(), 948 DNSSearchDomains: []string{"mydomain"}, 949 GatewayAddress: corenetwork.NewMachineAddress("192.168.0.1").AsProviderAddress(), 950 Routes: []corenetwork.Route{{ 951 DestinationCIDR: "10.0.0.0/16", 952 GatewayIP: "192.168.0.1", 953 Metric: 55, 954 }}, 955 }}) 956 } 957 958 func (s *provisionerContainerSuite) TestGetContainerProfileInfo(c *gc.C) { 959 ctrl := gomock.NewController(c) 960 defer ctrl.Finish() 961 962 args := params.Entities{ 963 Entities: []params.Entity{{Tag: s.containerTag.String()}}, 964 } 965 results := params.ContainerProfileResults{ 966 Results: []params.ContainerProfileResult{ 967 { 968 LXDProfiles: []*params.ContainerLXDProfile{ 969 { 970 Profile: params.CharmLXDProfile{ 971 Config: map[string]string{ 972 "security.nesting": "true", 973 "security.privileged": "true", 974 }, 975 }, 976 Name: "one", 977 }, 978 { 979 Profile: params.CharmLXDProfile{ 980 Devices: map[string]map[string]string{ 981 "bdisk": { 982 "source": "/dev/loop0", 983 "type": "unix-block", 984 }, 985 }, 986 }, 987 Name: "two", 988 }}, 989 Error: nil, 990 }}, 991 } 992 993 facadeCaller := apimocks.NewMockFacadeCaller(ctrl) 994 fExp := facadeCaller.EXPECT() 995 fExp.FacadeCall("GetContainerProfileInfo", args, gomock.Any()).SetArg(2, results).Return(nil) 996 997 provisionerApi := provisioner.NewStateFromFacade(facadeCaller) 998 999 obtainedResults, err := provisionerApi.GetContainerProfileInfo(s.containerTag) 1000 c.Assert(err, jc.ErrorIsNil) 1001 c.Assert(obtainedResults, gc.DeepEquals, []*provisioner.LXDProfileResult{ 1002 { 1003 Config: map[string]string{ 1004 "security.nesting": "true", 1005 "security.privileged": "true", 1006 }, 1007 Name: "one", 1008 }, 1009 { 1010 Devices: map[string]map[string]string{ 1011 "bdisk": { 1012 "source": "/dev/loop0", 1013 "type": "unix-block", 1014 }, 1015 }, 1016 Name: "two", 1017 }}) 1018 1019 }