github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/ec2/local_test.go (about) 1 // Copyright 2011, 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package ec2_test 5 6 import ( 7 "fmt" 8 "regexp" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/juju/errors" 14 jc "github.com/juju/testing/checkers" 15 "github.com/juju/utils" 16 "github.com/juju/utils/arch" 17 "github.com/juju/utils/clock" 18 "github.com/juju/utils/series" 19 "github.com/juju/utils/set" 20 "github.com/juju/utils/ssh" 21 "gopkg.in/amz.v3/aws" 22 amzec2 "gopkg.in/amz.v3/ec2" 23 "gopkg.in/amz.v3/ec2/ec2test" 24 "gopkg.in/amz.v3/s3/s3test" 25 gc "gopkg.in/check.v1" 26 "gopkg.in/juju/names.v2" 27 goyaml "gopkg.in/yaml.v2" 28 29 "github.com/juju/juju/cloud" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/bootstrap" 33 "github.com/juju/juju/environs/imagemetadata" 34 imagetesting "github.com/juju/juju/environs/imagemetadata/testing" 35 "github.com/juju/juju/environs/jujutest" 36 "github.com/juju/juju/environs/simplestreams" 37 sstesting "github.com/juju/juju/environs/simplestreams/testing" 38 "github.com/juju/juju/environs/tags" 39 envtesting "github.com/juju/juju/environs/testing" 40 "github.com/juju/juju/environs/tools" 41 "github.com/juju/juju/instance" 42 "github.com/juju/juju/juju/keys" 43 "github.com/juju/juju/juju/testing" 44 "github.com/juju/juju/jujuclient/jujuclienttesting" 45 "github.com/juju/juju/network" 46 "github.com/juju/juju/provider/common" 47 "github.com/juju/juju/provider/ec2" 48 "github.com/juju/juju/storage" 49 coretesting "github.com/juju/juju/testing" 50 jujuversion "github.com/juju/juju/version" 51 ) 52 53 var localConfigAttrs = coretesting.FakeConfig().Merge(coretesting.Attrs{ 54 "name": "sample", 55 "type": "ec2", 56 "agent-version": coretesting.FakeVersionNumber.String(), 57 }) 58 59 func registerLocalTests() { 60 // N.B. Make sure the region we use here 61 // has entries in the images/query txt files. 62 aws.Regions["test"] = aws.Region{ 63 Name: "test", 64 } 65 66 gc.Suite(&localServerSuite{}) 67 gc.Suite(&localLiveSuite{}) 68 gc.Suite(&localNonUSEastSuite{}) 69 } 70 71 // localLiveSuite runs tests from LiveTests using a fake 72 // EC2 server that runs within the test process itself. 73 type localLiveSuite struct { 74 LiveTests 75 srv localServer 76 restoreEC2Patching func() 77 } 78 79 func (t *localLiveSuite) SetUpSuite(c *gc.C) { 80 t.LiveTests.SetUpSuite(c) 81 t.Credential = cloud.NewCredential( 82 cloud.AccessKeyAuthType, 83 map[string]string{ 84 "access-key": "x", 85 "secret-key": "x", 86 }, 87 ) 88 t.CloudRegion = "test" 89 90 // Upload arches that ec2 supports; add to this 91 // as ec2 coverage expands. 92 t.UploadArches = []string{arch.AMD64, arch.I386} 93 t.TestConfig = localConfigAttrs 94 t.restoreEC2Patching = patchEC2ForTesting(c) 95 imagetesting.PatchOfficialDataSources(&t.BaseSuite.CleanupSuite, "test:") 96 t.BaseSuite.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 97 t.BaseSuite.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 98 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 99 t.srv.createRootDisks = true 100 t.srv.startServer(c) 101 } 102 103 func (t *localLiveSuite) TearDownSuite(c *gc.C) { 104 t.LiveTests.TearDownSuite(c) 105 t.srv.stopServer(c) 106 t.restoreEC2Patching() 107 } 108 109 // localServer represents a fake EC2 server running within 110 // the test process itself. 111 type localServer struct { 112 // createRootDisks is used to decide whether or not 113 // the ec2test server will create root disks for 114 // instances. 115 createRootDisks bool 116 117 client *amzec2.EC2 118 ec2srv *ec2test.Server 119 s3srv *s3test.Server 120 config *s3test.Config 121 122 defaultVPC *amzec2.VPC 123 zones []amzec2.AvailabilityZoneInfo 124 } 125 126 func (srv *localServer) startServer(c *gc.C) { 127 var err error 128 srv.ec2srv, err = ec2test.NewServer() 129 if err != nil { 130 c.Fatalf("cannot start ec2 test server: %v", err) 131 } 132 srv.ec2srv.SetCreateRootDisks(srv.createRootDisks) 133 srv.s3srv, err = s3test.NewServer(srv.config) 134 if err != nil { 135 c.Fatalf("cannot start s3 test server: %v", err) 136 } 137 aws.Regions["test"] = aws.Region{ 138 Name: "test", 139 EC2Endpoint: srv.ec2srv.URL(), 140 S3Endpoint: srv.s3srv.URL(), 141 S3LocationConstraint: true, 142 } 143 srv.addSpice(c) 144 145 region := aws.Regions["test"] 146 signer := aws.SignV4Factory(region.Name, "ec2") 147 srv.client = amzec2.New(aws.Auth{}, region, signer) 148 149 zones := make([]amzec2.AvailabilityZoneInfo, 3) 150 zones[0].Region = "test" 151 zones[0].Name = "test-available" 152 zones[0].State = "available" 153 zones[1].Region = "test" 154 zones[1].Name = "test-impaired" 155 zones[1].State = "impaired" 156 zones[2].Region = "test" 157 zones[2].Name = "test-unavailable" 158 zones[2].State = "unavailable" 159 srv.ec2srv.SetAvailabilityZones(zones) 160 srv.ec2srv.SetInitialInstanceState(ec2test.Pending) 161 srv.zones = zones 162 163 defaultVPC, err := srv.ec2srv.AddDefaultVPCAndSubnets() 164 c.Assert(err, jc.ErrorIsNil) 165 srv.defaultVPC = &defaultVPC 166 } 167 168 // addSpice adds some "spice" to the local server 169 // by adding state that may cause tests to fail. 170 func (srv *localServer) addSpice(c *gc.C) { 171 states := []amzec2.InstanceState{ 172 ec2test.ShuttingDown, 173 ec2test.Terminated, 174 ec2test.Stopped, 175 } 176 for _, state := range states { 177 srv.ec2srv.NewInstances(1, "m1.small", "ami-a7f539ce", state, nil) 178 } 179 } 180 181 func (srv *localServer) stopServer(c *gc.C) { 182 srv.ec2srv.Reset(false) 183 srv.ec2srv.Quit() 184 srv.s3srv.Quit() 185 // Clear out the region because the server address is 186 // no longer valid. 187 delete(aws.Regions, "test") 188 189 srv.defaultVPC = nil 190 } 191 192 // localServerSuite contains tests that run against a fake EC2 server 193 // running within the test process itself. These tests can test things that 194 // would be unreasonably slow or expensive to test on a live Amazon server. 195 // It starts a new local ec2test server for each test. The server is 196 // accessed by using the "test" region, which is changed to point to the 197 // network address of the local server. 198 type localServerSuite struct { 199 coretesting.BaseSuite 200 jujutest.Tests 201 srv localServer 202 restoreEC2Patching func() 203 } 204 205 func (t *localServerSuite) SetUpSuite(c *gc.C) { 206 t.BaseSuite.SetUpSuite(c) 207 t.Credential = cloud.NewCredential( 208 cloud.AccessKeyAuthType, 209 map[string]string{ 210 "access-key": "x", 211 "secret-key": "x", 212 }, 213 ) 214 t.CloudRegion = "test" 215 216 // Upload arches that ec2 supports; add to this 217 // as ec2 coverage expands. 218 t.UploadArches = []string{arch.AMD64, arch.I386} 219 t.TestConfig = localConfigAttrs 220 t.restoreEC2Patching = patchEC2ForTesting(c) 221 imagetesting.PatchOfficialDataSources(&t.BaseSuite.CleanupSuite, "test:") 222 t.BaseSuite.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 223 t.BaseSuite.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 224 t.BaseSuite.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 225 t.BaseSuite.PatchValue(&arch.HostArch, func() string { return arch.AMD64 }) 226 t.BaseSuite.PatchValue(&series.HostSeries, func() string { return series.LatestLts() }) 227 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 228 t.srv.createRootDisks = true 229 t.srv.startServer(c) 230 // TODO(jam) I don't understand why we shouldn't do this. 231 // t.Tests embeds the sstesting.TestDataSuite, but if we call this 232 // SetUpSuite, then all of the tests fail because they go to access 233 // "test:/streams/..." and it isn't found 234 // t.Tests.SetUpSuite(c) 235 } 236 237 func (t *localServerSuite) TearDownSuite(c *gc.C) { 238 t.restoreEC2Patching() 239 t.Tests.TearDownSuite(c) 240 t.BaseSuite.TearDownSuite(c) 241 } 242 243 func (t *localServerSuite) SetUpTest(c *gc.C) { 244 t.BaseSuite.SetUpTest(c) 245 t.srv.startServer(c) 246 t.Tests.SetUpTest(c) 247 } 248 249 func (t *localServerSuite) TearDownTest(c *gc.C) { 250 t.Tests.TearDownTest(c) 251 t.srv.stopServer(c) 252 t.BaseSuite.TearDownTest(c) 253 } 254 255 func (t *localServerSuite) prepareEnviron(c *gc.C) environs.NetworkingEnviron { 256 env := t.Prepare(c) 257 netenv, supported := environs.SupportsNetworking(env) 258 c.Assert(supported, jc.IsTrue) 259 return netenv 260 } 261 262 func (t *localServerSuite) TestPrepareForBootstrapWithInvalidVPCID(c *gc.C) { 263 badVPCIDConfig := coretesting.Attrs{"vpc-id": "bad"} 264 265 expectedError := `invalid EC2 provider config: vpc-id: "bad" is not a valid AWS VPC ID` 266 t.AssertPrepareFailsWithConfig(c, badVPCIDConfig, expectedError) 267 } 268 269 func (t *localServerSuite) TestPrepareForBootstrapWithUnknownVPCID(c *gc.C) { 270 unknownVPCIDConfig := coretesting.Attrs{"vpc-id": "vpc-unknown"} 271 272 expectedError := `Juju cannot use the given vpc-id for bootstrapping(.|\n)*Error details: VPC "vpc-unknown" not found` 273 err := t.AssertPrepareFailsWithConfig(c, unknownVPCIDConfig, expectedError) 274 c.Check(err, jc.Satisfies, ec2.IsVPCNotUsableError) 275 } 276 277 func (t *localServerSuite) TestPrepareForBootstrapWithNotRecommendedVPCID(c *gc.C) { 278 t.makeTestingDefaultVPCUnavailable(c) 279 notRecommendedVPCIDConfig := coretesting.Attrs{"vpc-id": t.srv.defaultVPC.Id} 280 281 expectedError := `The given vpc-id does not meet one or more(.|\n)*Error details: VPC has unexpected state "unavailable"` 282 err := t.AssertPrepareFailsWithConfig(c, notRecommendedVPCIDConfig, expectedError) 283 c.Check(err, jc.Satisfies, ec2.IsVPCNotRecommendedError) 284 } 285 286 func (t *localServerSuite) makeTestingDefaultVPCUnavailable(c *gc.C) { 287 // For simplicity, here the test server's default VPC is updated to change 288 // its state to unavailable, we just verify the behavior of a "not 289 // recommended VPC". 290 t.srv.defaultVPC.State = "unavailable" 291 err := t.srv.ec2srv.UpdateVPC(*t.srv.defaultVPC) 292 c.Assert(err, jc.ErrorIsNil) 293 } 294 295 func (t *localServerSuite) TestPrepareForBootstrapWithNotRecommendedButForcedVPCID(c *gc.C) { 296 t.makeTestingDefaultVPCUnavailable(c) 297 params := t.PrepareParams(c) 298 params.ModelConfig["vpc-id"] = t.srv.defaultVPC.Id 299 params.ModelConfig["vpc-id-force"] = true 300 301 t.prepareWithParamsAndBootstrapWithVPCID(c, params, t.srv.defaultVPC.Id) 302 } 303 304 func (t *localServerSuite) TestPrepareForBootstrapWithEmptyVPCID(c *gc.C) { 305 const emptyVPCID = "" 306 307 params := t.PrepareParams(c) 308 params.ModelConfig["vpc-id"] = emptyVPCID 309 310 t.prepareWithParamsAndBootstrapWithVPCID(c, params, emptyVPCID) 311 } 312 313 func (t *localServerSuite) prepareWithParamsAndBootstrapWithVPCID(c *gc.C, params bootstrap.PrepareParams, expectedVPCID string) { 314 env := t.PrepareWithParams(c, params) 315 unknownAttrs := env.Config().UnknownAttrs() 316 vpcID, ok := unknownAttrs["vpc-id"] 317 c.Check(vpcID, gc.Equals, expectedVPCID) 318 c.Check(ok, jc.IsTrue) 319 320 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 321 ControllerConfig: coretesting.FakeControllerConfig(), 322 AdminSecret: testing.AdminSecret, 323 CAPrivateKey: coretesting.CAKey, 324 }) 325 c.Assert(err, jc.ErrorIsNil) 326 } 327 328 func (t *localServerSuite) TestPrepareForBootstrapWithVPCIDNone(c *gc.C) { 329 params := t.PrepareParams(c) 330 params.ModelConfig["vpc-id"] = "none" 331 332 t.prepareWithParamsAndBootstrapWithVPCID(c, params, ec2.VPCIDNone) 333 } 334 335 func (t *localServerSuite) TestPrepareForBootstrapWithDefaultVPCID(c *gc.C) { 336 params := t.PrepareParams(c) 337 params.ModelConfig["vpc-id"] = t.srv.defaultVPC.Id 338 339 t.prepareWithParamsAndBootstrapWithVPCID(c, params, t.srv.defaultVPC.Id) 340 } 341 342 func (t *localServerSuite) TestSystemdBootstrapInstanceUserDataAndState(c *gc.C) { 343 env := t.Prepare(c) 344 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 345 ControllerConfig: coretesting.FakeControllerConfig(), 346 // TODO(redir): BBB: When we no longer support upstart based systems this can change to series.LatestLts() 347 BootstrapSeries: "xenial", 348 AdminSecret: testing.AdminSecret, 349 CAPrivateKey: coretesting.CAKey, 350 }) 351 c.Assert(err, jc.ErrorIsNil) 352 353 // check that ControllerInstances returns the id of the bootstrap machine. 354 instanceIds, err := env.ControllerInstances(t.ControllerUUID) 355 c.Assert(err, jc.ErrorIsNil) 356 c.Assert(instanceIds, gc.HasLen, 1) 357 358 insts, err := env.AllInstances() 359 c.Assert(err, jc.ErrorIsNil) 360 c.Assert(insts, gc.HasLen, 1) 361 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 362 363 // check that the user data is configured to and the machine and 364 // provisioning agents. check that the user data is configured to only 365 // configure authorized SSH keys and set the log output; everything else 366 // happens after the machine is brought up. 367 inst := t.srv.ec2srv.Instance(string(insts[0].Id())) 368 c.Assert(inst, gc.NotNil) 369 addresses, err := insts[0].Addresses() 370 c.Assert(err, jc.ErrorIsNil) 371 c.Assert(addresses, gc.Not(gc.HasLen), 0) 372 userData, err := utils.Gunzip(inst.UserData) 373 c.Assert(err, jc.ErrorIsNil) 374 c.Assert(string(userData), jc.YAMLEquals, map[interface{}]interface{}{ 375 "output": map[interface{}]interface{}{ 376 "all": "| tee -a /var/log/cloud-init-output.log", 377 }, 378 "users": []interface{}{ 379 map[interface{}]interface{}{ 380 "name": "ubuntu", 381 "lock_passwd": true, 382 "groups": []interface{}{"adm", "audio", 383 "cdrom", "dialout", "dip", "floppy", 384 "netdev", "plugdev", "sudo", "video"}, 385 "shell": "/bin/bash", 386 "sudo": []interface{}{"ALL=(ALL) NOPASSWD:ALL"}, 387 "ssh-authorized-keys": splitAuthKeys(env.Config().AuthorizedKeys()), 388 }, 389 }, 390 "runcmd": []interface{}{ 391 "set -xe", 392 "install -D -m 644 /dev/null '/etc/systemd/system/juju-clean-shutdown.service'", 393 "printf '%s\\n' '\n[Unit]\nDescription=Stop all network interfaces on shutdown\nDefaultDependencies=false\nAfter=final.target\n\n[Service]\nType=oneshot\nExecStart=/sbin/ifdown -a -v --force\nStandardOutput=tty\nStandardError=tty\n\n[Install]\nWantedBy=final.target\n' > '/etc/systemd/system/juju-clean-shutdown.service'", "/bin/systemctl enable '/etc/systemd/system/juju-clean-shutdown.service'", 394 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 395 "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", 396 }, 397 }) 398 399 // check that a new instance will be started with a machine agent 400 inst1, hc := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 401 c.Check(*hc.Arch, gc.Equals, "amd64") 402 c.Check(*hc.Mem, gc.Equals, uint64(3840)) 403 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 404 c.Assert(*hc.CpuPower, gc.Equals, uint64(300)) 405 inst = t.srv.ec2srv.Instance(string(inst1.Id())) 406 c.Assert(inst, gc.NotNil) 407 userData, err = utils.Gunzip(inst.UserData) 408 c.Assert(err, jc.ErrorIsNil) 409 c.Logf("second instance: UserData: %q", userData) 410 var userDataMap map[interface{}]interface{} 411 err = goyaml.Unmarshal(userData, &userDataMap) 412 c.Assert(err, jc.ErrorIsNil) 413 CheckPackage(c, userDataMap, "curl", true) 414 CheckPackage(c, userDataMap, "mongodb-server", false) 415 CheckScripts(c, userDataMap, "jujud bootstrap-state", false) 416 CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true) 417 // TODO check for provisioning agent 418 419 err = env.Destroy() 420 c.Assert(err, jc.ErrorIsNil) 421 422 _, err = env.ControllerInstances(t.ControllerUUID) 423 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 424 } 425 426 // TestUpstartBoostrapInstanceUserDataAndState is a test for legacy systems 427 // using upstart which will be around until trusty is no longer supported. 428 // TODO(redir): BBB: remove when trusty is no longer supported 429 func (t *localServerSuite) TestUpstartBootstrapInstanceUserDataAndState(c *gc.C) { 430 env := t.Prepare(c) 431 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 432 ControllerConfig: coretesting.FakeControllerConfig(), 433 BootstrapSeries: "trusty", 434 AdminSecret: testing.AdminSecret, 435 CAPrivateKey: coretesting.CAKey, 436 }) 437 c.Assert(err, jc.ErrorIsNil) 438 439 // check that ControllerInstances returns the id of the bootstrap machine. 440 instanceIds, err := env.ControllerInstances(t.ControllerUUID) 441 c.Assert(err, jc.ErrorIsNil) 442 c.Assert(instanceIds, gc.HasLen, 1) 443 444 insts, err := env.AllInstances() 445 c.Assert(err, jc.ErrorIsNil) 446 c.Assert(insts, gc.HasLen, 1) 447 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 448 449 // check that the user data is configured to and the machine and 450 // provisioning agents. check that the user data is configured to only 451 // configure authorized SSH keys and set the log output; everything else 452 // happens after the machine is brought up. 453 inst := t.srv.ec2srv.Instance(string(insts[0].Id())) 454 c.Assert(inst, gc.NotNil) 455 addresses, err := insts[0].Addresses() 456 c.Assert(err, jc.ErrorIsNil) 457 c.Assert(addresses, gc.Not(gc.HasLen), 0) 458 userData, err := utils.Gunzip(inst.UserData) 459 c.Assert(err, jc.ErrorIsNil) 460 c.Assert(string(userData), jc.YAMLEquals, map[interface{}]interface{}{ 461 "output": map[interface{}]interface{}{ 462 "all": "| tee -a /var/log/cloud-init-output.log", 463 }, 464 "users": []interface{}{ 465 map[interface{}]interface{}{ 466 "name": "ubuntu", 467 "lock_passwd": true, 468 "groups": []interface{}{"adm", "audio", 469 "cdrom", "dialout", "dip", "floppy", 470 "netdev", "plugdev", "sudo", "video"}, 471 "shell": "/bin/bash", 472 "sudo": []interface{}{"ALL=(ALL) NOPASSWD:ALL"}, 473 "ssh-authorized-keys": splitAuthKeys(env.Config().AuthorizedKeys()), 474 }, 475 }, 476 "runcmd": []interface{}{ 477 "set -xe", 478 "install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown.conf'", 479 "printf '%s\\n' '\nauthor \"Juju Team <juju@lists.ubuntu.com>\"\ndescription \"Stop all network interfaces on shutdown\"\nstart on runlevel [016]\ntask\nconsole output\n\nexec /sbin/ifdown -a -v --force\n' > '/etc/init/juju-clean-shutdown.conf'", 480 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 481 "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", 482 }, 483 }) 484 485 // check that a new instance will be started with a machine agent 486 inst1, hc := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 487 c.Check(*hc.Arch, gc.Equals, "amd64") 488 c.Check(*hc.Mem, gc.Equals, uint64(3840)) 489 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 490 c.Assert(*hc.CpuPower, gc.Equals, uint64(300)) 491 inst = t.srv.ec2srv.Instance(string(inst1.Id())) 492 c.Assert(inst, gc.NotNil) 493 userData, err = utils.Gunzip(inst.UserData) 494 c.Assert(err, jc.ErrorIsNil) 495 c.Logf("second instance: UserData: %q", userData) 496 var userDataMap map[interface{}]interface{} 497 err = goyaml.Unmarshal(userData, &userDataMap) 498 c.Assert(err, jc.ErrorIsNil) 499 CheckPackage(c, userDataMap, "curl", true) 500 CheckPackage(c, userDataMap, "mongodb-server", false) 501 CheckScripts(c, userDataMap, "jujud bootstrap-state", false) 502 CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true) 503 // TODO check for provisioning agent 504 505 err = env.Destroy() 506 c.Assert(err, jc.ErrorIsNil) 507 508 _, err = env.ControllerInstances(t.ControllerUUID) 509 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 510 } 511 512 func (t *localServerSuite) TestTerminateInstancesIgnoresNotFound(c *gc.C) { 513 env := t.Prepare(c) 514 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 515 ControllerConfig: coretesting.FakeControllerConfig(), 516 AdminSecret: testing.AdminSecret, 517 CAPrivateKey: coretesting.CAKey, 518 }) 519 c.Assert(err, jc.ErrorIsNil) 520 521 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 522 insts, err := env.AllInstances() 523 c.Assert(err, jc.ErrorIsNil) 524 idsToStop := make([]instance.Id, len(insts)+1) 525 for i, one := range insts { 526 idsToStop[i] = one.Id() 527 } 528 idsToStop[len(insts)] = instance.Id("i-am-not-found") 529 530 err = env.StopInstances(idsToStop...) 531 // NotFound should be ignored 532 c.Assert(err, jc.ErrorIsNil) 533 } 534 535 func (t *localServerSuite) TestDestroyErr(c *gc.C) { 536 env := t.prepareAndBootstrap(c) 537 538 msg := "terminate instances error" 539 t.BaseSuite.PatchValue(ec2.TerminateInstancesById, func(ec2inst *amzec2.EC2, ids ...instance.Id) (*amzec2.TerminateInstancesResp, error) { 540 return nil, errors.New(msg) 541 }) 542 543 err := env.Destroy() 544 c.Assert(errors.Cause(err).Error(), jc.Contains, msg) 545 } 546 547 func (t *localServerSuite) TestGetTerminatedInstances(c *gc.C) { 548 env := t.Prepare(c) 549 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 550 ControllerConfig: coretesting.FakeControllerConfig(), 551 AdminSecret: testing.AdminSecret, 552 CAPrivateKey: coretesting.CAKey, 553 }) 554 c.Assert(err, jc.ErrorIsNil) 555 556 // create another instance to terminate 557 inst1, _ := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 558 inst := t.srv.ec2srv.Instance(string(inst1.Id())) 559 c.Assert(inst, gc.NotNil) 560 t.BaseSuite.PatchValue(ec2.TerminateInstancesById, func(ec2inst *amzec2.EC2, ids ...instance.Id) (*amzec2.TerminateInstancesResp, error) { 561 // Terminate the one destined for termination and 562 // err out to ensure that one instance will be terminated, the other - not. 563 _, err = ec2inst.TerminateInstances([]string{string(inst1.Id())}) 564 c.Assert(err, jc.ErrorIsNil) 565 return nil, errors.New("terminate instances error") 566 }) 567 err = env.Destroy() 568 c.Assert(err, gc.NotNil) 569 570 terminated, err := ec2.TerminatedInstances(env) 571 c.Assert(err, jc.ErrorIsNil) 572 c.Assert(terminated, gc.HasLen, 1) 573 c.Assert(terminated[0].Id(), jc.DeepEquals, inst1.Id()) 574 } 575 576 func (t *localServerSuite) TestInstanceSecurityGroupsWitheInstanceStatusFilter(c *gc.C) { 577 env := t.prepareAndBootstrap(c) 578 579 insts, err := env.AllInstances() 580 c.Assert(err, jc.ErrorIsNil) 581 ids := make([]instance.Id, len(insts)) 582 for i, one := range insts { 583 ids[i] = one.Id() 584 } 585 586 groupsNoInstanceFilter, err := ec2.InstanceSecurityGroups(env, ids) 587 c.Assert(err, jc.ErrorIsNil) 588 // get all security groups for test instances 589 c.Assert(groupsNoInstanceFilter, gc.HasLen, 2) 590 591 groupsFilteredForTerminatedInstances, err := ec2.InstanceSecurityGroups(env, ids, "shutting-down", "terminated") 592 c.Assert(err, jc.ErrorIsNil) 593 // get all security groups for terminated test instances 594 c.Assert(groupsFilteredForTerminatedInstances, gc.HasLen, 0) 595 } 596 597 func (t *localServerSuite) TestDestroyControllerModelDeleteSecurityGroupInsistentlyError(c *gc.C) { 598 env := t.prepareAndBootstrap(c) 599 msg := "destroy security group error" 600 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, func( 601 ec2.SecurityGroupCleaner, amzec2.SecurityGroup, clock.Clock, 602 ) error { 603 return errors.New(msg) 604 }) 605 err := env.DestroyController(t.ControllerUUID) 606 c.Assert(err, gc.ErrorMatches, "destroying managed environs: cannot delete security group .*: "+msg) 607 } 608 609 func (t *localServerSuite) TestDestroyHostedModelDeleteSecurityGroupInsistentlyError(c *gc.C) { 610 env := t.prepareAndBootstrap(c) 611 hostedEnv, err := environs.New(environs.OpenParams{ 612 Cloud: t.CloudSpec(), 613 Config: env.Config(), 614 }) 615 c.Assert(err, jc.ErrorIsNil) 616 617 msg := "destroy security group error" 618 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, func( 619 ec2.SecurityGroupCleaner, amzec2.SecurityGroup, clock.Clock, 620 ) error { 621 return errors.New(msg) 622 }) 623 err = hostedEnv.Destroy() 624 c.Assert(err, gc.ErrorMatches, "cannot delete environment security groups: cannot delete default security group: "+msg) 625 } 626 627 func (t *localServerSuite) TestDestroyControllerDestroysHostedModelResources(c *gc.C) { 628 controllerEnv := t.prepareAndBootstrap(c) 629 630 // Create a hosted model environment with an instance and a volume. 631 hostedModelUUID := "7e386e08-cba7-44a4-a76e-7c1633584210" 632 t.srv.ec2srv.SetInitialInstanceState(ec2test.Running) 633 cfg, err := controllerEnv.Config().Apply(map[string]interface{}{ 634 "uuid": hostedModelUUID, 635 "firewall-mode": "global", 636 }) 637 c.Assert(err, jc.ErrorIsNil) 638 env, err := environs.New(environs.OpenParams{ 639 Cloud: t.CloudSpec(), 640 Config: cfg, 641 }) 642 c.Assert(err, jc.ErrorIsNil) 643 inst, _ := testing.AssertStartInstance(c, env, t.ControllerUUID, "0") 644 c.Assert(err, jc.ErrorIsNil) 645 ebsProvider, err := env.StorageProvider(ec2.EBS_ProviderType) 646 c.Assert(err, jc.ErrorIsNil) 647 vs, err := ebsProvider.VolumeSource(nil) 648 c.Assert(err, jc.ErrorIsNil) 649 volumeResults, err := vs.CreateVolumes([]storage.VolumeParams{{ 650 Tag: names.NewVolumeTag("0"), 651 Size: 1024, 652 Provider: ec2.EBS_ProviderType, 653 ResourceTags: map[string]string{ 654 tags.JujuController: t.ControllerUUID, 655 tags.JujuModel: hostedModelUUID, 656 }, 657 Attachment: &storage.VolumeAttachmentParams{ 658 AttachmentParams: storage.AttachmentParams{ 659 InstanceId: inst.Id(), 660 }, 661 }, 662 }}) 663 c.Assert(err, jc.ErrorIsNil) 664 c.Assert(volumeResults, gc.HasLen, 1) 665 c.Assert(volumeResults[0].Error, jc.ErrorIsNil) 666 667 assertInstances := func(expect ...instance.Id) { 668 insts, err := env.AllInstances() 669 c.Assert(err, jc.ErrorIsNil) 670 ids := make([]instance.Id, len(insts)) 671 for i, inst := range insts { 672 ids[i] = inst.Id() 673 } 674 c.Assert(ids, jc.SameContents, expect) 675 } 676 assertVolumes := func(expect ...string) { 677 volIds, err := vs.ListVolumes() 678 c.Assert(err, jc.ErrorIsNil) 679 c.Assert(volIds, jc.SameContents, expect) 680 } 681 assertGroups := func(expect ...string) { 682 groupsResp, err := t.srv.client.SecurityGroups(nil, nil) 683 c.Assert(err, jc.ErrorIsNil) 684 names := make([]string, len(groupsResp.Groups)) 685 for i, group := range groupsResp.Groups { 686 names[i] = group.Name 687 } 688 c.Assert(names, jc.SameContents, expect) 689 } 690 691 assertInstances(inst.Id()) 692 assertVolumes(volumeResults[0].Volume.VolumeId) 693 assertGroups( 694 "default", 695 "juju-"+controllerEnv.Config().UUID(), 696 "juju-"+controllerEnv.Config().UUID()+"-0", 697 "juju-"+hostedModelUUID, 698 "juju-"+hostedModelUUID+"-global", 699 ) 700 701 // Destroy the controller resources. This should destroy the hosted 702 // environment too. 703 err = controllerEnv.DestroyController(t.ControllerUUID) 704 c.Assert(err, jc.ErrorIsNil) 705 706 assertInstances() 707 assertVolumes() 708 assertGroups("default") 709 } 710 711 // splitAuthKeys splits the given authorized keys 712 // into the form expected to be found in the 713 // user data. 714 func splitAuthKeys(keys string) []interface{} { 715 slines := strings.FieldsFunc(keys, func(r rune) bool { 716 return r == '\n' 717 }) 718 var lines []interface{} 719 for _, line := range slines { 720 lines = append(lines, ssh.EnsureJujuComment(strings.TrimSpace(line))) 721 } 722 return lines 723 } 724 725 func (t *localServerSuite) TestInstanceStatus(c *gc.C) { 726 env := t.Prepare(c) 727 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 728 ControllerConfig: coretesting.FakeControllerConfig(), 729 AdminSecret: testing.AdminSecret, 730 CAPrivateKey: coretesting.CAKey, 731 }) 732 c.Assert(err, jc.ErrorIsNil) 733 t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated) 734 inst, _ := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 735 c.Assert(err, jc.ErrorIsNil) 736 c.Assert(inst.Status().Message, gc.Equals, "terminated") 737 } 738 739 func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 740 env := t.prepareAndBootstrap(c) 741 _, hc := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 742 c.Check(*hc.Arch, gc.Equals, "amd64") 743 c.Check(*hc.Mem, gc.Equals, uint64(3840)) 744 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 745 c.Assert(*hc.CpuPower, gc.Equals, uint64(300)) 746 } 747 748 func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { 749 inst, err := t.testStartInstanceAvailZone(c, "test-available") 750 c.Assert(err, jc.ErrorIsNil) 751 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 752 } 753 754 func (t *localServerSuite) TestStartInstanceAvailZoneImpaired(c *gc.C) { 755 _, err := t.testStartInstanceAvailZone(c, "test-impaired") 756 c.Assert(err, gc.ErrorMatches, `availability zone "test-impaired" is impaired`) 757 } 758 759 func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 760 _, err := t.testStartInstanceAvailZone(c, "test-unknown") 761 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 762 } 763 764 func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 765 env := t.prepareAndBootstrap(c) 766 767 params := environs.StartInstanceParams{ControllerUUID: t.ControllerUUID, Placement: "zone=" + zone} 768 result, err := testing.StartInstanceWithParams(env, "1", params) 769 if err != nil { 770 return nil, err 771 } 772 return result.Instance, nil 773 } 774 775 func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) { 776 var resultZones []amzec2.AvailabilityZoneInfo 777 var resultErr error 778 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 779 resp := &amzec2.AvailabilityZonesResp{ 780 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 781 } 782 return resp, resultErr 783 }) 784 env := t.Prepare(c).(common.ZonedEnviron) 785 786 resultErr = fmt.Errorf("failed to get availability zones") 787 zones, err := env.AvailabilityZones() 788 c.Assert(err, gc.Equals, resultErr) 789 c.Assert(zones, gc.IsNil) 790 791 resultErr = nil 792 resultZones = make([]amzec2.AvailabilityZoneInfo, 1) 793 resultZones[0].Name = "whatever" 794 zones, err = env.AvailabilityZones() 795 c.Assert(err, jc.ErrorIsNil) 796 c.Assert(zones, gc.HasLen, 1) 797 c.Assert(zones[0].Name(), gc.Equals, "whatever") 798 799 // A successful result is cached, currently for the lifetime 800 // of the Environ. This will change if/when we have long-lived 801 // Environs to cut down repeated IaaS requests. 802 resultErr = fmt.Errorf("failed to get availability zones") 803 resultZones[0].Name = "andever" 804 zones, err = env.AvailabilityZones() 805 c.Assert(err, jc.ErrorIsNil) 806 c.Assert(zones, gc.HasLen, 1) 807 c.Assert(zones[0].Name(), gc.Equals, "whatever") 808 } 809 810 func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { 811 var resultZones []amzec2.AvailabilityZoneInfo 812 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 813 resp := &amzec2.AvailabilityZonesResp{ 814 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 815 } 816 return resp, nil 817 }) 818 env := t.Prepare(c).(common.ZonedEnviron) 819 resultZones = make([]amzec2.AvailabilityZoneInfo, 2) 820 resultZones[0].Name = "az1" 821 resultZones[1].Name = "az2" 822 resultZones[0].State = "available" 823 resultZones[1].State = "impaired" 824 zones, err := env.AvailabilityZones() 825 c.Assert(err, jc.ErrorIsNil) 826 c.Assert(zones, gc.HasLen, 2) 827 c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) 828 c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) 829 c.Assert(zones[0].Available(), jc.IsTrue) 830 c.Assert(zones[1].Available(), jc.IsFalse) 831 } 832 833 type mockAvailabilityZoneAllocations struct { 834 group []instance.Id // input param 835 result []common.AvailabilityZoneInstances 836 err error 837 } 838 839 func (t *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 840 e common.ZonedEnviron, group []instance.Id, 841 ) ([]common.AvailabilityZoneInstances, error) { 842 t.group = group 843 return t.result, t.err 844 } 845 846 func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) { 847 env := t.prepareAndBootstrap(c) 848 849 mock := mockAvailabilityZoneAllocations{ 850 result: []common.AvailabilityZoneInstances{{ZoneName: "az1"}}, 851 } 852 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 853 854 // no distribution group specified 855 testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 856 c.Assert(mock.group, gc.HasLen, 0) 857 858 // distribution group specified: ensure it's passed through to AvailabilityZone. 859 expectedInstances := []instance.Id{"i-0", "i-1"} 860 params := environs.StartInstanceParams{ 861 ControllerUUID: t.ControllerUUID, 862 DistributionGroup: func() ([]instance.Id, error) { 863 return expectedInstances, nil 864 }, 865 } 866 _, err := testing.StartInstanceWithParams(env, "1", params) 867 c.Assert(err, jc.ErrorIsNil) 868 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 869 } 870 871 func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) { 872 env := t.prepareAndBootstrap(c) 873 874 mock := mockAvailabilityZoneAllocations{ 875 err: fmt.Errorf("AvailabilityZoneAllocations failed"), 876 } 877 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 878 _, _, _, err := testing.StartInstance(env, t.ControllerUUID, "1") 879 c.Assert(errors.Cause(err), gc.Equals, mock.err) 880 881 mock.err = nil 882 dgErr := fmt.Errorf("DistributionGroup failed") 883 params := environs.StartInstanceParams{ 884 ControllerUUID: t.ControllerUUID, 885 DistributionGroup: func() ([]instance.Id, error) { 886 return nil, dgErr 887 }, 888 } 889 _, err = testing.StartInstanceWithParams(env, "1", params) 890 c.Assert(errors.Cause(err), gc.Equals, dgErr) 891 } 892 893 func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) { 894 env := t.prepareAndBootstrap(c) 895 896 // test-available is the only available AZ, so AvailabilityZoneAllocations 897 // is guaranteed to return that. 898 inst, _ := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 899 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 900 } 901 902 var azConstrainedErr = &amzec2.Error{ 903 Code: "Unsupported", 904 Message: "The requested Availability Zone is currently constrained etc.", 905 } 906 907 var azVolumeTypeNotAvailableInZoneErr = &amzec2.Error{ 908 Code: "VolumeTypeNotAvailableInZone", 909 Message: "blah blah", 910 } 911 912 var azInsufficientInstanceCapacityErr = &amzec2.Error{ 913 Code: "InsufficientInstanceCapacity", 914 Message: "We currently do not have sufficient m1.small capacity in the " + 915 "Availability Zone you requested (us-east-1d). Our system will " + 916 "be working on provisioning additional capacity. You can currently get m1.small " + 917 "capacity by not specifying an Availability Zone in your request or choosing " + 918 "us-east-1c, us-east-1a.", 919 } 920 921 var azNoDefaultSubnetErr = &amzec2.Error{ 922 Code: "InvalidInput", 923 Message: "No default subnet for availability zone: ''us-east-1e''.", 924 } 925 926 func (t *localServerSuite) TestStartInstanceAvailZoneAllConstrained(c *gc.C) { 927 t.testStartInstanceAvailZoneAllConstrained(c, azConstrainedErr) 928 } 929 930 func (t *localServerSuite) TestStartInstanceVolumeTypeNotAvailable(c *gc.C) { 931 t.testStartInstanceAvailZoneAllConstrained(c, azVolumeTypeNotAvailableInZoneErr) 932 } 933 934 func (t *localServerSuite) TestStartInstanceAvailZoneAllInsufficientInstanceCapacity(c *gc.C) { 935 t.testStartInstanceAvailZoneAllConstrained(c, azInsufficientInstanceCapacityErr) 936 } 937 938 func (t *localServerSuite) TestStartInstanceAvailZoneAllNoDefaultSubnet(c *gc.C) { 939 t.testStartInstanceAvailZoneAllConstrained(c, azNoDefaultSubnetErr) 940 } 941 942 func (t *localServerSuite) testStartInstanceAvailZoneAllConstrained(c *gc.C, runInstancesError *amzec2.Error) { 943 env := t.prepareAndBootstrap(c) 944 945 mock := mockAvailabilityZoneAllocations{ 946 result: []common.AvailabilityZoneInstances{ 947 {ZoneName: "az1"}, {ZoneName: "az2"}, 948 }, 949 } 950 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 951 952 var azArgs []string 953 t.PatchValue(ec2.RunInstances, func(e *amzec2.EC2, ri *amzec2.RunInstances) (*amzec2.RunInstancesResp, error) { 954 azArgs = append(azArgs, ri.AvailZone) 955 return nil, runInstancesError 956 }) 957 _, _, _, err := testing.StartInstance(env, t.ControllerUUID, "1") 958 c.Assert(err, gc.ErrorMatches, fmt.Sprintf( 959 "cannot run instances: %s \\(%s\\)", 960 regexp.QuoteMeta(runInstancesError.Message), 961 runInstancesError.Code, 962 )) 963 c.Assert(azArgs, gc.DeepEquals, []string{"az1", "az2"}) 964 } 965 966 // addTestingSubnets adds a testing default VPC with 3 subnets in the EC2 test 967 // server: 2 of the subnets are in the "test-available" AZ, the remaining - in 968 // "test-unavailable". Returns a slice with the IDs of the created subnets. 969 func (t *localServerSuite) addTestingSubnets(c *gc.C) []network.Id { 970 vpc := t.srv.ec2srv.AddVPC(amzec2.VPC{ 971 CIDRBlock: "0.1.0.0/16", 972 IsDefault: true, 973 }) 974 results := make([]network.Id, 3) 975 sub1, err := t.srv.ec2srv.AddSubnet(amzec2.Subnet{ 976 VPCId: vpc.Id, 977 CIDRBlock: "0.1.2.0/24", 978 AvailZone: "test-available", 979 DefaultForAZ: true, 980 }) 981 c.Assert(err, jc.ErrorIsNil) 982 results[0] = network.Id(sub1.Id) 983 sub2, err := t.srv.ec2srv.AddSubnet(amzec2.Subnet{ 984 VPCId: vpc.Id, 985 CIDRBlock: "0.1.3.0/24", 986 AvailZone: "test-available", 987 }) 988 c.Assert(err, jc.ErrorIsNil) 989 results[1] = network.Id(sub2.Id) 990 sub3, err := t.srv.ec2srv.AddSubnet(amzec2.Subnet{ 991 VPCId: vpc.Id, 992 CIDRBlock: "0.1.4.0/24", 993 AvailZone: "test-unavailable", 994 DefaultForAZ: true, 995 }) 996 c.Assert(err, jc.ErrorIsNil) 997 results[2] = network.Id(sub3.Id) 998 return results 999 } 1000 1001 func (t *localServerSuite) prepareAndBootstrap(c *gc.C) environs.Environ { 1002 env := t.Prepare(c) 1003 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 1004 ControllerConfig: coretesting.FakeControllerConfig(), 1005 AdminSecret: testing.AdminSecret, 1006 CAPrivateKey: coretesting.CAKey, 1007 }) 1008 c.Assert(err, jc.ErrorIsNil) 1009 return env 1010 } 1011 1012 func (t *localServerSuite) TestSpaceConstraintsSpaceNotInPlacementZone(c *gc.C) { 1013 c.Skip("temporarily disabled") 1014 env := t.prepareAndBootstrap(c) 1015 subIDs := t.addTestingSubnets(c) 1016 1017 // Expect an error because zone test-available isn't in SubnetsToZones 1018 params := environs.StartInstanceParams{ 1019 ControllerUUID: t.ControllerUUID, 1020 Placement: "zone=test-available", 1021 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 1022 SubnetsToZones: map[network.Id][]string{ 1023 subIDs[0]: []string{"zone2"}, 1024 subIDs[1]: []string{"zone3"}, 1025 subIDs[2]: []string{"zone4"}, 1026 }, 1027 } 1028 _, err := testing.StartInstanceWithParams(env, "1", params) 1029 c.Assert(err, gc.ErrorMatches, `unable to resolve constraints: space and/or subnet unavailable in zones \[test-available\]`) 1030 } 1031 1032 func (t *localServerSuite) TestSpaceConstraintsSpaceInPlacementZone(c *gc.C) { 1033 env := t.prepareAndBootstrap(c) 1034 subIDs := t.addTestingSubnets(c) 1035 1036 // Should work - test-available is in SubnetsToZones and in myspace. 1037 params := environs.StartInstanceParams{ 1038 ControllerUUID: t.ControllerUUID, 1039 Placement: "zone=test-available", 1040 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 1041 SubnetsToZones: map[network.Id][]string{ 1042 subIDs[0]: []string{"test-available"}, 1043 subIDs[1]: []string{"zone3"}, 1044 }, 1045 } 1046 _, err := testing.StartInstanceWithParams(env, "1", params) 1047 c.Assert(err, jc.ErrorIsNil) 1048 } 1049 1050 func (t *localServerSuite) TestSpaceConstraintsNoPlacement(c *gc.C) { 1051 env := t.prepareAndBootstrap(c) 1052 subIDs := t.addTestingSubnets(c) 1053 1054 // Shoule work because zone is not specified so we can resolve the constraints 1055 params := environs.StartInstanceParams{ 1056 ControllerUUID: t.ControllerUUID, 1057 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 1058 SubnetsToZones: map[network.Id][]string{ 1059 subIDs[0]: []string{"test-available"}, 1060 subIDs[1]: []string{"zone3"}, 1061 }, 1062 } 1063 _, err := testing.StartInstanceWithParams(env, "1", params) 1064 c.Assert(err, jc.ErrorIsNil) 1065 } 1066 1067 func (t *localServerSuite) TestSpaceConstraintsNoAvailableSubnets(c *gc.C) { 1068 c.Skip("temporarily disabled") 1069 1070 env := t.prepareAndBootstrap(c) 1071 subIDs := t.addTestingSubnets(c) 1072 1073 // We requested a space, but there are no subnets in SubnetsToZones, so we can't resolve 1074 // the constraints 1075 params := environs.StartInstanceParams{ 1076 ControllerUUID: t.ControllerUUID, 1077 Constraints: constraints.MustParse("spaces=aaaaaaaaaa"), 1078 SubnetsToZones: map[network.Id][]string{ 1079 subIDs[0]: []string{""}, 1080 }, 1081 } 1082 _, err := testing.StartInstanceWithParams(env, "1", params) 1083 c.Assert(err, gc.ErrorMatches, `unable to resolve constraints: space and/or subnet unavailable in zones \[test-available\]`) 1084 } 1085 1086 func (t *localServerSuite) TestStartInstanceAvailZoneOneConstrained(c *gc.C) { 1087 t.testStartInstanceAvailZoneOneConstrained(c, azConstrainedErr) 1088 } 1089 1090 func (t *localServerSuite) TestStartInstanceAvailZoneOneInsufficientInstanceCapacity(c *gc.C) { 1091 t.testStartInstanceAvailZoneOneConstrained(c, azInsufficientInstanceCapacityErr) 1092 } 1093 1094 func (t *localServerSuite) TestStartInstanceAvailZoneOneNoDefaultSubnetErr(c *gc.C) { 1095 t.testStartInstanceAvailZoneOneConstrained(c, azNoDefaultSubnetErr) 1096 } 1097 1098 func (t *localServerSuite) testStartInstanceAvailZoneOneConstrained(c *gc.C, runInstancesError *amzec2.Error) { 1099 env := t.prepareAndBootstrap(c) 1100 1101 mock := mockAvailabilityZoneAllocations{ 1102 result: []common.AvailabilityZoneInstances{ 1103 {ZoneName: "az1"}, {ZoneName: "az2"}, 1104 }, 1105 } 1106 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1107 1108 // The first call to RunInstances fails with an error indicating the AZ 1109 // is constrained. The second attempt succeeds, and so allocates to az2. 1110 var azArgs []string 1111 realRunInstances := *ec2.RunInstances 1112 t.PatchValue(ec2.RunInstances, func(e *amzec2.EC2, ri *amzec2.RunInstances) (*amzec2.RunInstancesResp, error) { 1113 azArgs = append(azArgs, ri.AvailZone) 1114 if len(azArgs) == 1 { 1115 return nil, runInstancesError 1116 } 1117 return realRunInstances(e, ri) 1118 }) 1119 inst, hwc := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 1120 c.Assert(azArgs, gc.DeepEquals, []string{"az1", "az2"}) 1121 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "az2") 1122 c.Check(*hwc.AvailabilityZone, gc.Equals, "az2") 1123 } 1124 1125 func (t *localServerSuite) TestAddresses(c *gc.C) { 1126 env := t.prepareAndBootstrap(c) 1127 inst, _ := testing.AssertStartInstance(c, env, t.ControllerUUID, "1") 1128 addrs, err := inst.Addresses() 1129 c.Assert(err, jc.ErrorIsNil) 1130 // Expected values use Address type but really contain a regexp for 1131 // the value rather than a valid ip or hostname. 1132 expected := []network.Address{{ 1133 Value: "8.0.0.*", 1134 Type: network.IPv4Address, 1135 Scope: network.ScopePublic, 1136 }, { 1137 Value: "127.0.0.*", 1138 Type: network.IPv4Address, 1139 Scope: network.ScopeCloudLocal, 1140 }} 1141 c.Assert(addrs, gc.HasLen, len(expected)) 1142 for i, addr := range addrs { 1143 c.Check(addr.Value, gc.Matches, expected[i].Value) 1144 c.Check(addr.Type, gc.Equals, expected[i].Type) 1145 c.Check(addr.Scope, gc.Equals, expected[i].Scope) 1146 } 1147 } 1148 1149 func (t *localServerSuite) TestConstraintsValidatorUnsupported(c *gc.C) { 1150 env := t.Prepare(c) 1151 validator, err := env.ConstraintsValidator() 1152 c.Assert(err, jc.ErrorIsNil) 1153 cons := constraints.MustParse("arch=amd64 tags=foo virt-type=kvm") 1154 unsupported, err := validator.Validate(cons) 1155 c.Assert(err, jc.ErrorIsNil) 1156 c.Assert(unsupported, jc.SameContents, []string{"tags", "virt-type"}) 1157 } 1158 1159 func (t *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { 1160 env := t.Prepare(c) 1161 validator, err := env.ConstraintsValidator() 1162 c.Assert(err, jc.ErrorIsNil) 1163 cons := constraints.MustParse("instance-type=foo") 1164 _, err = validator.Validate(cons) 1165 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 1166 } 1167 1168 func (t *localServerSuite) TestConstraintsMerge(c *gc.C) { 1169 env := t.Prepare(c) 1170 validator, err := env.ConstraintsValidator() 1171 c.Assert(err, jc.ErrorIsNil) 1172 consA := constraints.MustParse("arch=amd64 mem=1G cpu-power=10 cores=2 tags=bar") 1173 consB := constraints.MustParse("arch=i386 instance-type=m1.small") 1174 cons, err := validator.Merge(consA, consB) 1175 c.Assert(err, jc.ErrorIsNil) 1176 c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=i386 instance-type=m1.small tags=bar")) 1177 } 1178 1179 func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 1180 env := t.Prepare(c) 1181 cons := constraints.MustParse("instance-type=m1.small root-disk=1G") 1182 placement := "" 1183 err := env.PrecheckInstance(series.LatestLts(), cons, placement) 1184 c.Assert(err, jc.ErrorIsNil) 1185 } 1186 1187 func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 1188 env := t.Prepare(c) 1189 cons := constraints.MustParse("instance-type=m1.invalid") 1190 placement := "" 1191 err := env.PrecheckInstance(series.LatestLts(), cons, placement) 1192 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`) 1193 } 1194 1195 func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) { 1196 env := t.Prepare(c) 1197 cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386") 1198 placement := "" 1199 err := env.PrecheckInstance(series.LatestLts(), cons, placement) 1200 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`) 1201 } 1202 1203 func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 1204 env := t.Prepare(c) 1205 placement := "zone=test-available" 1206 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) 1207 c.Assert(err, jc.ErrorIsNil) 1208 } 1209 1210 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { 1211 env := t.Prepare(c) 1212 placement := "zone=test-unavailable" 1213 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) 1214 c.Assert(err, jc.ErrorIsNil) 1215 } 1216 1217 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 1218 env := t.Prepare(c) 1219 placement := "zone=test-unknown" 1220 err := env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) 1221 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 1222 } 1223 1224 func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) { 1225 env := t.Prepare(c) 1226 params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test") 1227 c.Assert(err, jc.ErrorIsNil) 1228 params.Series = series.LatestLts() 1229 params.Endpoint = "https://ec2.endpoint.com" 1230 params.Sources, err = environs.ImageMetadataSources(env) 1231 c.Assert(err, jc.ErrorIsNil) 1232 image_ids, _, err := imagemetadata.ValidateImageMetadata(params) 1233 c.Assert(err, jc.ErrorIsNil) 1234 sort.Strings(image_ids) 1235 c.Assert(image_ids, gc.DeepEquals, []string{"ami-00000133", "ami-00000135", "ami-00000139"}) 1236 } 1237 1238 func (t *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 1239 t.PatchValue(&tools.DefaultBaseURL, "") 1240 1241 env := t.Prepare(c) 1242 sources, err := tools.GetMetadataSources(env) 1243 c.Assert(err, jc.ErrorIsNil) 1244 c.Assert(sources, gc.HasLen, 0) 1245 } 1246 1247 func (t *localServerSuite) TestSupportsNetworking(c *gc.C) { 1248 env := t.Prepare(c) 1249 _, supported := environs.SupportsNetworking(env) 1250 c.Assert(supported, jc.IsTrue) 1251 } 1252 1253 func (t *localServerSuite) setUpInstanceWithDefaultVpc(c *gc.C) (environs.NetworkingEnviron, instance.Id) { 1254 env := t.prepareEnviron(c) 1255 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ 1256 ControllerConfig: coretesting.FakeControllerConfig(), 1257 AdminSecret: testing.AdminSecret, 1258 CAPrivateKey: coretesting.CAKey, 1259 }) 1260 c.Assert(err, jc.ErrorIsNil) 1261 1262 instanceIds, err := env.ControllerInstances(t.ControllerUUID) 1263 c.Assert(err, jc.ErrorIsNil) 1264 return env, instanceIds[0] 1265 } 1266 1267 func (t *localServerSuite) TestNetworkInterfaces(c *gc.C) { 1268 env, instId := t.setUpInstanceWithDefaultVpc(c) 1269 interfaces, err := env.NetworkInterfaces(instId) 1270 c.Assert(err, jc.ErrorIsNil) 1271 1272 // The CIDR isn't predictable, but it is in the 10.10.x.0/24 format 1273 // The subnet ID is in the form "subnet-x", where x matches the same 1274 // number from the CIDR. The interfaces address is part of the CIDR. 1275 // For these reasons we check that the CIDR is in the expected format 1276 // and derive the expected values for ProviderSubnetId and Address. 1277 c.Assert(interfaces, gc.HasLen, 1) 1278 cidr := interfaces[0].CIDR 1279 re := regexp.MustCompile(`10\.10\.(\d+)\.0/24`) 1280 c.Assert(re.Match([]byte(cidr)), jc.IsTrue) 1281 index := re.FindStringSubmatch(cidr)[1] 1282 addr := fmt.Sprintf("10.10.%s.5", index) 1283 subnetId := network.Id("subnet-" + index) 1284 1285 // AvailabilityZones will either contain "test-available", 1286 // "test-impaired" or "test-unavailable" depending on which subnet is 1287 // picked. Any of these is fine. 1288 zones := interfaces[0].AvailabilityZones 1289 c.Assert(zones, gc.HasLen, 1) 1290 re = regexp.MustCompile("test-available|test-unavailable|test-impaired") 1291 c.Assert(re.Match([]byte(zones[0])), jc.IsTrue) 1292 1293 expectedInterfaces := []network.InterfaceInfo{{ 1294 DeviceIndex: 0, 1295 MACAddress: "20:01:60:cb:27:37", 1296 CIDR: cidr, 1297 ProviderId: "eni-0", 1298 ProviderSubnetId: subnetId, 1299 VLANTag: 0, 1300 InterfaceName: "unsupported0", 1301 Disabled: false, 1302 NoAutoStart: false, 1303 ConfigType: network.ConfigDHCP, 1304 InterfaceType: network.EthernetInterface, 1305 Address: network.NewScopedAddress(addr, network.ScopeCloudLocal), 1306 AvailabilityZones: zones, 1307 }} 1308 c.Assert(interfaces, jc.DeepEquals, expectedInterfaces) 1309 } 1310 1311 func (t *localServerSuite) TestSubnetsWithInstanceId(c *gc.C) { 1312 env, instId := t.setUpInstanceWithDefaultVpc(c) 1313 subnets, err := env.Subnets(instId, nil) 1314 c.Assert(err, jc.ErrorIsNil) 1315 c.Assert(subnets, gc.HasLen, 1) 1316 validateSubnets(c, subnets) 1317 1318 interfaces, err := env.NetworkInterfaces(instId) 1319 c.Assert(err, jc.ErrorIsNil) 1320 c.Assert(interfaces, gc.HasLen, 1) 1321 c.Assert(interfaces[0].ProviderSubnetId, gc.Equals, subnets[0].ProviderId) 1322 } 1323 1324 func (t *localServerSuite) TestSubnetsWithInstanceIdAndSubnetId(c *gc.C) { 1325 env, instId := t.setUpInstanceWithDefaultVpc(c) 1326 interfaces, err := env.NetworkInterfaces(instId) 1327 c.Assert(err, jc.ErrorIsNil) 1328 c.Assert(interfaces, gc.HasLen, 1) 1329 1330 subnets, err := env.Subnets(instId, []network.Id{interfaces[0].ProviderSubnetId}) 1331 c.Assert(err, jc.ErrorIsNil) 1332 c.Assert(subnets, gc.HasLen, 1) 1333 c.Assert(subnets[0].ProviderId, gc.Equals, interfaces[0].ProviderSubnetId) 1334 validateSubnets(c, subnets) 1335 } 1336 1337 func (t *localServerSuite) TestSubnetsWithInstanceIdMissingSubnet(c *gc.C) { 1338 env, instId := t.setUpInstanceWithDefaultVpc(c) 1339 subnets, err := env.Subnets(instId, []network.Id{"missing"}) 1340 c.Assert(err, gc.ErrorMatches, `failed to find the following subnet ids: \[missing\]`) 1341 c.Assert(subnets, gc.HasLen, 0) 1342 } 1343 1344 func validateSubnets(c *gc.C, subnets []network.SubnetInfo) { 1345 // These are defined in the test server for the testing default 1346 // VPC. 1347 defaultSubnets := []network.SubnetInfo{{ 1348 CIDR: "10.10.0.0/24", 1349 ProviderId: "subnet-0", 1350 VLANTag: 0, 1351 AvailabilityZones: []string{"test-available"}, 1352 }, { 1353 CIDR: "10.10.1.0/24", 1354 ProviderId: "subnet-1", 1355 VLANTag: 0, 1356 AvailabilityZones: []string{"test-impaired"}, 1357 }, { 1358 CIDR: "10.10.2.0/24", 1359 ProviderId: "subnet-2", 1360 VLANTag: 0, 1361 AvailabilityZones: []string{"test-unavailable"}, 1362 }} 1363 1364 re := regexp.MustCompile(`10\.10\.(\d+)\.0/24`) 1365 for _, subnet := range subnets { 1366 // We can find the expected data by looking at the CIDR. 1367 // subnets isn't in a predictable order due to the use of maps. 1368 c.Assert(re.Match([]byte(subnet.CIDR)), jc.IsTrue) 1369 index, err := strconv.Atoi(re.FindStringSubmatch(subnet.CIDR)[1]) 1370 c.Assert(err, jc.ErrorIsNil) 1371 // Don't know which AZ the subnet will end up in. 1372 defaultSubnets[index].AvailabilityZones = subnet.AvailabilityZones 1373 c.Assert(subnet, jc.DeepEquals, defaultSubnets[index]) 1374 } 1375 } 1376 1377 func (t *localServerSuite) TestSubnets(c *gc.C) { 1378 env, _ := t.setUpInstanceWithDefaultVpc(c) 1379 1380 subnets, err := env.Subnets(instance.UnknownId, []network.Id{"subnet-0"}) 1381 c.Assert(err, jc.ErrorIsNil) 1382 c.Assert(subnets, gc.HasLen, 1) 1383 validateSubnets(c, subnets) 1384 1385 subnets, err = env.Subnets(instance.UnknownId, nil) 1386 c.Assert(err, jc.ErrorIsNil) 1387 c.Assert(subnets, gc.HasLen, 3) 1388 validateSubnets(c, subnets) 1389 } 1390 1391 func (t *localServerSuite) TestSubnetsMissingSubnet(c *gc.C) { 1392 env, _ := t.setUpInstanceWithDefaultVpc(c) 1393 1394 _, err := env.Subnets("", []network.Id{"subnet-0", "Missing"}) 1395 c.Assert(err, gc.ErrorMatches, `failed to find the following subnet ids: \[Missing\]`) 1396 } 1397 1398 func (t *localServerSuite) TestInstanceTags(c *gc.C) { 1399 env := t.prepareAndBootstrap(c) 1400 1401 instances, err := env.AllInstances() 1402 c.Assert(err, jc.ErrorIsNil) 1403 c.Assert(instances, gc.HasLen, 1) 1404 1405 ec2Inst := ec2.InstanceEC2(instances[0]) 1406 c.Assert(ec2Inst.Tags, jc.SameContents, []amzec2.Tag{ 1407 {"Name", "juju-sample-machine-0"}, 1408 {"juju-model-uuid", coretesting.ModelTag.Id()}, 1409 {"juju-controller-uuid", t.ControllerUUID}, 1410 {"juju-is-controller", "true"}, 1411 }) 1412 } 1413 1414 func (t *localServerSuite) TestRootDiskTags(c *gc.C) { 1415 env := t.prepareAndBootstrap(c) 1416 1417 instances, err := env.AllInstances() 1418 c.Assert(err, jc.ErrorIsNil) 1419 c.Assert(instances, gc.HasLen, 1) 1420 1421 ec2conn := ec2.EnvironEC2(env) 1422 resp, err := ec2conn.Volumes(nil, nil) 1423 c.Assert(err, jc.ErrorIsNil) 1424 c.Assert(resp.Volumes, gc.Not(gc.HasLen), 0) 1425 1426 var found *amzec2.Volume 1427 for _, vol := range resp.Volumes { 1428 if len(vol.Tags) != 0 { 1429 found = &vol 1430 break 1431 } 1432 } 1433 c.Assert(found, gc.NotNil) 1434 c.Assert(found.Tags, jc.SameContents, []amzec2.Tag{ 1435 {"Name", "juju-sample-machine-0-root"}, 1436 {"juju-model-uuid", coretesting.ModelTag.Id()}, 1437 {"juju-controller-uuid", t.ControllerUUID}, 1438 }) 1439 } 1440 1441 // localNonUSEastSuite is similar to localServerSuite but the S3 mock server 1442 // behaves as if it is not in the us-east region. 1443 type localNonUSEastSuite struct { 1444 coretesting.BaseSuite 1445 sstesting.TestDataSuite 1446 1447 restoreEC2Patching func() 1448 srv localServer 1449 env environs.Environ 1450 } 1451 1452 func (t *localNonUSEastSuite) SetUpSuite(c *gc.C) { 1453 t.BaseSuite.SetUpSuite(c) 1454 t.TestDataSuite.SetUpSuite(c) 1455 1456 t.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 1457 t.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 1458 1459 t.restoreEC2Patching = patchEC2ForTesting(c) 1460 t.BaseSuite.PatchValue(ec2.DeleteSecurityGroupInsistently, deleteSecurityGroupForTestFunc) 1461 } 1462 1463 func (t *localNonUSEastSuite) TearDownSuite(c *gc.C) { 1464 t.restoreEC2Patching() 1465 t.TestDataSuite.TearDownSuite(c) 1466 t.BaseSuite.TearDownSuite(c) 1467 } 1468 1469 func (t *localNonUSEastSuite) SetUpTest(c *gc.C) { 1470 t.BaseSuite.SetUpTest(c) 1471 t.srv.config = &s3test.Config{ 1472 Send409Conflict: true, 1473 } 1474 t.srv.startServer(c) 1475 1476 credential := cloud.NewCredential( 1477 cloud.AccessKeyAuthType, 1478 map[string]string{ 1479 "access-key": "x", 1480 "secret-key": "x", 1481 }, 1482 ) 1483 1484 env, err := bootstrap.Prepare( 1485 envtesting.BootstrapContext(c), 1486 jujuclienttesting.NewMemStore(), 1487 bootstrap.PrepareParams{ 1488 ControllerConfig: coretesting.FakeControllerConfig(), 1489 ModelConfig: localConfigAttrs, 1490 ControllerName: localConfigAttrs["name"].(string), 1491 Cloud: environs.CloudSpec{ 1492 Type: "ec2", 1493 Region: "test", 1494 Credential: &credential, 1495 }, 1496 AdminSecret: testing.AdminSecret, 1497 }, 1498 ) 1499 c.Assert(err, jc.ErrorIsNil) 1500 t.env = env 1501 } 1502 1503 func (t *localNonUSEastSuite) TearDownTest(c *gc.C) { 1504 t.srv.stopServer(c) 1505 t.BaseSuite.TearDownTest(c) 1506 } 1507 1508 func patchEC2ForTesting(c *gc.C) func() { 1509 ec2.UseTestImageData(c, ec2.TestImagesData) 1510 ec2.UseTestInstanceTypeData(ec2.TestInstanceTypeCosts) 1511 ec2.UseTestRegionData(ec2.TestRegions) 1512 restoreTimeouts := envtesting.PatchAttemptStrategies(ec2.ShortAttempt, ec2.StorageAttempt) 1513 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 1514 return func() { 1515 restoreFinishBootstrap() 1516 restoreTimeouts() 1517 ec2.UseTestImageData(c, nil) 1518 ec2.UseTestInstanceTypeData(nil) 1519 ec2.UseTestRegionData(nil) 1520 } 1521 } 1522 1523 // If match is true, CheckScripts checks that at least one script started 1524 // by the cloudinit data matches the given regexp pattern, otherwise it 1525 // checks that no script matches. It's exported so it can be used by tests 1526 // defined in ec2_test. 1527 func CheckScripts(c *gc.C, userDataMap map[interface{}]interface{}, pattern string, match bool) { 1528 scripts0 := userDataMap["runcmd"] 1529 if scripts0 == nil { 1530 c.Errorf("cloudinit has no entry for runcmd") 1531 return 1532 } 1533 scripts := scripts0.([]interface{}) 1534 re := regexp.MustCompile(pattern) 1535 found := false 1536 for _, s0 := range scripts { 1537 s := s0.(string) 1538 if re.MatchString(s) { 1539 found = true 1540 } 1541 } 1542 switch { 1543 case match && !found: 1544 c.Errorf("script %q not found in %q", pattern, scripts) 1545 case !match && found: 1546 c.Errorf("script %q found but not expected in %q", pattern, scripts) 1547 } 1548 } 1549 1550 // CheckPackage checks that the cloudinit will or won't install the given 1551 // package, depending on the value of match. It's exported so it can be 1552 // used by tests defined outside the ec2 package. 1553 func CheckPackage(c *gc.C, userDataMap map[interface{}]interface{}, pkg string, match bool) { 1554 pkgs0 := userDataMap["packages"] 1555 if pkgs0 == nil { 1556 if match { 1557 c.Errorf("cloudinit has no entry for packages") 1558 } 1559 return 1560 } 1561 1562 pkgs := pkgs0.([]interface{}) 1563 1564 found := false 1565 for _, p0 := range pkgs { 1566 p := p0.(string) 1567 // p might be a space separate list of packages eg 'foo bar qed' so split them up 1568 manyPkgs := set.NewStrings(strings.Split(p, " ")...) 1569 hasPkg := manyPkgs.Contains(pkg) 1570 if p == pkg || hasPkg { 1571 found = true 1572 break 1573 } 1574 } 1575 switch { 1576 case match && !found: 1577 c.Errorf("package %q not found in %v", pkg, pkgs) 1578 case !match && found: 1579 c.Errorf("%q found but not expected in %v", pkg, pkgs) 1580 } 1581 }