github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/provider/gce/testing_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gce 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "strings" 10 11 gitjujutesting "github.com/juju/testing" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 15 "github.com/juju/juju/cloudconfig/instancecfg" 16 "github.com/juju/juju/cloudconfig/providerinit" 17 "github.com/juju/juju/constraints" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/environs/imagemetadata" 21 "github.com/juju/juju/environs/instances" 22 "github.com/juju/juju/environs/simplestreams" 23 "github.com/juju/juju/instance" 24 "github.com/juju/juju/juju/arch" 25 "github.com/juju/juju/network" 26 "github.com/juju/juju/provider/common" 27 "github.com/juju/juju/provider/gce/google" 28 "github.com/juju/juju/testing" 29 "github.com/juju/juju/tools" 30 "github.com/juju/juju/version" 31 ) 32 33 // These values are fake GCE auth credentials for use in tests. 34 const ( 35 ClientName = "ba9876543210-0123456789abcdefghijklmnopqrstuv" 36 ClientID = ClientName + ".apps.googleusercontent.com" 37 ClientEmail = ClientName + "@developer.gserviceaccount.com" 38 PrivateKey = `-----BEGIN PRIVATE KEY----- 39 ... 40 ... 41 ... 42 ... 43 ... 44 ... 45 ... 46 ... 47 ... 48 ... 49 ... 50 ... 51 ... 52 ... 53 -----END PRIVATE KEY----- 54 ` 55 ) 56 57 // These are fake config values for use in tests. 58 var ( 59 AuthFile = fmt.Sprintf(`{ 60 "private_key_id": "abcdef0123456789abcdef0123456789abcdef01", 61 "private_key": "%s", 62 "client_email": "%s", 63 "client_id": "%s", 64 "type": "service_account" 65 }`, strings.Replace(PrivateKey, "\n", "\\n", -1), ClientEmail, ClientID) 66 67 ConfigAttrs = testing.FakeConfig().Merge(testing.Attrs{ 68 "type": "gce", 69 "auth-file": "", 70 "private-key": PrivateKey, 71 "client-id": ClientID, 72 "client-email": ClientEmail, 73 "region": "home", 74 "project-id": "my-juju", 75 "image-endpoint": "https://www.googleapis.com", 76 "uuid": "2d02eeac-9dbb-11e4-89d3-123b93f75cba", 77 }) 78 ) 79 80 type BaseSuiteUnpatched struct { 81 gitjujutesting.IsolationSuite 82 83 Config *config.Config 84 EnvConfig *environConfig 85 Env *environ 86 Prefix string 87 88 Addresses []network.Address 89 BaseInstance *google.Instance 90 BaseDisk *google.Disk 91 Instance *environInstance 92 InstName string 93 Metadata map[string]string 94 StartInstArgs environs.StartInstanceParams 95 InstanceType instances.InstanceType 96 97 Ports []network.PortRange 98 } 99 100 var _ environs.Environ = (*environ)(nil) 101 var _ simplestreams.HasRegion = (*environ)(nil) 102 var _ instance.Instance = (*environInstance)(nil) 103 104 func (s *BaseSuiteUnpatched) SetUpTest(c *gc.C) { 105 s.IsolationSuite.SetUpTest(c) 106 107 s.initEnv(c) 108 s.initInst(c) 109 s.initNet(c) 110 } 111 112 func (s *BaseSuiteUnpatched) initEnv(c *gc.C) { 113 s.Env = &environ{ 114 name: "google", 115 } 116 cfg := s.NewConfig(c, nil) 117 s.setConfig(c, cfg) 118 } 119 120 func (s *BaseSuiteUnpatched) initInst(c *gc.C) { 121 tools := []*tools.Tools{{ 122 Version: version.Binary{Arch: arch.AMD64, Series: "trusty"}, 123 URL: "https://example.org", 124 }} 125 126 cons := constraints.Value{InstanceType: &allInstanceTypes[0].Name} 127 128 instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(cons, "trusty") 129 c.Assert(err, jc.ErrorIsNil) 130 131 instanceConfig.Tools = tools[0] 132 instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys() 133 134 userData, err := providerinit.ComposeUserData(instanceConfig, nil) 135 c.Assert(err, jc.ErrorIsNil) 136 b64UserData := base64.StdEncoding.EncodeToString([]byte(userData)) 137 138 authKeys, err := google.FormatAuthorizedKeys(instanceConfig.AuthorizedKeys, "ubuntu") 139 c.Assert(err, jc.ErrorIsNil) 140 141 s.Metadata = map[string]string{ 142 metadataKeyIsState: metadataValueTrue, 143 metadataKeyCloudInit: b64UserData, 144 metadataKeyEncoding: "base64", 145 metadataKeySSHKeys: authKeys, 146 } 147 s.Addresses = []network.Address{{ 148 Value: "10.0.0.1", 149 Type: network.IPv4Address, 150 Scope: network.ScopeCloudLocal, 151 }} 152 s.Instance = s.NewInstance(c, "spam") 153 s.BaseInstance = s.Instance.base 154 s.InstName = s.Prefix + "machine-spam" 155 156 s.StartInstArgs = environs.StartInstanceParams{ 157 InstanceConfig: instanceConfig, 158 Tools: tools, 159 Constraints: cons, 160 //Placement: "", 161 //DistributionGroup: nil, 162 } 163 164 s.InstanceType = allInstanceTypes[0] 165 // Storage 166 s.BaseDisk = &google.Disk{ 167 Id: 1234567, 168 Name: "home-zone--c930380d-8337-4bf5-b07a-9dbb5ae771e4", 169 Zone: "home-zone", 170 Status: google.StatusReady, 171 Size: 1024, 172 } 173 } 174 175 func (s *BaseSuiteUnpatched) initNet(c *gc.C) { 176 s.Ports = []network.PortRange{{ 177 FromPort: 80, 178 ToPort: 80, 179 Protocol: "tcp", 180 }} 181 } 182 183 func (s *BaseSuiteUnpatched) setConfig(c *gc.C, cfg *config.Config) { 184 s.Config = cfg 185 ecfg, err := newValidConfig(cfg, configDefaults) 186 c.Assert(err, jc.ErrorIsNil) 187 s.EnvConfig = ecfg 188 uuid, _ := cfg.UUID() 189 s.Env.uuid = uuid 190 s.Env.ecfg = s.EnvConfig 191 s.Prefix = "juju-" + uuid + "-" 192 } 193 194 func (s *BaseSuiteUnpatched) NewConfig(c *gc.C, updates testing.Attrs) *config.Config { 195 var err error 196 cfg := testing.EnvironConfig(c) 197 cfg, err = cfg.Apply(ConfigAttrs) 198 c.Assert(err, jc.ErrorIsNil) 199 cfg, err = cfg.Apply(updates) 200 c.Assert(err, jc.ErrorIsNil) 201 return cfg 202 } 203 204 func (s *BaseSuiteUnpatched) UpdateConfig(c *gc.C, attrs map[string]interface{}) { 205 cfg, err := s.Config.Apply(attrs) 206 c.Assert(err, jc.ErrorIsNil) 207 s.setConfig(c, cfg) 208 } 209 210 func (s *BaseSuiteUnpatched) NewBaseInstance(c *gc.C, id string) *google.Instance { 211 diskSpec := google.DiskSpec{ 212 SizeHintGB: 15, 213 ImageURL: "some/image/path", 214 Boot: true, 215 Scratch: false, 216 Readonly: false, 217 AutoDelete: true, 218 } 219 instanceSpec := google.InstanceSpec{ 220 ID: id, 221 Type: "mtype", 222 Disks: []google.DiskSpec{diskSpec}, 223 Network: google.NetworkSpec{Name: "somenetwork"}, 224 NetworkInterfaces: []string{"somenetif"}, 225 Metadata: s.Metadata, 226 Tags: []string{id}, 227 } 228 summary := google.InstanceSummary{ 229 ID: id, 230 ZoneName: "home-zone", 231 Status: google.StatusRunning, 232 Metadata: s.Metadata, 233 Addresses: s.Addresses, 234 } 235 return google.NewInstance(summary, &instanceSpec) 236 } 237 238 func (s *BaseSuiteUnpatched) NewInstance(c *gc.C, id string) *environInstance { 239 base := s.NewBaseInstance(c, id) 240 return newInstance(base, s.Env) 241 } 242 243 type BaseSuite struct { 244 BaseSuiteUnpatched 245 246 FakeConn *fakeConn 247 FakeCommon *fakeCommon 248 FakeEnviron *fakeEnviron 249 FakeImages *fakeImages 250 } 251 252 func (s *BaseSuite) SetUpTest(c *gc.C) { 253 s.BaseSuiteUnpatched.SetUpTest(c) 254 255 s.FakeConn = &fakeConn{} 256 s.FakeCommon = &fakeCommon{} 257 s.FakeEnviron = &fakeEnviron{} 258 s.FakeImages = &fakeImages{} 259 260 // Patch out all expensive external deps. 261 s.Env.gce = s.FakeConn 262 s.PatchValue(&newConnection, func(*environConfig) (gceConnection, error) { 263 return s.FakeConn, nil 264 }) 265 s.PatchValue(&supportedArchitectures, s.FakeCommon.SupportedArchitectures) 266 s.PatchValue(&bootstrap, s.FakeCommon.Bootstrap) 267 s.PatchValue(&destroyEnv, s.FakeCommon.Destroy) 268 s.PatchValue(&availabilityZoneAllocations, s.FakeCommon.AvailabilityZoneAllocations) 269 s.PatchValue(&buildInstanceSpec, s.FakeEnviron.BuildInstanceSpec) 270 s.PatchValue(&getHardwareCharacteristics, s.FakeEnviron.GetHardwareCharacteristics) 271 s.PatchValue(&newRawInstance, s.FakeEnviron.NewRawInstance) 272 s.PatchValue(&findInstanceSpec, s.FakeEnviron.FindInstanceSpec) 273 s.PatchValue(&getInstances, s.FakeEnviron.GetInstances) 274 s.PatchValue(&imageMetadataFetch, s.FakeImages.ImageMetadataFetch) 275 } 276 277 func (s *BaseSuite) CheckNoAPI(c *gc.C) { 278 c.Check(s.FakeConn.Calls, gc.HasLen, 0) 279 } 280 281 // TODO(ericsnow) Move fakeCallArgs, fakeCall, and fake to the testing repo? 282 283 type FakeCallArgs map[string]interface{} 284 285 type FakeCall struct { 286 FuncName string 287 Args FakeCallArgs 288 } 289 290 type fake struct { 291 calls []FakeCall 292 293 Err error 294 FailOnCall int 295 } 296 297 func (f *fake) err() error { 298 if len(f.calls) != f.FailOnCall+1 { 299 return nil 300 } 301 return f.Err 302 } 303 304 func (f *fake) addCall(funcName string, args FakeCallArgs) { 305 f.calls = append(f.calls, FakeCall{ 306 FuncName: funcName, 307 Args: args, 308 }) 309 } 310 311 func (f *fake) CheckCalls(c *gc.C, expected []FakeCall) { 312 c.Check(f.calls, jc.DeepEquals, expected) 313 } 314 315 type fakeCommon struct { 316 fake 317 318 Arches []string 319 Arch string 320 Series string 321 BSFinalizer environs.BootstrapFinalizer 322 AZInstances []common.AvailabilityZoneInstances 323 } 324 325 func (fc *fakeCommon) SupportedArchitectures(env environs.Environ, cons *imagemetadata.ImageConstraint) ([]string, error) { 326 fc.addCall("SupportedArchitectures", FakeCallArgs{ 327 "env": env, 328 "cons": cons, 329 }) 330 return fc.Arches, fc.err() 331 } 332 333 func (fc *fakeCommon) Bootstrap(ctx environs.BootstrapContext, env environs.Environ, params environs.BootstrapParams) (string, string, environs.BootstrapFinalizer, error) { 334 fc.addCall("Bootstrap", FakeCallArgs{ 335 "ctx": ctx, 336 "env": env, 337 "params": params, 338 }) 339 return fc.Arch, fc.Series, fc.BSFinalizer, fc.err() 340 } 341 342 func (fc *fakeCommon) Destroy(env environs.Environ) error { 343 fc.addCall("Destroy", FakeCallArgs{ 344 "env": env, 345 }) 346 return fc.err() 347 } 348 349 func (fc *fakeCommon) AvailabilityZoneAllocations(env common.ZonedEnviron, group []instance.Id) ([]common.AvailabilityZoneInstances, error) { 350 fc.addCall("AvailabilityZoneAllocations", FakeCallArgs{ 351 "env": env, 352 "group": group, 353 }) 354 return fc.AZInstances, fc.err() 355 } 356 357 type fakeEnviron struct { 358 fake 359 360 Inst *google.Instance 361 Insts []instance.Instance 362 Hwc *instance.HardwareCharacteristics 363 Spec *instances.InstanceSpec 364 } 365 366 func (fe *fakeEnviron) GetInstances(env *environ) ([]instance.Instance, error) { 367 fe.addCall("GetInstances", FakeCallArgs{ 368 "env": env, 369 }) 370 return fe.Insts, fe.err() 371 } 372 373 func (fe *fakeEnviron) BuildInstanceSpec(env *environ, args environs.StartInstanceParams) (*instances.InstanceSpec, error) { 374 fe.addCall("BuildInstanceSpec", FakeCallArgs{ 375 "env": env, 376 "args": args, 377 }) 378 return fe.Spec, fe.err() 379 } 380 381 func (fe *fakeEnviron) GetHardwareCharacteristics(env *environ, spec *instances.InstanceSpec, inst *environInstance) *instance.HardwareCharacteristics { 382 fe.addCall("GetHardwareCharacteristics", FakeCallArgs{ 383 "env": env, 384 "spec": spec, 385 "inst": inst, 386 }) 387 return fe.Hwc 388 } 389 390 func (fe *fakeEnviron) NewRawInstance(env *environ, args environs.StartInstanceParams, spec *instances.InstanceSpec) (*google.Instance, error) { 391 fe.addCall("NewRawInstance", FakeCallArgs{ 392 "env": env, 393 "args": args, 394 "spec": spec, 395 }) 396 return fe.Inst, fe.err() 397 } 398 399 func (fe *fakeEnviron) FindInstanceSpec(env *environ, stream string, ic *instances.InstanceConstraint) (*instances.InstanceSpec, error) { 400 fe.addCall("FindInstanceSpec", FakeCallArgs{ 401 "env": env, 402 "stream": stream, 403 "ic": ic, 404 }) 405 return fe.Spec, fe.err() 406 } 407 408 type fakeImages struct { 409 fake 410 411 Metadata []*imagemetadata.ImageMetadata 412 ResolveInfo *simplestreams.ResolveInfo 413 } 414 415 func (fi *fakeImages) ImageMetadataFetch(sources []simplestreams.DataSource, cons *imagemetadata.ImageConstraint, onlySigned bool) ([]*imagemetadata.ImageMetadata, *simplestreams.ResolveInfo, error) { 416 return fi.Metadata, fi.ResolveInfo, fi.err() 417 } 418 419 // TODO(ericsnow) Refactor fakeConnCall and fakeConn to embed fakeCall and fake. 420 421 type fakeConnCall struct { 422 FuncName string 423 424 ID string 425 IDs []string 426 ZoneName string 427 ZoneNames []string 428 Prefix string 429 Statuses []string 430 InstanceSpec google.InstanceSpec 431 FirewallName string 432 PortRanges []network.PortRange 433 Region string 434 Disks []google.DiskSpec 435 VolumeName string 436 InstanceId string 437 Mode string 438 } 439 440 type fakeConn struct { 441 Calls []fakeConnCall 442 443 Inst *google.Instance 444 Insts []google.Instance 445 PortRanges []network.PortRange 446 Zones []google.AvailabilityZone 447 448 GoogleDisks []*google.Disk 449 GoogleDisk *google.Disk 450 AttachedDisk *google.AttachedDisk 451 AttachedDisks []*google.AttachedDisk 452 453 Err error 454 FailOnCall int 455 } 456 457 func (fc *fakeConn) err() error { 458 if len(fc.Calls) != fc.FailOnCall+1 { 459 return nil 460 } 461 return fc.Err 462 } 463 464 func (fc *fakeConn) VerifyCredentials() error { 465 fc.Calls = append(fc.Calls, fakeConnCall{ 466 FuncName: "", 467 }) 468 return fc.err() 469 } 470 471 func (fc *fakeConn) Instance(id, zone string) (google.Instance, error) { 472 fc.Calls = append(fc.Calls, fakeConnCall{ 473 FuncName: "Instance", 474 ID: id, 475 ZoneName: zone, 476 }) 477 return *fc.Inst, fc.err() 478 } 479 480 func (fc *fakeConn) Instances(prefix string, statuses ...string) ([]google.Instance, error) { 481 fc.Calls = append(fc.Calls, fakeConnCall{ 482 FuncName: "Instances", 483 Prefix: prefix, 484 Statuses: statuses, 485 }) 486 return fc.Insts, fc.err() 487 } 488 489 func (fc *fakeConn) AddInstance(spec google.InstanceSpec, zones ...string) (*google.Instance, error) { 490 fc.Calls = append(fc.Calls, fakeConnCall{ 491 FuncName: "AddInstance", 492 InstanceSpec: spec, 493 ZoneNames: zones, 494 }) 495 return fc.Inst, fc.err() 496 } 497 498 func (fc *fakeConn) RemoveInstances(prefix string, ids ...string) error { 499 fc.Calls = append(fc.Calls, fakeConnCall{ 500 FuncName: "RemoveInstances", 501 Prefix: prefix, 502 IDs: ids, 503 }) 504 return fc.err() 505 } 506 507 func (fc *fakeConn) Ports(fwname string) ([]network.PortRange, error) { 508 fc.Calls = append(fc.Calls, fakeConnCall{ 509 FuncName: "Ports", 510 FirewallName: fwname, 511 }) 512 return fc.PortRanges, fc.err() 513 } 514 515 func (fc *fakeConn) OpenPorts(fwname string, ports ...network.PortRange) error { 516 fc.Calls = append(fc.Calls, fakeConnCall{ 517 FuncName: "OpenPorts", 518 FirewallName: fwname, 519 PortRanges: ports, 520 }) 521 return fc.err() 522 } 523 524 func (fc *fakeConn) ClosePorts(fwname string, ports ...network.PortRange) error { 525 fc.Calls = append(fc.Calls, fakeConnCall{ 526 FuncName: "ClosePorts", 527 FirewallName: fwname, 528 PortRanges: ports, 529 }) 530 return fc.err() 531 } 532 533 func (fc *fakeConn) AvailabilityZones(region string) ([]google.AvailabilityZone, error) { 534 fc.Calls = append(fc.Calls, fakeConnCall{ 535 FuncName: "AvailabilityZones", 536 Region: region, 537 }) 538 return fc.Zones, fc.err() 539 } 540 541 func (fc *fakeConn) CreateDisks(zone string, disks []google.DiskSpec) ([]*google.Disk, error) { 542 fc.Calls = append(fc.Calls, fakeConnCall{ 543 FuncName: "CreateDisks", 544 ZoneName: zone, 545 Disks: disks, 546 }) 547 return fc.GoogleDisks, fc.err() 548 } 549 550 func (fc *fakeConn) Disks(zone string) ([]*google.Disk, error) { 551 fc.Calls = append(fc.Calls, fakeConnCall{ 552 FuncName: "Disks", 553 ZoneName: zone, 554 }) 555 return fc.GoogleDisks, fc.err() 556 } 557 558 func (fc *fakeConn) RemoveDisk(zone, id string) error { 559 fc.Calls = append(fc.Calls, fakeConnCall{ 560 FuncName: "RemoveDisk", 561 ZoneName: zone, 562 ID: id, 563 }) 564 return fc.err() 565 } 566 567 func (fc *fakeConn) Disk(zone, id string) (*google.Disk, error) { 568 fc.Calls = append(fc.Calls, fakeConnCall{ 569 FuncName: "Disk", 570 ZoneName: zone, 571 ID: id, 572 }) 573 return fc.GoogleDisk, fc.err() 574 } 575 576 func (fc *fakeConn) AttachDisk(zone, volumeName, instanceId string, mode google.DiskMode) (*google.AttachedDisk, error) { 577 fc.Calls = append(fc.Calls, fakeConnCall{ 578 FuncName: "AttachDisk", 579 ZoneName: zone, 580 VolumeName: volumeName, 581 InstanceId: instanceId, 582 Mode: string(mode), 583 }) 584 return fc.AttachedDisk, fc.err() 585 } 586 587 func (fc *fakeConn) DetachDisk(zone, instanceId, volumeName string) error { 588 fc.Calls = append(fc.Calls, fakeConnCall{ 589 FuncName: "DetachDisk", 590 ZoneName: zone, 591 InstanceId: instanceId, 592 VolumeName: volumeName, 593 }) 594 return fc.err() 595 } 596 597 func (fc *fakeConn) InstanceDisks(zone, instanceId string) ([]*google.AttachedDisk, error) { 598 fc.Calls = append(fc.Calls, fakeConnCall{ 599 FuncName: "InstanceDisks", 600 ZoneName: zone, 601 InstanceId: instanceId, 602 }) 603 return fc.AttachedDisks, fc.err() 604 } 605 606 func (fc *fakeConn) WasCalled(funcName string) (bool, []fakeConnCall) { 607 var calls []fakeConnCall 608 called := false 609 for _, call := range fc.Calls { 610 if call.FuncName == funcName { 611 called = true 612 calls = append(calls, call) 613 } 614 } 615 return called, calls 616 }