github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 "net" 9 "regexp" 10 "sort" 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/set" 17 "gopkg.in/amz.v3/aws" 18 amzec2 "gopkg.in/amz.v3/ec2" 19 "gopkg.in/amz.v3/ec2/ec2test" 20 "gopkg.in/amz.v3/s3/s3test" 21 gc "gopkg.in/check.v1" 22 goyaml "gopkg.in/yaml.v1" 23 24 "github.com/juju/juju/constraints" 25 "github.com/juju/juju/environs" 26 "github.com/juju/juju/environs/bootstrap" 27 "github.com/juju/juju/environs/config" 28 "github.com/juju/juju/environs/configstore" 29 "github.com/juju/juju/environs/imagemetadata" 30 "github.com/juju/juju/environs/jujutest" 31 "github.com/juju/juju/environs/simplestreams" 32 envtesting "github.com/juju/juju/environs/testing" 33 "github.com/juju/juju/environs/tools" 34 "github.com/juju/juju/feature" 35 "github.com/juju/juju/instance" 36 "github.com/juju/juju/juju/arch" 37 "github.com/juju/juju/juju/testing" 38 "github.com/juju/juju/network" 39 "github.com/juju/juju/provider/common" 40 "github.com/juju/juju/provider/ec2" 41 coretesting "github.com/juju/juju/testing" 42 "github.com/juju/juju/utils/ssh" 43 "github.com/juju/juju/version" 44 ) 45 46 type ProviderSuite struct { 47 coretesting.BaseSuite 48 } 49 50 var _ = gc.Suite(&ProviderSuite{}) 51 52 var localConfigAttrs = coretesting.FakeConfig().Merge(coretesting.Attrs{ 53 "name": "sample", 54 "type": "ec2", 55 "region": "test", 56 "control-bucket": "test-bucket", 57 "access-key": "x", 58 "secret-key": "x", 59 "agent-version": version.Current.Number.String(), 60 }) 61 62 func registerLocalTests() { 63 // N.B. Make sure the region we use here 64 // has entries in the images/query txt files. 65 aws.Regions["test"] = aws.Region{ 66 Name: "test", 67 } 68 69 gc.Suite(&localServerSuite{}) 70 gc.Suite(&localLiveSuite{}) 71 gc.Suite(&localNonUSEastSuite{}) 72 } 73 74 // localLiveSuite runs tests from LiveTests using a fake 75 // EC2 server that runs within the test process itself. 76 type localLiveSuite struct { 77 LiveTests 78 srv localServer 79 restoreEC2Patching func() 80 } 81 82 func (t *localLiveSuite) SetUpSuite(c *gc.C) { 83 // Upload arches that ec2 supports; add to this 84 // as ec2 coverage expands. 85 t.UploadArches = []string{arch.AMD64, arch.I386} 86 t.TestConfig = localConfigAttrs 87 t.restoreEC2Patching = patchEC2ForTesting() 88 t.srv.startServer(c) 89 t.LiveTests.SetUpSuite(c) 90 } 91 92 func (t *localLiveSuite) TearDownSuite(c *gc.C) { 93 t.LiveTests.TearDownSuite(c) 94 t.srv.stopServer(c) 95 t.restoreEC2Patching() 96 } 97 98 // localServer represents a fake EC2 server running within 99 // the test process itself. 100 type localServer struct { 101 ec2srv *ec2test.Server 102 s3srv *s3test.Server 103 config *s3test.Config 104 } 105 106 func (srv *localServer) startServer(c *gc.C) { 107 var err error 108 srv.ec2srv, err = ec2test.NewServer() 109 if err != nil { 110 c.Fatalf("cannot start ec2 test server: %v", err) 111 } 112 srv.s3srv, err = s3test.NewServer(srv.config) 113 if err != nil { 114 c.Fatalf("cannot start s3 test server: %v", err) 115 } 116 aws.Regions["test"] = aws.Region{ 117 Name: "test", 118 EC2Endpoint: srv.ec2srv.URL(), 119 S3Endpoint: srv.s3srv.URL(), 120 S3LocationConstraint: true, 121 } 122 srv.addSpice(c) 123 124 zones := make([]amzec2.AvailabilityZoneInfo, 3) 125 zones[0].Region = "test" 126 zones[0].Name = "test-available" 127 zones[0].State = "available" 128 zones[1].Region = "test" 129 zones[1].Name = "test-impaired" 130 zones[1].State = "impaired" 131 zones[2].Region = "test" 132 zones[2].Name = "test-unavailable" 133 zones[2].State = "unavailable" 134 srv.ec2srv.SetAvailabilityZones(zones) 135 } 136 137 // addSpice adds some "spice" to the local server 138 // by adding state that may cause tests to fail. 139 func (srv *localServer) addSpice(c *gc.C) { 140 states := []amzec2.InstanceState{ 141 ec2test.ShuttingDown, 142 ec2test.Terminated, 143 ec2test.Stopped, 144 } 145 for _, state := range states { 146 srv.ec2srv.NewInstances(1, "m1.small", "ami-a7f539ce", state, nil) 147 } 148 } 149 150 func (srv *localServer) stopServer(c *gc.C) { 151 srv.ec2srv.Quit() 152 srv.s3srv.Quit() 153 // Clear out the region because the server address is 154 // no longer valid. 155 delete(aws.Regions, "test") 156 } 157 158 // localServerSuite contains tests that run against a fake EC2 server 159 // running within the test process itself. These tests can test things that 160 // would be unreasonably slow or expensive to test on a live Amazon server. 161 // It starts a new local ec2test server for each test. The server is 162 // accessed by using the "test" region, which is changed to point to the 163 // network address of the local server. 164 type localServerSuite struct { 165 coretesting.BaseSuite 166 jujutest.Tests 167 srv localServer 168 restoreEC2Patching func() 169 } 170 171 func (t *localServerSuite) SetUpSuite(c *gc.C) { 172 // Upload arches that ec2 supports; add to this 173 // as ec2 coverage expands. 174 t.UploadArches = []string{arch.AMD64, arch.I386} 175 t.TestConfig = localConfigAttrs 176 t.restoreEC2Patching = patchEC2ForTesting() 177 t.BaseSuite.SetUpSuite(c) 178 } 179 180 func (t *localServerSuite) TearDownSuite(c *gc.C) { 181 t.BaseSuite.TearDownSuite(c) 182 t.restoreEC2Patching() 183 } 184 185 func (t *localServerSuite) SetUpTest(c *gc.C) { 186 t.BaseSuite.SetUpTest(c) 187 t.SetFeatureFlags(feature.AddressAllocation) 188 t.srv.startServer(c) 189 t.Tests.SetUpTest(c) 190 t.PatchValue(&version.Current, version.Binary{ 191 Number: version.Current.Number, 192 Series: coretesting.FakeDefaultSeries, 193 Arch: arch.AMD64, 194 }) 195 } 196 197 func (t *localServerSuite) TearDownTest(c *gc.C) { 198 t.Tests.TearDownTest(c) 199 t.srv.stopServer(c) 200 t.BaseSuite.TearDownTest(c) 201 } 202 203 func (t *localServerSuite) prepareEnviron(c *gc.C) environs.NetworkingEnviron { 204 env := t.Prepare(c) 205 netenv, supported := environs.SupportsNetworking(env) 206 c.Assert(supported, jc.IsTrue) 207 return netenv 208 } 209 210 func (t *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { 211 env := t.Prepare(c) 212 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 213 c.Assert(err, jc.ErrorIsNil) 214 215 // check that StateServerInstances returns the id of the bootstrap machine. 216 instanceIds, err := env.StateServerInstances() 217 c.Assert(err, jc.ErrorIsNil) 218 c.Assert(instanceIds, gc.HasLen, 1) 219 220 insts, err := env.AllInstances() 221 c.Assert(err, jc.ErrorIsNil) 222 c.Assert(insts, gc.HasLen, 1) 223 c.Check(insts[0].Id(), gc.Equals, instanceIds[0]) 224 225 // check that the user data is configured to start zookeeper 226 // and the machine and provisioning agents. 227 // check that the user data is configured to only configure 228 // authorized SSH keys and set the log output; everything 229 // else happens after the machine is brought up. 230 inst := t.srv.ec2srv.Instance(string(insts[0].Id())) 231 c.Assert(inst, gc.NotNil) 232 addresses, err := insts[0].Addresses() 233 c.Assert(err, jc.ErrorIsNil) 234 c.Assert(addresses, gc.Not(gc.HasLen), 0) 235 userData, err := utils.Gunzip(inst.UserData) 236 c.Assert(err, jc.ErrorIsNil) 237 c.Logf("first instance: UserData: %q", userData) 238 var userDataMap map[interface{}]interface{} 239 err = goyaml.Unmarshal(userData, &userDataMap) 240 c.Assert(err, jc.ErrorIsNil) 241 c.Assert(userDataMap, jc.DeepEquals, map[interface{}]interface{}{ 242 "output": map[interface{}]interface{}{ 243 "all": "| tee -a /var/log/cloud-init-output.log", 244 }, 245 "ssh_authorized_keys": splitAuthKeys(env.Config().AuthorizedKeys()), 246 "runcmd": []interface{}{ 247 "set -xe", 248 "install -D -m 644 /dev/null '/etc/init/juju-clean-shutdown.conf'", 249 "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'", 250 "install -D -m 644 /dev/null '/var/lib/juju/nonce.txt'", 251 "printf '%s\\n' 'user-admin:bootstrap' > '/var/lib/juju/nonce.txt'", 252 }, 253 }) 254 255 // check that a new instance will be started with a machine agent 256 inst1, hc := testing.AssertStartInstance(c, env, "1") 257 c.Check(*hc.Arch, gc.Equals, "amd64") 258 c.Check(*hc.Mem, gc.Equals, uint64(1740)) 259 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 260 c.Assert(*hc.CpuPower, gc.Equals, uint64(100)) 261 inst = t.srv.ec2srv.Instance(string(inst1.Id())) 262 c.Assert(inst, gc.NotNil) 263 userData, err = utils.Gunzip(inst.UserData) 264 c.Assert(err, jc.ErrorIsNil) 265 c.Logf("second instance: UserData: %q", userData) 266 userDataMap = nil 267 err = goyaml.Unmarshal(userData, &userDataMap) 268 c.Assert(err, jc.ErrorIsNil) 269 CheckPackage(c, userDataMap, "curl", true) 270 CheckPackage(c, userDataMap, "mongodb-server", false) 271 CheckScripts(c, userDataMap, "jujud bootstrap-state", false) 272 CheckScripts(c, userDataMap, "/var/lib/juju/agents/machine-1/agent.conf", true) 273 // TODO check for provisioning agent 274 275 err = env.Destroy() 276 c.Assert(err, jc.ErrorIsNil) 277 278 _, err = env.StateServerInstances() 279 c.Assert(err, gc.Equals, environs.ErrNotBootstrapped) 280 } 281 282 // splitAuthKeys splits the given authorized keys 283 // into the form expected to be found in the 284 // user data. 285 func splitAuthKeys(keys string) []interface{} { 286 slines := strings.FieldsFunc(keys, func(r rune) bool { 287 return r == '\n' 288 }) 289 var lines []interface{} 290 for _, line := range slines { 291 lines = append(lines, ssh.EnsureJujuComment(strings.TrimSpace(line))) 292 } 293 return lines 294 } 295 296 func (t *localServerSuite) TestInstanceStatus(c *gc.C) { 297 env := t.Prepare(c) 298 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 299 c.Assert(err, jc.ErrorIsNil) 300 t.srv.ec2srv.SetInitialInstanceState(ec2test.Terminated) 301 inst, _ := testing.AssertStartInstance(c, env, "1") 302 c.Assert(err, jc.ErrorIsNil) 303 c.Assert(inst.Status(), gc.Equals, "terminated") 304 } 305 306 func (t *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 307 env := t.Prepare(c) 308 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 309 c.Assert(err, jc.ErrorIsNil) 310 _, hc := testing.AssertStartInstance(c, env, "1") 311 c.Check(*hc.Arch, gc.Equals, "amd64") 312 c.Check(*hc.Mem, gc.Equals, uint64(1740)) 313 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 314 c.Assert(*hc.CpuPower, gc.Equals, uint64(100)) 315 } 316 317 func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { 318 inst, err := t.testStartInstanceAvailZone(c, "test-available") 319 c.Assert(err, jc.ErrorIsNil) 320 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 321 } 322 323 func (t *localServerSuite) TestStartInstanceAvailZoneImpaired(c *gc.C) { 324 _, err := t.testStartInstanceAvailZone(c, "test-impaired") 325 c.Assert(err, gc.ErrorMatches, `availability zone "test-impaired" is impaired`) 326 } 327 328 func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 329 _, err := t.testStartInstanceAvailZone(c, "test-unknown") 330 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 331 } 332 333 func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 334 env := t.Prepare(c) 335 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 336 c.Assert(err, jc.ErrorIsNil) 337 338 params := environs.StartInstanceParams{Placement: "zone=" + zone} 339 result, err := testing.StartInstanceWithParams(env, "1", params, nil) 340 if err != nil { 341 return nil, err 342 } 343 return result.Instance, nil 344 } 345 346 func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) { 347 var resultZones []amzec2.AvailabilityZoneInfo 348 var resultErr error 349 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 350 resp := &amzec2.AvailabilityZonesResp{ 351 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 352 } 353 return resp, resultErr 354 }) 355 env := t.Prepare(c).(common.ZonedEnviron) 356 357 resultErr = fmt.Errorf("failed to get availability zones") 358 zones, err := env.AvailabilityZones() 359 c.Assert(err, gc.Equals, resultErr) 360 c.Assert(zones, gc.IsNil) 361 362 resultErr = nil 363 resultZones = make([]amzec2.AvailabilityZoneInfo, 1) 364 resultZones[0].Name = "whatever" 365 zones, err = env.AvailabilityZones() 366 c.Assert(err, jc.ErrorIsNil) 367 c.Assert(zones, gc.HasLen, 1) 368 c.Assert(zones[0].Name(), gc.Equals, "whatever") 369 370 // A successful result is cached, currently for the lifetime 371 // of the Environ. This will change if/when we have long-lived 372 // Environs to cut down repeated IaaS requests. 373 resultErr = fmt.Errorf("failed to get availability zones") 374 resultZones[0].Name = "andever" 375 zones, err = env.AvailabilityZones() 376 c.Assert(err, jc.ErrorIsNil) 377 c.Assert(zones, gc.HasLen, 1) 378 c.Assert(zones[0].Name(), gc.Equals, "whatever") 379 } 380 381 func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { 382 var resultZones []amzec2.AvailabilityZoneInfo 383 t.PatchValue(ec2.EC2AvailabilityZones, func(e *amzec2.EC2, f *amzec2.Filter) (*amzec2.AvailabilityZonesResp, error) { 384 resp := &amzec2.AvailabilityZonesResp{ 385 Zones: append([]amzec2.AvailabilityZoneInfo{}, resultZones...), 386 } 387 return resp, nil 388 }) 389 env := t.Prepare(c).(common.ZonedEnviron) 390 resultZones = make([]amzec2.AvailabilityZoneInfo, 2) 391 resultZones[0].Name = "az1" 392 resultZones[1].Name = "az2" 393 resultZones[0].State = "available" 394 resultZones[1].State = "impaired" 395 zones, err := env.AvailabilityZones() 396 c.Assert(err, jc.ErrorIsNil) 397 c.Assert(zones, gc.HasLen, 2) 398 c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) 399 c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) 400 c.Assert(zones[0].Available(), jc.IsTrue) 401 c.Assert(zones[1].Available(), jc.IsFalse) 402 } 403 404 type mockAvailabilityZoneAllocations struct { 405 group []instance.Id // input param 406 result []common.AvailabilityZoneInstances 407 err error 408 } 409 410 func (t *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 411 e common.ZonedEnviron, group []instance.Id, 412 ) ([]common.AvailabilityZoneInstances, error) { 413 t.group = group 414 return t.result, t.err 415 } 416 417 func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) { 418 env := t.Prepare(c) 419 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 420 c.Assert(err, jc.ErrorIsNil) 421 422 mock := mockAvailabilityZoneAllocations{ 423 result: []common.AvailabilityZoneInstances{{ZoneName: "az1"}}, 424 } 425 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 426 427 // no distribution group specified 428 testing.AssertStartInstance(c, env, "1") 429 c.Assert(mock.group, gc.HasLen, 0) 430 431 // distribution group specified: ensure it's passed through to AvailabilityZone. 432 expectedInstances := []instance.Id{"i-0", "i-1"} 433 params := environs.StartInstanceParams{ 434 DistributionGroup: func() ([]instance.Id, error) { 435 return expectedInstances, nil 436 }, 437 } 438 _, err = testing.StartInstanceWithParams(env, "1", params, nil) 439 c.Assert(err, jc.ErrorIsNil) 440 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 441 } 442 443 func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) { 444 env := t.Prepare(c) 445 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 446 c.Assert(err, jc.ErrorIsNil) 447 448 mock := mockAvailabilityZoneAllocations{ 449 err: fmt.Errorf("AvailabilityZoneAllocations failed"), 450 } 451 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 452 _, _, _, err = testing.StartInstance(env, "1") 453 c.Assert(errors.Cause(err), gc.Equals, mock.err) 454 455 mock.err = nil 456 dgErr := fmt.Errorf("DistributionGroup failed") 457 params := environs.StartInstanceParams{ 458 DistributionGroup: func() ([]instance.Id, error) { 459 return nil, dgErr 460 }, 461 } 462 _, err = testing.StartInstanceWithParams(env, "1", params, nil) 463 c.Assert(errors.Cause(err), gc.Equals, dgErr) 464 } 465 466 func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) { 467 env := t.Prepare(c) 468 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 469 c.Assert(err, jc.ErrorIsNil) 470 471 // test-available is the only available AZ, so AvailabilityZoneAllocations 472 // is guaranteed to return that. 473 inst, _ := testing.AssertStartInstance(c, env, "1") 474 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "test-available") 475 } 476 477 var azConstrainedErr = &amzec2.Error{ 478 Code: "Unsupported", 479 Message: "The requested Availability Zone is currently constrained etc.", 480 } 481 482 var azVolumeTypeNotAvailableInZoneErr = &amzec2.Error{ 483 Code: "VolumeTypeNotAvailableInZone", 484 Message: "blah blah", 485 } 486 487 var azInsufficientInstanceCapacityErr = &amzec2.Error{ 488 Code: "InsufficientInstanceCapacity", 489 Message: "We currently do not have sufficient m1.small capacity in the " + 490 "Availability Zone you requested (us-east-1d). Our system will " + 491 "be working on provisioning additional capacity. You can currently get m1.small " + 492 "capacity by not specifying an Availability Zone in your request or choosing " + 493 "us-east-1c, us-east-1a.", 494 } 495 496 var azNoDefaultSubnetErr = &amzec2.Error{ 497 Code: "InvalidInput", 498 Message: "No default subnet for availability zone: ''us-east-1e''.", 499 } 500 501 func (t *localServerSuite) TestStartInstanceAvailZoneAllConstrained(c *gc.C) { 502 t.testStartInstanceAvailZoneAllConstrained(c, azConstrainedErr) 503 } 504 505 func (t *localServerSuite) TestStartInstanceVolumeTypeNotAvailable(c *gc.C) { 506 t.testStartInstanceAvailZoneAllConstrained(c, azVolumeTypeNotAvailableInZoneErr) 507 } 508 509 func (t *localServerSuite) TestStartInstanceAvailZoneAllInsufficientInstanceCapacity(c *gc.C) { 510 t.testStartInstanceAvailZoneAllConstrained(c, azInsufficientInstanceCapacityErr) 511 } 512 513 func (t *localServerSuite) TestStartInstanceAvailZoneAllNoDefaultSubnet(c *gc.C) { 514 t.testStartInstanceAvailZoneAllConstrained(c, azNoDefaultSubnetErr) 515 } 516 517 func (t *localServerSuite) testStartInstanceAvailZoneAllConstrained(c *gc.C, runInstancesError *amzec2.Error) { 518 env := t.Prepare(c) 519 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 520 c.Assert(err, jc.ErrorIsNil) 521 522 mock := mockAvailabilityZoneAllocations{ 523 result: []common.AvailabilityZoneInstances{ 524 {ZoneName: "az1"}, {ZoneName: "az2"}, 525 }, 526 } 527 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 528 529 var azArgs []string 530 t.PatchValue(ec2.RunInstances, func(e *amzec2.EC2, ri *amzec2.RunInstances) (*amzec2.RunInstancesResp, error) { 531 azArgs = append(azArgs, ri.AvailZone) 532 return nil, runInstancesError 533 }) 534 _, _, _, err = testing.StartInstance(env, "1") 535 c.Assert(err, gc.ErrorMatches, fmt.Sprintf( 536 "cannot run instances: %s \\(%s\\)", 537 regexp.QuoteMeta(runInstancesError.Message), 538 runInstancesError.Code, 539 )) 540 c.Assert(azArgs, gc.DeepEquals, []string{"az1", "az2"}) 541 } 542 543 func (t *localServerSuite) TestStartInstanceAvailZoneOneConstrained(c *gc.C) { 544 t.testStartInstanceAvailZoneOneConstrained(c, azConstrainedErr) 545 } 546 547 func (t *localServerSuite) TestStartInstanceAvailZoneOneInsufficientInstanceCapacity(c *gc.C) { 548 t.testStartInstanceAvailZoneOneConstrained(c, azInsufficientInstanceCapacityErr) 549 } 550 551 func (t *localServerSuite) TestStartInstanceAvailZoneOneNoDefaultSubnetErr(c *gc.C) { 552 t.testStartInstanceAvailZoneOneConstrained(c, azNoDefaultSubnetErr) 553 } 554 555 func (t *localServerSuite) testStartInstanceAvailZoneOneConstrained(c *gc.C, runInstancesError *amzec2.Error) { 556 env := t.Prepare(c) 557 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 558 c.Assert(err, jc.ErrorIsNil) 559 560 mock := mockAvailabilityZoneAllocations{ 561 result: []common.AvailabilityZoneInstances{ 562 {ZoneName: "az1"}, {ZoneName: "az2"}, 563 }, 564 } 565 t.PatchValue(ec2.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 566 567 // The first call to RunInstances fails with an error indicating the AZ 568 // is constrained. The second attempt succeeds, and so allocates to az2. 569 var azArgs []string 570 realRunInstances := *ec2.RunInstances 571 t.PatchValue(ec2.RunInstances, func(e *amzec2.EC2, ri *amzec2.RunInstances) (*amzec2.RunInstancesResp, error) { 572 azArgs = append(azArgs, ri.AvailZone) 573 if len(azArgs) == 1 { 574 return nil, runInstancesError 575 } 576 return realRunInstances(e, ri) 577 }) 578 inst, hwc := testing.AssertStartInstance(c, env, "1") 579 c.Assert(azArgs, gc.DeepEquals, []string{"az1", "az2"}) 580 c.Assert(ec2.InstanceEC2(inst).AvailZone, gc.Equals, "az2") 581 c.Check(*hwc.AvailabilityZone, gc.Equals, "az2") 582 } 583 584 func (t *localServerSuite) TestAddresses(c *gc.C) { 585 env := t.Prepare(c) 586 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 587 c.Assert(err, jc.ErrorIsNil) 588 inst, _ := testing.AssertStartInstance(c, env, "1") 589 c.Assert(err, jc.ErrorIsNil) 590 addrs, err := inst.Addresses() 591 c.Assert(err, jc.ErrorIsNil) 592 // Expected values use Address type but really contain a regexp for 593 // the value rather than a valid ip or hostname. 594 expected := []network.Address{{ 595 Value: "8.0.0.*", 596 Type: network.IPv4Address, 597 Scope: network.ScopePublic, 598 }, { 599 Value: "127.0.0.*", 600 Type: network.IPv4Address, 601 Scope: network.ScopeCloudLocal, 602 }} 603 c.Assert(addrs, gc.HasLen, len(expected)) 604 for i, addr := range addrs { 605 c.Check(addr.Value, gc.Matches, expected[i].Value) 606 c.Check(addr.Type, gc.Equals, expected[i].Type) 607 c.Check(addr.Scope, gc.Equals, expected[i].Scope) 608 } 609 } 610 611 func (t *localServerSuite) TestConstraintsValidatorUnsupported(c *gc.C) { 612 env := t.Prepare(c) 613 validator, err := env.ConstraintsValidator() 614 c.Assert(err, jc.ErrorIsNil) 615 cons := constraints.MustParse("arch=amd64 tags=foo") 616 unsupported, err := validator.Validate(cons) 617 c.Assert(err, jc.ErrorIsNil) 618 c.Assert(unsupported, gc.DeepEquals, []string{"tags"}) 619 } 620 621 func (t *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { 622 env := t.Prepare(c) 623 validator, err := env.ConstraintsValidator() 624 c.Assert(err, jc.ErrorIsNil) 625 cons := constraints.MustParse("arch=ppc64el") 626 _, err = validator.Validate(cons) 627 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are:.*") 628 cons = constraints.MustParse("instance-type=foo") 629 _, err = validator.Validate(cons) 630 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 631 } 632 633 func (t *localServerSuite) TestConstraintsMerge(c *gc.C) { 634 env := t.Prepare(c) 635 validator, err := env.ConstraintsValidator() 636 c.Assert(err, jc.ErrorIsNil) 637 consA := constraints.MustParse("arch=amd64 mem=1G cpu-power=10 cpu-cores=2 tags=bar") 638 consB := constraints.MustParse("arch=i386 instance-type=m1.small") 639 cons, err := validator.Merge(consA, consB) 640 c.Assert(err, jc.ErrorIsNil) 641 c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=i386 instance-type=m1.small tags=bar")) 642 } 643 644 func (t *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 645 env := t.Prepare(c) 646 cons := constraints.MustParse("instance-type=m1.small root-disk=1G") 647 placement := "" 648 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 649 c.Assert(err, jc.ErrorIsNil) 650 } 651 652 func (t *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 653 env := t.Prepare(c) 654 cons := constraints.MustParse("instance-type=m1.invalid") 655 placement := "" 656 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 657 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "m1.invalid" specified`) 658 } 659 660 func (t *localServerSuite) TestPrecheckInstanceUnsupportedArch(c *gc.C) { 661 env := t.Prepare(c) 662 cons := constraints.MustParse("instance-type=cc1.4xlarge arch=i386") 663 placement := "" 664 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 665 c.Assert(err, gc.ErrorMatches, `invalid AWS instance type "cc1.4xlarge" and arch "i386" specified`) 666 } 667 668 func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 669 env := t.Prepare(c) 670 placement := "zone=test-available" 671 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 672 c.Assert(err, jc.ErrorIsNil) 673 } 674 675 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { 676 env := t.Prepare(c) 677 placement := "zone=test-unavailable" 678 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 679 c.Assert(err, jc.ErrorIsNil) 680 } 681 682 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 683 env := t.Prepare(c) 684 placement := "zone=test-unknown" 685 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 686 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 687 } 688 689 func (t *localServerSuite) TestValidateImageMetadata(c *gc.C) { 690 env := t.Prepare(c) 691 params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("test") 692 c.Assert(err, jc.ErrorIsNil) 693 params.Series = coretesting.FakeDefaultSeries 694 params.Endpoint = "https://ec2.endpoint.com" 695 params.Sources, err = environs.ImageMetadataSources(env) 696 c.Assert(err, jc.ErrorIsNil) 697 image_ids, _, err := imagemetadata.ValidateImageMetadata(params) 698 c.Assert(err, jc.ErrorIsNil) 699 sort.Strings(image_ids) 700 c.Assert(image_ids, gc.DeepEquals, []string{"ami-00000033", "ami-00000034", "ami-00000035", "ami-00000039"}) 701 } 702 703 func (t *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 704 t.PatchValue(&tools.DefaultBaseURL, "") 705 706 env := t.Prepare(c) 707 sources, err := tools.GetMetadataSources(env) 708 c.Assert(err, jc.ErrorIsNil) 709 c.Assert(sources, gc.HasLen, 0) 710 } 711 712 func (t *localServerSuite) TestSupportedArchitectures(c *gc.C) { 713 env := t.Prepare(c) 714 a, err := env.SupportedArchitectures() 715 c.Assert(err, jc.ErrorIsNil) 716 c.Assert(a, jc.SameContents, []string{"amd64", "i386"}) 717 } 718 719 func (t *localServerSuite) TestSupportsNetworking(c *gc.C) { 720 env := t.Prepare(c) 721 _, supported := environs.SupportsNetworking(env) 722 c.Assert(supported, jc.IsTrue) 723 } 724 725 func (t *localServerSuite) TestAllocateAddressFailureToFindNetworkInterface(c *gc.C) { 726 env := t.prepareEnviron(c) 727 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 728 c.Assert(err, jc.ErrorIsNil) 729 730 instanceIds, err := env.StateServerInstances() 731 c.Assert(err, jc.ErrorIsNil) 732 733 instId := instanceIds[0] 734 addr := network.Address{Value: "8.0.0.4"} 735 736 // Invalid instance found 737 err = env.AllocateAddress(instId+"foo", "", addr) 738 c.Assert(err, gc.ErrorMatches, ".*InvalidInstanceID.NotFound.*") 739 740 // No network interface 741 err = env.AllocateAddress(instId, "", addr) 742 c.Assert(errors.Cause(err), gc.ErrorMatches, "unexpected AWS response: network interface not found") 743 } 744 745 func (t *localServerSuite) setUpInstanceWithDefaultVpc(c *gc.C) (environs.NetworkingEnviron, instance.Id) { 746 // setting a default-vpc will create a network interface 747 t.srv.ec2srv.SetInitialAttributes(map[string][]string{ 748 "default-vpc": {"vpc-xxxxxxx"}, 749 }) 750 env := t.prepareEnviron(c) 751 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 752 c.Assert(err, jc.ErrorIsNil) 753 754 instanceIds, err := env.StateServerInstances() 755 c.Assert(err, jc.ErrorIsNil) 756 return env, instanceIds[0] 757 } 758 759 func (t *localServerSuite) TestAllocateAddress(c *gc.C) { 760 env, instId := t.setUpInstanceWithDefaultVpc(c) 761 762 addr := network.Address{Value: "8.0.0.4"} 763 var actualAddr network.Address 764 mockAssign := func(ec2Inst *amzec2.EC2, netId string, addr network.Address) error { 765 actualAddr = addr 766 return nil 767 } 768 t.PatchValue(&ec2.AssignPrivateIPAddress, mockAssign) 769 770 err := env.AllocateAddress(instId, "", addr) 771 c.Assert(err, jc.ErrorIsNil) 772 c.Assert(actualAddr, gc.Equals, addr) 773 } 774 775 func (t *localServerSuite) TestAllocateAddressIPAddressInUseOrEmpty(c *gc.C) { 776 env, instId := t.setUpInstanceWithDefaultVpc(c) 777 778 addr := network.Address{Value: "8.0.0.4"} 779 mockAssign := func(ec2Inst *amzec2.EC2, netId string, addr network.Address) error { 780 return &amzec2.Error{Code: "InvalidParameterValue"} 781 } 782 t.PatchValue(&ec2.AssignPrivateIPAddress, mockAssign) 783 784 err := env.AllocateAddress(instId, "", addr) 785 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable) 786 787 err = env.AllocateAddress(instId, "", network.Address{}) 788 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressUnavailable) 789 } 790 791 func (t *localServerSuite) TestAllocateAddressNetworkInterfaceFull(c *gc.C) { 792 env, instId := t.setUpInstanceWithDefaultVpc(c) 793 794 addr := network.Address{Value: "8.0.0.4"} 795 mockAssign := func(ec2Inst *amzec2.EC2, netId string, addr network.Address) error { 796 return &amzec2.Error{Code: "PrivateIpAddressLimitExceeded"} 797 } 798 t.PatchValue(&ec2.AssignPrivateIPAddress, mockAssign) 799 800 err := env.AllocateAddress(instId, "", addr) 801 c.Assert(errors.Cause(err), gc.Equals, environs.ErrIPAddressesExhausted) 802 } 803 804 func (t *localServerSuite) TestReleaseAddress(c *gc.C) { 805 env, instId := t.setUpInstanceWithDefaultVpc(c) 806 addr := network.Address{Value: "8.0.0.4"} 807 // Allocate the address first so we can release it 808 err := env.AllocateAddress(instId, "", addr) 809 c.Assert(err, jc.ErrorIsNil) 810 811 err = env.ReleaseAddress(instId, "", addr) 812 c.Assert(err, jc.ErrorIsNil) 813 814 // Releasing a second time tests that the first call actually released 815 // it plus tests the error handling of ReleaseAddress 816 err = env.ReleaseAddress(instId, "", addr) 817 msg := fmt.Sprintf(`failed to release address "8\.0\.0\.4" from instance %q.*`, instId) 818 c.Assert(err, gc.ErrorMatches, msg) 819 } 820 821 func (t *localServerSuite) TestReleaseAddressUnknownInstance(c *gc.C) { 822 env, _ := t.setUpInstanceWithDefaultVpc(c) 823 824 // We should be able to release an address with an unknown instance id 825 // without it being allocated. 826 addr := network.Address{Value: "8.0.0.4"} 827 err := env.ReleaseAddress(instance.UnknownId, "", addr) 828 c.Assert(err, jc.ErrorIsNil) 829 } 830 831 func (t *localServerSuite) TestNetworkInterfaces(c *gc.C) { 832 env, instId := t.setUpInstanceWithDefaultVpc(c) 833 interfaces, err := env.NetworkInterfaces(instId) 834 c.Assert(err, jc.ErrorIsNil) 835 expectedInterfaces := []network.InterfaceInfo{{ 836 DeviceIndex: 0, 837 MACAddress: "20:01:60:cb:27:37", 838 CIDR: "10.10.0.0/20", 839 ProviderId: "eni-0", 840 ProviderSubnetId: "subnet-0", 841 VLANTag: 0, 842 InterfaceName: "unsupported0", 843 Disabled: false, 844 NoAutoStart: false, 845 ConfigType: network.ConfigDHCP, 846 Address: network.NewScopedAddress("10.10.0.5", network.ScopeCloudLocal), 847 }} 848 c.Assert(interfaces, jc.DeepEquals, expectedInterfaces) 849 } 850 851 func (t *localServerSuite) TestSubnets(c *gc.C) { 852 env, _ := t.setUpInstanceWithDefaultVpc(c) 853 854 subnets, err := env.Subnets("", []network.Id{"subnet-0"}) 855 c.Assert(err, jc.ErrorIsNil) 856 857 defaultSubnets := []network.SubnetInfo{{ 858 // this is defined in the test server for the default-vpc 859 CIDR: "10.10.0.0/20", 860 ProviderId: "subnet-0", 861 VLANTag: 0, 862 AllocatableIPLow: net.ParseIP("10.10.0.4").To4(), 863 AllocatableIPHigh: net.ParseIP("10.10.15.254").To4(), 864 }} 865 c.Assert(subnets, jc.DeepEquals, defaultSubnets) 866 } 867 868 func (t *localServerSuite) TestSubnetsNoNetIds(c *gc.C) { 869 env, _ := t.setUpInstanceWithDefaultVpc(c) 870 871 _, err := env.Subnets("", []network.Id{}) 872 c.Assert(err, gc.ErrorMatches, "subnetIds must not be empty") 873 } 874 875 func (t *localServerSuite) TestSubnetsMissingSubnet(c *gc.C) { 876 env, _ := t.setUpInstanceWithDefaultVpc(c) 877 878 _, err := env.Subnets("", []network.Id{"subnet-0", "Missing"}) 879 c.Assert(err, gc.ErrorMatches, `failed to find the following subnet ids: \[Missing\]`) 880 } 881 882 func (t *localServerSuite) TestSupportsAddressAllocationTrue(c *gc.C) { 883 t.srv.ec2srv.SetInitialAttributes(map[string][]string{ 884 "default-vpc": {"vpc-xxxxxxx"}, 885 }) 886 env := t.prepareEnviron(c) 887 result, err := env.SupportsAddressAllocation("") 888 c.Assert(err, jc.ErrorIsNil) 889 c.Assert(result, jc.IsTrue) 890 } 891 892 func (t *localServerSuite) TestSupportsAddressAllocationWithNoFeatureFlag(c *gc.C) { 893 t.SetFeatureFlags() // clear the flags. 894 env := t.prepareEnviron(c) 895 result, err := env.SupportsAddressAllocation("") 896 c.Assert(err, gc.ErrorMatches, "address allocation not supported") 897 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 898 c.Assert(result, jc.IsFalse) 899 } 900 901 func (t *localServerSuite) TestAllocateAddressWithNoFeatureFlag(c *gc.C) { 902 t.SetFeatureFlags() // clear the flags. 903 env := t.prepareEnviron(c) 904 err := env.AllocateAddress("i-foo", "net1", network.NewAddresses("1.2.3.4")[0]) 905 c.Assert(err, gc.ErrorMatches, "address allocation not supported") 906 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 907 } 908 909 func (t *localServerSuite) TestReleaseAddressWithNoFeatureFlag(c *gc.C) { 910 t.SetFeatureFlags() // clear the flags. 911 env := t.prepareEnviron(c) 912 err := env.ReleaseAddress("i-foo", "net1", network.NewAddresses("1.2.3.4")[0]) 913 c.Assert(err, gc.ErrorMatches, "address allocation not supported") 914 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 915 } 916 917 func (t *localServerSuite) TestSupportsAddressAllocationCaches(c *gc.C) { 918 t.srv.ec2srv.SetInitialAttributes(map[string][]string{ 919 "default-vpc": {"none"}, 920 }) 921 env := t.prepareEnviron(c) 922 result, err := env.SupportsAddressAllocation("") 923 c.Assert(err, jc.ErrorIsNil) 924 c.Assert(result, jc.IsFalse) 925 926 // this value won't change normally, the change here is to 927 // ensure that subsequent calls use the cached value 928 t.srv.ec2srv.SetInitialAttributes(map[string][]string{ 929 "default-vpc": {"vpc-xxxxxxx"}, 930 }) 931 result, err = env.SupportsAddressAllocation("") 932 c.Assert(err, jc.ErrorIsNil) 933 c.Assert(result, jc.IsFalse) 934 } 935 936 func (t *localServerSuite) TestSupportsAddressAllocationFalse(c *gc.C) { 937 t.srv.ec2srv.SetInitialAttributes(map[string][]string{ 938 "default-vpc": {"none"}, 939 }) 940 env := t.prepareEnviron(c) 941 result, err := env.SupportsAddressAllocation("") 942 c.Assert(err, jc.ErrorIsNil) 943 c.Assert(result, jc.IsFalse) 944 } 945 946 func (t *localServerSuite) TestInstanceTags(c *gc.C) { 947 env := t.Prepare(c) 948 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 949 c.Assert(err, jc.ErrorIsNil) 950 951 instances, err := env.AllInstances() 952 c.Assert(err, jc.ErrorIsNil) 953 c.Assert(instances, gc.HasLen, 1) 954 955 ec2Inst := ec2.InstanceEC2(instances[0]) 956 c.Assert(ec2Inst.Tags, jc.SameContents, []amzec2.Tag{ 957 {"Name", "juju-sample-machine-0"}, 958 {"juju-env-uuid", coretesting.EnvironmentTag.Id()}, 959 {"juju-is-state", "true"}, 960 }) 961 } 962 963 // localNonUSEastSuite is similar to localServerSuite but the S3 mock server 964 // behaves as if it is not in the us-east region. 965 type localNonUSEastSuite struct { 966 coretesting.BaseSuite 967 restoreEC2Patching func() 968 srv localServer 969 env environs.Environ 970 } 971 972 func (t *localNonUSEastSuite) SetUpSuite(c *gc.C) { 973 t.BaseSuite.SetUpSuite(c) 974 t.restoreEC2Patching = patchEC2ForTesting() 975 } 976 977 func (t *localNonUSEastSuite) TearDownSuite(c *gc.C) { 978 t.restoreEC2Patching() 979 t.BaseSuite.TearDownSuite(c) 980 } 981 982 func (t *localNonUSEastSuite) SetUpTest(c *gc.C) { 983 t.BaseSuite.SetUpTest(c) 984 t.srv.config = &s3test.Config{ 985 Send409Conflict: true, 986 } 987 t.srv.startServer(c) 988 989 cfg, err := config.New(config.NoDefaults, localConfigAttrs) 990 c.Assert(err, jc.ErrorIsNil) 991 env, err := environs.Prepare(cfg, envtesting.BootstrapContext(c), configstore.NewMem()) 992 c.Assert(err, jc.ErrorIsNil) 993 t.env = env 994 } 995 996 func (t *localNonUSEastSuite) TearDownTest(c *gc.C) { 997 t.srv.stopServer(c) 998 t.BaseSuite.TearDownTest(c) 999 } 1000 1001 func patchEC2ForTesting() func() { 1002 ec2.UseTestImageData(ec2.TestImagesData) 1003 ec2.UseTestInstanceTypeData(ec2.TestInstanceTypeCosts) 1004 ec2.UseTestRegionData(ec2.TestRegions) 1005 restoreTimeouts := envtesting.PatchAttemptStrategies(ec2.ShortAttempt, ec2.StorageAttempt) 1006 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 1007 return func() { 1008 restoreFinishBootstrap() 1009 restoreTimeouts() 1010 ec2.UseTestImageData(nil) 1011 ec2.UseTestInstanceTypeData(nil) 1012 ec2.UseTestRegionData(nil) 1013 } 1014 } 1015 1016 // If match is true, CheckScripts checks that at least one script started 1017 // by the cloudinit data matches the given regexp pattern, otherwise it 1018 // checks that no script matches. It's exported so it can be used by tests 1019 // defined in ec2_test. 1020 func CheckScripts(c *gc.C, userDataMap map[interface{}]interface{}, pattern string, match bool) { 1021 scripts0 := userDataMap["runcmd"] 1022 if scripts0 == nil { 1023 c.Errorf("cloudinit has no entry for runcmd") 1024 return 1025 } 1026 scripts := scripts0.([]interface{}) 1027 re := regexp.MustCompile(pattern) 1028 found := false 1029 for _, s0 := range scripts { 1030 s := s0.(string) 1031 if re.MatchString(s) { 1032 found = true 1033 } 1034 } 1035 switch { 1036 case match && !found: 1037 c.Errorf("script %q not found in %q", pattern, scripts) 1038 case !match && found: 1039 c.Errorf("script %q found but not expected in %q", pattern, scripts) 1040 } 1041 } 1042 1043 // CheckPackage checks that the cloudinit will or won't install the given 1044 // package, depending on the value of match. It's exported so it can be 1045 // used by tests defined outside the ec2 package. 1046 func CheckPackage(c *gc.C, userDataMap map[interface{}]interface{}, pkg string, match bool) { 1047 pkgs0 := userDataMap["packages"] 1048 if pkgs0 == nil { 1049 if match { 1050 c.Errorf("cloudinit has no entry for packages") 1051 } 1052 return 1053 } 1054 1055 pkgs := pkgs0.([]interface{}) 1056 1057 found := false 1058 for _, p0 := range pkgs { 1059 p := p0.(string) 1060 // p might be a space separate list of packages eg 'foo bar qed' so split them up 1061 manyPkgs := set.NewStrings(strings.Split(p, " ")...) 1062 hasPkg := manyPkgs.Contains(pkg) 1063 if p == pkg || hasPkg { 1064 found = true 1065 break 1066 } 1067 } 1068 switch { 1069 case match && !found: 1070 c.Errorf("package %q not found in %v", pkg, pkgs) 1071 case !match && found: 1072 c.Errorf("%q found but not expected in %v", pkg, pkgs) 1073 } 1074 }