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