github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/provisioner/provisioninginfo_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner_test 5 6 import ( 7 "fmt" 8 9 jc "github.com/juju/testing/checkers" 10 gc "gopkg.in/check.v1" 11 "gopkg.in/juju/names.v2" 12 13 "github.com/juju/juju/apiserver/facades/agent/provisioner" 14 "github.com/juju/juju/apiserver/params" 15 apiservertesting "github.com/juju/juju/apiserver/testing" 16 "github.com/juju/juju/core/constraints" 17 "github.com/juju/juju/environs/tags" 18 "github.com/juju/juju/juju/testing" 19 "github.com/juju/juju/provider/dummy" 20 "github.com/juju/juju/state" 21 "github.com/juju/juju/state/multiwatcher" 22 "github.com/juju/juju/storage" 23 "github.com/juju/juju/storage/poolmanager" 24 "github.com/juju/juju/storage/provider" 25 coretesting "github.com/juju/juju/testing" 26 ) 27 28 func (s *withoutControllerSuite) TestProvisioningInfoWithStorage(c *gc.C) { 29 pm := poolmanager.New(state.NewStateSettings(s.State), storage.ChainedProviderRegistry{ 30 dummy.StorageProviders(), 31 provider.CommonStorageProviders(), 32 }) 33 _, err := pm.Create("static-pool", "static", map[string]interface{}{"foo": "bar"}) 34 c.Assert(err, jc.ErrorIsNil) 35 36 cons := constraints.MustParse("cores=123 mem=8G") 37 template := state.MachineTemplate{ 38 Series: "quantal", 39 Jobs: []state.MachineJob{state.JobHostUnits}, 40 Constraints: cons, 41 Placement: "valid", 42 Volumes: []state.HostVolumeParams{ 43 {Volume: state.VolumeParams{Size: 1000, Pool: "static-pool"}}, 44 {Volume: state.VolumeParams{Size: 2000, Pool: "static-pool"}}, 45 }, 46 } 47 placementMachine, err := s.State.AddOneMachine(template) 48 c.Assert(err, jc.ErrorIsNil) 49 50 args := params.Entities{Entities: []params.Entity{ 51 {Tag: s.machines[0].Tag().String()}, 52 {Tag: placementMachine.Tag().String()}, 53 }} 54 result, err := s.provisioner.ProvisioningInfo(args) 55 c.Assert(err, jc.ErrorIsNil) 56 57 controllerCfg := coretesting.FakeControllerConfig() 58 // Dummy provider uses a random port, which is added to cfg used to create environment. 59 apiPort := dummy.APIPort(s.Environ.Provider()) 60 controllerCfg["api-port"] = apiPort 61 expected := params.ProvisioningInfoResults{ 62 Results: []params.ProvisioningInfoResult{ 63 {Result: ¶ms.ProvisioningInfo{ 64 ControllerConfig: controllerCfg, 65 Series: "quantal", 66 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 67 Tags: map[string]string{ 68 tags.JujuController: coretesting.ControllerTag.Id(), 69 tags.JujuModel: coretesting.ModelTag.Id(), 70 tags.JujuMachine: "controller-machine-0", 71 }, 72 }}, 73 {Result: ¶ms.ProvisioningInfo{ 74 ControllerConfig: controllerCfg, 75 Series: "quantal", 76 Constraints: template.Constraints, 77 Placement: template.Placement, 78 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 79 Tags: map[string]string{ 80 tags.JujuController: coretesting.ControllerTag.Id(), 81 tags.JujuModel: coretesting.ModelTag.Id(), 82 tags.JujuMachine: "controller-machine-5", 83 }, 84 Volumes: []params.VolumeParams{{ 85 VolumeTag: "volume-0", 86 Size: 1000, 87 Provider: "static", 88 Attributes: map[string]interface{}{"foo": "bar"}, 89 Tags: map[string]string{ 90 tags.JujuController: coretesting.ControllerTag.Id(), 91 tags.JujuModel: coretesting.ModelTag.Id(), 92 }, 93 Attachment: ¶ms.VolumeAttachmentParams{ 94 MachineTag: placementMachine.Tag().String(), 95 VolumeTag: "volume-0", 96 Provider: "static", 97 }, 98 }, { 99 VolumeTag: "volume-1", 100 Size: 2000, 101 Provider: "static", 102 Attributes: map[string]interface{}{"foo": "bar"}, 103 Tags: map[string]string{ 104 tags.JujuController: coretesting.ControllerTag.Id(), 105 tags.JujuModel: coretesting.ModelTag.Id(), 106 }, 107 Attachment: ¶ms.VolumeAttachmentParams{ 108 MachineTag: placementMachine.Tag().String(), 109 VolumeTag: "volume-1", 110 Provider: "static", 111 }, 112 }}, 113 }}, 114 }, 115 } 116 // The order of volumes is not predictable, so we make sure we 117 // compare the right ones. This only applies to Results[1] since 118 // it is the only result to contain volumes. 119 if expected.Results[1].Result.Volumes[0].VolumeTag != result.Results[1].Result.Volumes[0].VolumeTag { 120 vols := expected.Results[1].Result.Volumes 121 vols[0], vols[1] = vols[1], vols[0] 122 } 123 c.Assert(result, jc.DeepEquals, expected) 124 } 125 126 func (s *withoutControllerSuite) TestProvisioningInfoWithSingleNegativeAndPositiveSpaceInConstraints(c *gc.C) { 127 s.addSpacesAndSubnets(c) 128 129 cons := constraints.MustParse("cores=123 mem=8G spaces=^space1,space2") 130 template := state.MachineTemplate{ 131 Series: "quantal", 132 Jobs: []state.MachineJob{state.JobHostUnits}, 133 Constraints: cons, 134 Placement: "valid", 135 } 136 placementMachine, err := s.State.AddOneMachine(template) 137 c.Assert(err, jc.ErrorIsNil) 138 139 args := params.Entities{Entities: []params.Entity{ 140 {Tag: placementMachine.Tag().String()}, 141 }} 142 result, err := s.provisioner.ProvisioningInfo(args) 143 c.Assert(err, jc.ErrorIsNil) 144 145 controllerCfg := coretesting.FakeControllerConfig() 146 // Dummy provider uses a random port, which is added to cfg used to create environment. 147 apiPort := dummy.APIPort(s.Environ.Provider()) 148 controllerCfg["api-port"] = apiPort 149 expected := params.ProvisioningInfoResults{ 150 Results: []params.ProvisioningInfoResult{{ 151 Result: ¶ms.ProvisioningInfo{ 152 ControllerConfig: controllerCfg, 153 Series: "quantal", 154 Constraints: template.Constraints, 155 Placement: template.Placement, 156 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 157 Tags: map[string]string{ 158 tags.JujuController: coretesting.ControllerTag.Id(), 159 tags.JujuModel: coretesting.ModelTag.Id(), 160 tags.JujuMachine: "controller-machine-5", 161 }, 162 SubnetsToZones: map[string][]string{ 163 "subnet-1": {"zone1"}, 164 "subnet-2": {"zone2"}, 165 }, 166 }, 167 }}} 168 c.Assert(result, jc.DeepEquals, expected) 169 } 170 171 func (s *withoutControllerSuite) addSpacesAndSubnets(c *gc.C) { 172 // Add a couple of spaces. 173 _, err := s.State.AddSpace("space1", "first space id", nil, true) 174 c.Assert(err, jc.ErrorIsNil) 175 _, err = s.State.AddSpace("space2", "", nil, false) // no provider ID 176 c.Assert(err, jc.ErrorIsNil) 177 // Add 1 subnet into space1, and 2 into space2. 178 // Each subnet is in a matching zone (e.g "subnet-#" in "zone#"). 179 testing.AddSubnetsWithTemplate(c, s.State, 3, state.SubnetInfo{ 180 CIDR: "10.10.{{.}}.0/24", 181 ProviderId: "subnet-{{.}}", 182 AvailabilityZone: "zone{{.}}", 183 SpaceName: "{{if (eq . 0)}}space1{{else}}space2{{end}}", 184 VLANTag: 42, 185 }) 186 } 187 188 func (s *withoutControllerSuite) TestProvisioningInfoWithEndpointBindings(c *gc.C) { 189 s.addSpacesAndSubnets(c) 190 191 wordpressMachine, err := s.State.AddOneMachine(state.MachineTemplate{ 192 Series: "quantal", 193 Jobs: []state.MachineJob{state.JobHostUnits}, 194 }) 195 c.Assert(err, jc.ErrorIsNil) 196 197 // Use juju names for spaces in bindings, simulating ''juju deploy 198 // --bind...' was called. 199 bindings := map[string]string{ 200 "url": "space1", // has both name and provider ID 201 "db": "space2", // has only name, no provider ID 202 } 203 wordpressCharm := s.AddTestingCharm(c, "wordpress") 204 wordpressService := s.AddTestingApplicationWithBindings(c, "wordpress", wordpressCharm, bindings) 205 wordpressUnit, err := wordpressService.AddUnit(state.AddUnitParams{}) 206 c.Assert(err, jc.ErrorIsNil) 207 err = wordpressUnit.AssignToMachine(wordpressMachine) 208 c.Assert(err, jc.ErrorIsNil) 209 210 args := params.Entities{Entities: []params.Entity{ 211 {Tag: wordpressMachine.Tag().String()}, 212 }} 213 result, err := s.provisioner.ProvisioningInfo(args) 214 c.Assert(err, jc.ErrorIsNil) 215 216 controllerCfg := coretesting.FakeControllerConfig() 217 // Dummy provider uses a random port, which is added to cfg used to create environment. 218 apiPort := dummy.APIPort(s.Environ.Provider()) 219 controllerCfg["api-port"] = apiPort 220 expected := params.ProvisioningInfoResults{ 221 Results: []params.ProvisioningInfoResult{{ 222 Result: ¶ms.ProvisioningInfo{ 223 ControllerConfig: controllerCfg, 224 Series: "quantal", 225 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 226 Tags: map[string]string{ 227 tags.JujuController: coretesting.ControllerTag.Id(), 228 tags.JujuModel: coretesting.ModelTag.Id(), 229 tags.JujuMachine: "controller-machine-5", 230 tags.JujuUnitsDeployed: wordpressUnit.Name(), 231 }, 232 // Ensure space names are translated to provider IDs, where 233 // possible. 234 EndpointBindings: map[string]string{ 235 "db": "space2", // just name, no provider ID 236 "url": "first space id", // has provider ID 237 // We expect none of the unspecified bindings in the result. 238 }, 239 }, 240 }}} 241 c.Assert(result, jc.DeepEquals, expected) 242 } 243 244 func (s *withoutControllerSuite) TestProvisioningInfoWithUnsuitableSpacesConstraints(c *gc.C) { 245 // Add an empty space. 246 _, err := s.State.AddSpace("empty", "", nil, true) 247 c.Assert(err, jc.ErrorIsNil) 248 249 consEmptySpace := constraints.MustParse("cores=123 mem=8G spaces=empty") 250 consMissingSpace := constraints.MustParse("cores=123 mem=8G spaces=missing") 251 templates := []state.MachineTemplate{{ 252 Series: "quantal", 253 Jobs: []state.MachineJob{state.JobHostUnits}, 254 Constraints: consEmptySpace, 255 Placement: "valid", 256 }, { 257 Series: "quantal", 258 Jobs: []state.MachineJob{state.JobHostUnits}, 259 Constraints: consMissingSpace, 260 Placement: "valid", 261 }} 262 placementMachines, err := s.State.AddMachines(templates...) 263 c.Assert(err, jc.ErrorIsNil) 264 c.Assert(placementMachines, gc.HasLen, 2) 265 266 args := params.Entities{Entities: []params.Entity{ 267 {Tag: placementMachines[0].Tag().String()}, 268 {Tag: placementMachines[1].Tag().String()}, 269 }} 270 result, err := s.provisioner.ProvisioningInfo(args) 271 c.Assert(err, jc.ErrorIsNil) 272 273 expectedErrorEmptySpace := `cannot match subnets to zones: ` + 274 `cannot use space "empty" as deployment target: no subnets` 275 expectedErrorMissingSpace := `cannot match subnets to zones: ` + 276 `space "missing"` // " not found" will be appended by NotFoundError helper below. 277 expected := params.ProvisioningInfoResults{Results: []params.ProvisioningInfoResult{ 278 {Error: apiservertesting.ServerError(expectedErrorEmptySpace)}, 279 {Error: apiservertesting.NotFoundError(expectedErrorMissingSpace)}, 280 }} 281 c.Assert(result, jc.DeepEquals, expected) 282 } 283 284 func (s *withoutControllerSuite) TestProvisioningInfoWithLXDProfile(c *gc.C) { 285 profileMachine, err := s.State.AddOneMachine(state.MachineTemplate{ 286 Series: "quantal", 287 Jobs: []state.MachineJob{state.JobHostUnits}, 288 }) 289 c.Assert(err, jc.ErrorIsNil) 290 291 profileCharm := s.AddTestingCharm(c, "lxd-profile") 292 profileService := s.AddTestingApplication(c, "lxd-profile", profileCharm) 293 profileUnit, err := profileService.AddUnit(state.AddUnitParams{}) 294 c.Assert(err, jc.ErrorIsNil) 295 err = profileUnit.AssignToMachine(profileMachine) 296 c.Assert(err, jc.ErrorIsNil) 297 298 args := params.Entities{Entities: []params.Entity{ 299 {Tag: profileMachine.Tag().String()}, 300 }} 301 result, err := s.provisioner.ProvisioningInfo(args) 302 c.Assert(err, jc.ErrorIsNil) 303 304 controllerCfg := coretesting.FakeControllerConfig() 305 // Dummy provider uses a random port, which is added to cfg used to create environment. 306 apiPort := dummy.APIPort(s.Environ.Provider()) 307 controllerCfg["api-port"] = apiPort 308 309 pName := fmt.Sprintf("juju-%s-lxd-profile-0", profileMachine.ModelName()) 310 expected := params.ProvisioningInfoResults{ 311 Results: []params.ProvisioningInfoResult{{ 312 Result: ¶ms.ProvisioningInfo{ 313 ControllerConfig: controllerCfg, 314 Series: "quantal", 315 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 316 Tags: map[string]string{ 317 tags.JujuController: coretesting.ControllerTag.Id(), 318 tags.JujuModel: coretesting.ModelTag.Id(), 319 tags.JujuMachine: "controller-machine-5", 320 tags.JujuUnitsDeployed: profileUnit.Name(), 321 }, 322 EndpointBindings: map[string]string{}, 323 CharmLXDProfiles: []string{pName}, 324 }, 325 }}} 326 c.Assert(result, jc.DeepEquals, expected) 327 } 328 329 func (s *withoutControllerSuite) TestStorageProviderFallbackToType(c *gc.C) { 330 template := state.MachineTemplate{ 331 Series: "quantal", 332 Jobs: []state.MachineJob{state.JobHostUnits}, 333 Placement: "valid", 334 Volumes: []state.HostVolumeParams{ 335 {Volume: state.VolumeParams{Size: 1000, Pool: "loop"}}, 336 {Volume: state.VolumeParams{Size: 1000, Pool: "static"}}, 337 }, 338 } 339 placementMachine, err := s.State.AddOneMachine(template) 340 c.Assert(err, jc.ErrorIsNil) 341 342 args := params.Entities{Entities: []params.Entity{ 343 {Tag: placementMachine.Tag().String()}, 344 }} 345 result, err := s.provisioner.ProvisioningInfo(args) 346 c.Assert(err, jc.ErrorIsNil) 347 348 controllerCfg := coretesting.FakeControllerConfig() 349 // Dummy provider uses a random port, which is added to cfg used to create environment. 350 apiPort := dummy.APIPort(s.Environ.Provider()) 351 controllerCfg["api-port"] = apiPort 352 c.Assert(result, jc.DeepEquals, params.ProvisioningInfoResults{ 353 Results: []params.ProvisioningInfoResult{ 354 {Result: ¶ms.ProvisioningInfo{ 355 ControllerConfig: controllerCfg, 356 Series: "quantal", 357 Constraints: template.Constraints, 358 Placement: template.Placement, 359 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 360 Tags: map[string]string{ 361 tags.JujuController: coretesting.ControllerTag.Id(), 362 tags.JujuModel: coretesting.ModelTag.Id(), 363 tags.JujuMachine: "controller-machine-5", 364 }, 365 // volume-0 should not be included as it is not managed by 366 // the environ provider. 367 Volumes: []params.VolumeParams{{ 368 VolumeTag: "volume-1", 369 Size: 1000, 370 Provider: "static", 371 Attributes: nil, 372 Tags: map[string]string{ 373 tags.JujuController: coretesting.ControllerTag.Id(), 374 tags.JujuModel: coretesting.ModelTag.Id(), 375 }, 376 Attachment: ¶ms.VolumeAttachmentParams{ 377 MachineTag: placementMachine.Tag().String(), 378 VolumeTag: "volume-1", 379 Provider: "static", 380 }, 381 }}, 382 }}, 383 }, 384 }) 385 } 386 387 func (s *withoutControllerSuite) TestStorageProviderVolumes(c *gc.C) { 388 template := state.MachineTemplate{ 389 Series: "quantal", 390 Jobs: []state.MachineJob{state.JobHostUnits}, 391 Volumes: []state.HostVolumeParams{ 392 {Volume: state.VolumeParams{Size: 1000, Pool: "modelscoped"}}, 393 {Volume: state.VolumeParams{Size: 1000, Pool: "modelscoped"}}, 394 }, 395 } 396 machine, err := s.State.AddOneMachine(template) 397 c.Assert(err, jc.ErrorIsNil) 398 399 // Provision just one of the volumes, but neither of the attachments. 400 sb, err := state.NewStorageBackend(s.State) 401 c.Assert(err, jc.ErrorIsNil) 402 err = sb.SetVolumeInfo(names.NewVolumeTag("1"), state.VolumeInfo{ 403 Pool: "modelscoped", 404 Size: 1000, 405 VolumeId: "vol-ume", 406 Persistent: true, 407 }) 408 c.Assert(err, jc.ErrorIsNil) 409 410 args := params.Entities{Entities: []params.Entity{ 411 {Tag: machine.Tag().String()}, 412 }} 413 result, err := s.provisioner.ProvisioningInfo(args) 414 c.Assert(err, jc.ErrorIsNil) 415 c.Assert(result.Results[0].Error, gc.IsNil) 416 c.Assert(result.Results[0].Result, gc.NotNil) 417 418 // volume-0 should be created, as it hasn't yet been provisioned. 419 c.Assert(result.Results[0].Result.Volumes, jc.DeepEquals, []params.VolumeParams{{ 420 VolumeTag: "volume-0", 421 Size: 1000, 422 Provider: "modelscoped", 423 Tags: map[string]string{ 424 tags.JujuController: coretesting.ControllerTag.Id(), 425 tags.JujuModel: coretesting.ModelTag.Id(), 426 }, 427 Attachment: ¶ms.VolumeAttachmentParams{ 428 MachineTag: machine.Tag().String(), 429 VolumeTag: "volume-0", 430 Provider: "modelscoped", 431 }, 432 }}) 433 434 // volume-1 has already been provisioned, it just needs to be attached. 435 c.Assert(result.Results[0].Result.VolumeAttachments, jc.DeepEquals, []params.VolumeAttachmentParams{{ 436 MachineTag: machine.Tag().String(), 437 VolumeTag: "volume-1", 438 VolumeId: "vol-ume", 439 Provider: "modelscoped", 440 }}) 441 } 442 443 func (s *withoutControllerSuite) TestProviderInfoCloudInitUserData(c *gc.C) { 444 attrs := map[string]interface{}{"cloudinit-userdata": validCloudInitUserData} 445 err := s.Model.UpdateModelConfig(attrs, nil) 446 c.Assert(err, jc.ErrorIsNil) 447 template := state.MachineTemplate{ 448 Series: "quantal", 449 Jobs: []state.MachineJob{state.JobHostUnits}, 450 } 451 m, err := s.State.AddOneMachine(template) 452 c.Assert(err, jc.ErrorIsNil) 453 454 args := params.Entities{Entities: []params.Entity{ 455 {Tag: m.Tag().String()}, 456 }} 457 result, err := s.provisioner.ProvisioningInfo(args) 458 c.Assert(err, jc.ErrorIsNil) 459 c.Assert(result.Results[0].Result.CloudInitUserData, gc.DeepEquals, map[string]interface{}{ 460 "packages": []interface{}{"python-keystoneclient", "python-glanceclient"}, 461 "preruncmd": []interface{}{"mkdir /tmp/preruncmd", "mkdir /tmp/preruncmd2"}, 462 "postruncmd": []interface{}{"mkdir /tmp/postruncmd", "mkdir /tmp/postruncmd2"}, 463 "package_upgrade": false}) 464 } 465 466 var validCloudInitUserData = ` 467 packages: 468 - 'python-keystoneclient' 469 - 'python-glanceclient' 470 preruncmd: 471 - mkdir /tmp/preruncmd 472 - mkdir /tmp/preruncmd2 473 postruncmd: 474 - mkdir /tmp/postruncmd 475 - mkdir /tmp/postruncmd2 476 package_upgrade: false 477 `[1:] 478 479 func (s *withoutControllerSuite) TestProvisioningInfoPermissions(c *gc.C) { 480 // Login as a machine agent for machine 0. 481 anAuthorizer := s.authorizer 482 anAuthorizer.Controller = false 483 anAuthorizer.Tag = s.machines[0].Tag() 484 aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer) 485 c.Assert(err, jc.ErrorIsNil) 486 c.Assert(aProvisioner, gc.NotNil) 487 488 args := params.Entities{Entities: []params.Entity{ 489 {Tag: s.machines[0].Tag().String()}, 490 {Tag: s.machines[0].Tag().String() + "-lxd-0"}, 491 {Tag: "machine-42"}, 492 {Tag: s.machines[1].Tag().String()}, 493 {Tag: "application-bar"}, 494 }} 495 496 // Only machine 0 and containers therein can be accessed. 497 results, err := aProvisioner.ProvisioningInfo(args) 498 controllerCfg := coretesting.FakeControllerConfig() 499 // Dummy provider uses a random port, which is added to cfg used to create environment. 500 apiPort := dummy.APIPort(s.Environ.Provider()) 501 controllerCfg["api-port"] = apiPort 502 c.Assert(results, jc.DeepEquals, params.ProvisioningInfoResults{ 503 Results: []params.ProvisioningInfoResult{ 504 {Result: ¶ms.ProvisioningInfo{ 505 ControllerConfig: controllerCfg, 506 Series: "quantal", 507 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 508 Tags: map[string]string{ 509 tags.JujuController: coretesting.ControllerTag.Id(), 510 tags.JujuModel: coretesting.ModelTag.Id(), 511 tags.JujuMachine: "controller-machine-0", 512 }, 513 }}, 514 {Error: apiservertesting.NotFoundError("machine 0/lxd/0")}, 515 {Error: apiservertesting.ErrUnauthorized}, 516 {Error: apiservertesting.ErrUnauthorized}, 517 {Error: apiservertesting.ErrUnauthorized}, 518 }, 519 }) 520 }