github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/lxd/testing_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lxd 5 6 import ( 7 "net" 8 "os" 9 "strconv" 10 "time" 11 12 "github.com/juju/clock" 13 "github.com/juju/errors" 14 gitjujutesting "github.com/juju/testing" 15 jc "github.com/juju/testing/checkers" 16 "github.com/juju/utils/arch" 17 "github.com/juju/version" 18 lxdclient "github.com/lxc/lxd/client" 19 "github.com/lxc/lxd/shared/api" 20 gc "gopkg.in/check.v1" 21 22 "github.com/juju/juju/cloud" 23 "github.com/juju/juju/cloudconfig/instancecfg" 24 "github.com/juju/juju/cloudconfig/providerinit" 25 "github.com/juju/juju/container/lxd" 26 containerlxd "github.com/juju/juju/container/lxd" 27 "github.com/juju/juju/core/constraints" 28 "github.com/juju/juju/core/instance" 29 "github.com/juju/juju/environs" 30 "github.com/juju/juju/environs/config" 31 "github.com/juju/juju/environs/context" 32 "github.com/juju/juju/environs/instances" 33 "github.com/juju/juju/environs/tags" 34 "github.com/juju/juju/network" 35 "github.com/juju/juju/testing" 36 coretools "github.com/juju/juju/tools" 37 ) 38 39 // Ensure LXD provider supports the expected interfaces. 40 var ( 41 _ config.ConfigSchemaSource = (*environProvider)(nil) 42 ) 43 44 // These values are stub LXD client credentials for use in tests. 45 const ( 46 PublicKey = `-----BEGIN CERTIFICATE----- 47 ... 48 ... 49 ... 50 ... 51 ... 52 ... 53 ... 54 ... 55 ... 56 ... 57 ... 58 ... 59 ... 60 ... 61 -----END CERTIFICATE----- 62 ` 63 PrivateKey = `-----BEGIN PRIVATE KEY----- 64 ... 65 ... 66 ... 67 ... 68 ... 69 ... 70 ... 71 ... 72 ... 73 ... 74 ... 75 ... 76 ... 77 ... 78 -----END PRIVATE KEY----- 79 ` 80 ) 81 82 // These are stub config values for use in tests. 83 var ( 84 ConfigAttrs = testing.FakeConfig().Merge(testing.Attrs{ 85 "type": "lxd", 86 "uuid": "2d02eeac-9dbb-11e4-89d3-123b93f75cba", 87 }) 88 ) 89 90 // We test these here since they are not exported. 91 var ( 92 _ environs.Environ = (*environ)(nil) 93 _ instances.Instance = (*environInstance)(nil) 94 ) 95 96 type BaseSuiteUnpatched struct { 97 testing.BaseSuite 98 99 osPathOrig string 100 101 Config *config.Config 102 EnvConfig *environConfig 103 Provider *environProvider 104 Env *environ 105 106 Addresses []network.Address 107 Instance *environInstance 108 Container *lxd.Container 109 InstName string 110 HWC *instance.HardwareCharacteristics 111 Metadata map[string]string 112 StartInstArgs environs.StartInstanceParams 113 114 Rules []network.IngressRule 115 EndpointAddrs []string 116 InterfaceAddr string 117 InterfaceAddrs []net.Addr 118 } 119 120 func (s *BaseSuiteUnpatched) SetUpSuite(c *gc.C) { 121 s.osPathOrig = os.Getenv("PATH") 122 if s.osPathOrig == "" { 123 // TODO(ericsnow) This shouldn't happen. However, an undiagnosed 124 // bug in testing.BaseSuite is causing $PATH to remain unset 125 // sometimes. Once that is cleared up this special-case can go 126 // away. 127 s.osPathOrig = 128 "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin" 129 } 130 s.BaseSuite.SetUpSuite(c) 131 } 132 133 func (s *BaseSuiteUnpatched) SetUpTest(c *gc.C) { 134 s.BaseSuite.SetUpTest(c) 135 136 s.initProvider(c) 137 s.initEnv(c) 138 s.initInst(c) 139 s.initNet(c) 140 } 141 142 func (s *BaseSuiteUnpatched) initProvider(c *gc.C) { 143 s.Provider = &environProvider{} 144 s.EndpointAddrs = []string{"1.2.3.4"} 145 s.InterfaceAddr = "1.2.3.4" 146 s.InterfaceAddrs = []net.Addr{ 147 &net.IPNet{IP: net.ParseIP("127.0.0.1")}, 148 &net.IPNet{IP: net.ParseIP("1.2.3.4")}, 149 } 150 } 151 152 func (s *BaseSuiteUnpatched) initEnv(c *gc.C) { 153 certCred := cloud.NewCredential(cloud.CertificateAuthType, map[string]string{ 154 "client-cert": testing.CACert, 155 "client-key": testing.CAKey, 156 "server-cert": testing.ServerCert, 157 }) 158 s.Env = &environ{ 159 cloud: environs.CloudSpec{ 160 Name: "localhost", 161 Type: "lxd", 162 Credential: &certCred, 163 }, 164 provider: s.Provider, 165 name: "lxd", 166 } 167 cfg := s.NewConfig(c, nil) 168 s.setConfig(c, cfg) 169 } 170 171 func (s *BaseSuiteUnpatched) Prefix() string { 172 return s.Env.namespace.Prefix() 173 } 174 175 func (s *BaseSuiteUnpatched) initInst(c *gc.C) { 176 tools := []*coretools.Tools{ 177 { 178 Version: version.Binary{Arch: arch.AMD64, Series: "trusty"}, 179 URL: "https://example.org/amd", 180 }, 181 { 182 Version: version.Binary{Arch: arch.ARM64, Series: "trusty"}, 183 URL: "https://example.org/arm", 184 }, 185 } 186 187 cons := constraints.Value{} 188 189 instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(testing.FakeControllerConfig(), cons, cons, "trusty", "") 190 c.Assert(err, jc.ErrorIsNil) 191 192 err = instanceConfig.SetTools(coretools.List{ 193 tools[0], 194 }) 195 c.Assert(err, jc.ErrorIsNil) 196 instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys() 197 198 userData, err := providerinit.ComposeUserData(instanceConfig, nil, lxdRenderer{}) 199 c.Assert(err, jc.ErrorIsNil) 200 201 var archName = arch.ARM64 202 var numCores uint64 = 1 203 var memoryMB uint64 = 3750 204 s.HWC = &instance.HardwareCharacteristics{ 205 Arch: &archName, 206 CpuCores: &numCores, 207 Mem: &memoryMB, 208 } 209 210 s.Metadata = map[string]string{ 211 containerlxd.UserNamespacePrefix + tags.JujuIsController: "true", 212 containerlxd.UserNamespacePrefix + tags.JujuController: testing.ControllerTag.Id(), 213 containerlxd.JujuModelKey: s.Config.UUID(), 214 containerlxd.UserDataKey: string(userData), 215 "limits.cpu": "1", 216 "limits.memory": strconv.Itoa(3750 * 1024 * 1024), 217 } 218 s.Addresses = []network.Address{{ 219 Value: "10.0.0.1", 220 Type: network.IPv4Address, 221 Scope: network.ScopeCloudLocal, 222 }} 223 // NOTE: the instance ids used throughout this package are not at all 224 // representative of what they would normally be. They would normally 225 // determined by the instance namespace and the machine id. 226 s.Instance = s.NewInstance(c, "spam") 227 s.Container = s.Instance.container 228 s.InstName, err = s.Env.namespace.Hostname("42") 229 c.Assert(err, jc.ErrorIsNil) 230 231 s.StartInstArgs = environs.StartInstanceParams{ 232 ControllerUUID: instanceConfig.Controller.Config.ControllerUUID(), 233 InstanceConfig: instanceConfig, 234 Tools: tools, 235 Constraints: cons, 236 } 237 } 238 239 func (s *BaseSuiteUnpatched) initNet(c *gc.C) { 240 s.Rules = []network.IngressRule{network.MustNewIngressRule("tcp", 80, 80)} 241 } 242 243 func (s *BaseSuiteUnpatched) setConfig(c *gc.C, cfg *config.Config) { 244 s.Config = cfg 245 ecfg, err := newValidConfig(cfg) 246 c.Assert(err, jc.ErrorIsNil) 247 s.EnvConfig = ecfg 248 uuid := cfg.UUID() 249 s.Env.uuid = uuid 250 s.Env.ecfg = s.EnvConfig 251 namespace, err := instance.NewNamespace(uuid) 252 c.Assert(err, jc.ErrorIsNil) 253 s.Env.namespace = namespace 254 } 255 256 func (s *BaseSuiteUnpatched) NewConfig(c *gc.C, updates testing.Attrs) *config.Config { 257 if updates == nil { 258 updates = make(testing.Attrs) 259 } 260 var err error 261 cfg := testing.ModelConfig(c) 262 cfg, err = cfg.Apply(ConfigAttrs) 263 c.Assert(err, jc.ErrorIsNil) 264 cfg, err = cfg.Apply(updates) 265 c.Assert(err, jc.ErrorIsNil) 266 return cfg 267 } 268 269 func (s *BaseSuiteUnpatched) UpdateConfig(c *gc.C, attrs map[string]interface{}) { 270 cfg, err := s.Config.Apply(attrs) 271 c.Assert(err, jc.ErrorIsNil) 272 s.setConfig(c, cfg) 273 } 274 275 func (s *BaseSuiteUnpatched) NewContainer(c *gc.C, name string) *containerlxd.Container { 276 metadata := make(map[string]string) 277 for k, v := range s.Metadata { 278 metadata[k] = v 279 } 280 281 return &containerlxd.Container{ 282 Container: api.Container{ 283 Name: name, 284 StatusCode: api.Running, 285 Status: api.Running.String(), 286 ContainerPut: api.ContainerPut{ 287 Config: metadata, 288 }, 289 }, 290 } 291 } 292 293 func (s *BaseSuiteUnpatched) NewInstance(c *gc.C, name string) *environInstance { 294 container := s.NewContainer(c, name) 295 return newInstance(container, s.Env) 296 } 297 298 func (s *BaseSuiteUnpatched) IsRunningLocally(c *gc.C) bool { 299 restore := gitjujutesting.PatchEnvPathPrepend(s.osPathOrig) 300 defer restore() 301 302 running, err := lxd.IsRunningLocally() 303 c.Assert(err, jc.ErrorIsNil) 304 return running 305 } 306 307 type BaseSuite struct { 308 BaseSuiteUnpatched 309 310 Stub *gitjujutesting.Stub 311 Client *StubClient 312 Common *stubCommon 313 } 314 315 func (s *BaseSuite) SetUpSuite(c *gc.C) { 316 s.BaseSuiteUnpatched.SetUpSuite(c) 317 // Do this *before* s.initEnv() gets called in BaseSuiteUnpatched.SetUpTest 318 } 319 320 func (s *BaseSuite) SetUpTest(c *gc.C) { 321 s.BaseSuiteUnpatched.SetUpTest(c) 322 323 s.Stub = &gitjujutesting.Stub{} 324 s.Client = &StubClient{ 325 Stub: s.Stub, 326 StorageIsSupported: true, 327 Server: &api.Server{ 328 ServerPut: api.ServerPut{ 329 Config: map[string]interface{}{}, 330 }, 331 Environment: api.ServerEnvironment{ 332 Certificate: "server-cert", 333 }, 334 }, 335 Profile: &api.Profile{}, 336 } 337 s.Common = &stubCommon{stub: s.Stub} 338 339 // Patch out all expensive external deps. 340 s.Env.server = s.Client 341 s.Env.base = s.Common 342 } 343 344 func (s *BaseSuite) TestingCert(c *gc.C) (lxd.Certificate, string) { 345 cert := lxd.Certificate{ 346 Name: "juju", 347 CertPEM: []byte(testing.CACert), 348 KeyPEM: []byte(testing.CAKey), 349 } 350 fingerprint, err := cert.Fingerprint() 351 c.Assert(err, jc.ErrorIsNil) 352 return cert, fingerprint 353 } 354 355 func (s *BaseSuite) CheckNoAPI(c *gc.C) { 356 s.Stub.CheckCalls(c, nil) 357 } 358 359 func NewBaseConfig(c *gc.C) *config.Config { 360 var err error 361 cfg := testing.ModelConfig(c) 362 363 cfg, err = cfg.Apply(ConfigAttrs) 364 c.Assert(err, jc.ErrorIsNil) 365 366 return cfg 367 } 368 369 type ConfigValues struct{} 370 371 type Config struct { 372 *environConfig 373 } 374 375 func NewConfig(cfg *config.Config) *Config { 376 ecfg := newConfig(cfg) 377 return &Config{ecfg} 378 } 379 380 func (ecfg *Config) Values(c *gc.C) (ConfigValues, map[string]interface{}) { 381 c.Assert(ecfg.attrs, jc.DeepEquals, ecfg.UnknownAttrs()) 382 383 var values ConfigValues 384 extras := make(map[string]interface{}) 385 for k, v := range ecfg.attrs { 386 switch k { 387 default: 388 extras[k] = v 389 } 390 } 391 return values, extras 392 } 393 394 func (ecfg *Config) Apply(c *gc.C, updates map[string]interface{}) *Config { 395 cfg, err := ecfg.Config.Apply(updates) 396 c.Assert(err, jc.ErrorIsNil) 397 return NewConfig(cfg) 398 } 399 400 func (ecfg *Config) Validate() error { 401 return ecfg.validate() 402 } 403 404 type stubCommon struct { 405 stub *gitjujutesting.Stub 406 407 BootstrapResult *environs.BootstrapResult 408 } 409 410 func (sc *stubCommon) BootstrapEnv(ctx environs.BootstrapContext, callCtx context.ProviderCallContext, params environs.BootstrapParams) (*environs.BootstrapResult, error) { 411 sc.stub.AddCall("Bootstrap", ctx, callCtx, params) 412 if err := sc.stub.NextErr(); err != nil { 413 return nil, errors.Trace(err) 414 } 415 416 return sc.BootstrapResult, nil 417 } 418 419 func (sc *stubCommon) DestroyEnv(callCtx context.ProviderCallContext) error { 420 sc.stub.AddCall("Destroy", callCtx) 421 if err := sc.stub.NextErr(); err != nil { 422 return errors.Trace(err) 423 } 424 425 return nil 426 } 427 428 type StubClient struct { 429 *gitjujutesting.Stub 430 431 Containers []lxd.Container 432 Container *lxd.Container 433 Server *api.Server 434 Profile *api.Profile 435 StorageIsSupported bool 436 Volumes map[string][]api.StorageVolume 437 ServerCert string 438 ServerHostArch string 439 } 440 441 func (conn *StubClient) FilterContainers(prefix string, statuses ...string) ([]lxd.Container, error) { 442 conn.AddCall("FilterContainers", prefix, statuses) 443 if err := conn.NextErr(); err != nil { 444 return nil, errors.Trace(err) 445 } 446 447 return conn.Containers, nil 448 } 449 450 func (conn *StubClient) CreateContainerFromSpec(spec lxd.ContainerSpec) (*lxd.Container, error) { 451 conn.AddCall("CreateContainerFromSpec", spec) 452 if err := conn.NextErr(); err != nil { 453 return nil, errors.Trace(err) 454 } 455 456 return conn.Container, nil 457 } 458 459 func (conn *StubClient) FindImage( 460 series, arch string, sources []lxd.ServerSpec, copyLocal bool, callback environs.StatusCallbackFunc, 461 ) (lxd.SourcedImage, error) { 462 conn.AddCall("FindImage", series, arch) 463 if err := conn.NextErr(); err != nil { 464 return lxd.SourcedImage{}, errors.Trace(err) 465 } 466 467 return lxd.SourcedImage{}, nil 468 } 469 470 func (conn *StubClient) CreateCertificate(cert api.CertificatesPost) error { 471 conn.AddCall("CreateCertificate", cert) 472 return conn.NextErr() 473 } 474 475 func (conn *StubClient) CreateClientCertificate(cert *lxd.Certificate) error { 476 conn.AddCall("CreateClientCertificate", cert) 477 return conn.NextErr() 478 } 479 480 func (conn *StubClient) DeleteCertificate(fingerprint string) error { 481 conn.AddCall("RemoveCertByFingerprint", fingerprint) 482 return conn.NextErr() 483 } 484 485 func (conn *StubClient) GetCertificate(fingerprint string) (*api.Certificate, string, error) { 486 conn.AddCall("GetCertificate", fingerprint) 487 return &api.Certificate{}, "", conn.NextErr() 488 } 489 490 func (conn *StubClient) GetServer() (*api.Server, string, error) { 491 conn.AddCall("ServerStatus") 492 if err := conn.NextErr(); err != nil { 493 return nil, "", err 494 } 495 return &api.Server{ 496 Environment: api.ServerEnvironment{ 497 Certificate: "server-cert", 498 }, 499 }, "etag", nil 500 } 501 502 func (conn *StubClient) GetConnectionInfo() (info *lxdclient.ConnectionInfo, err error) { 503 conn.AddCall("ServerAddresses") 504 return &lxdclient.ConnectionInfo{ 505 Addresses: []string{"127.0.0.1:1234", "1.2.3.4:1234"}, 506 }, conn.NextErr() 507 } 508 509 func (conn *StubClient) UpdateServerConfig(cfg map[string]string) error { 510 conn.AddCall("UpdateServerConfig", cfg) 511 return conn.NextErr() 512 } 513 514 func (conn *StubClient) UpdateContainerConfig(container string, cfg map[string]string) error { 515 conn.AddCall("UpdateContainerConfig", container, cfg) 516 return conn.NextErr() 517 } 518 519 func (conn *StubClient) LocalBridgeName() string { 520 conn.AddCall("LocalBridgeName") 521 return "test-bridge" 522 } 523 524 func (conn *StubClient) GetProfile(name string) (*api.Profile, string, error) { 525 conn.AddCall("GetProfile", name) 526 return conn.Profile, "etag", conn.NextErr() 527 } 528 529 func (conn *StubClient) GetContainerProfiles(name string) ([]string, error) { 530 conn.AddCall("GetContainerProfiles", name) 531 return []string{ 532 "default", 533 "juju-model-name", 534 }, conn.NextErr() 535 } 536 537 func (conn *StubClient) DeleteProfile(name string) error { 538 conn.AddCall("DeleteProfile", name) 539 return conn.NextErr() 540 } 541 542 func (conn *StubClient) HasProfile(name string) (bool, error) { 543 conn.AddCall("HasProfile", name) 544 return false, conn.NextErr() 545 } 546 547 func (conn *StubClient) ReplaceOrAddContainerProfile(name, oldProfile, newProfile string) error { 548 conn.AddCall("ReplaceOrAddContainerProfile", name, oldProfile, newProfile) 549 return conn.NextErr() 550 } 551 552 func (conn *StubClient) VerifyNetworkDevice(profile *api.Profile, ETag string) error { 553 conn.AddCall("VerifyNetworkDevice", profile, ETag) 554 return conn.NextErr() 555 } 556 557 func (conn *StubClient) StorageSupported() bool { 558 conn.AddCall("StorageSupported") 559 return conn.StorageIsSupported 560 } 561 562 func (conn *StubClient) EnsureDefaultStorage(profile *api.Profile, ETag string) error { 563 conn.AddCall("EnsureDefaultStorage", profile, ETag) 564 return conn.NextErr() 565 } 566 567 func (conn *StubClient) GetStoragePool(name string) (pool *api.StoragePool, ETag string, err error) { 568 conn.AddCall("GetStoragePool", name) 569 return &api.StoragePool{ 570 Name: name, 571 Driver: "dir", 572 }, "", conn.NextErr() 573 } 574 575 func (conn *StubClient) GetStoragePools() ([]api.StoragePool, error) { 576 conn.AddCall("GetStoragePools") 577 return []api.StoragePool{{ 578 Name: "juju", 579 Driver: "dir", 580 }, { 581 Name: "juju-zfs", 582 Driver: "zfs", 583 }}, conn.NextErr() 584 } 585 586 func (conn *StubClient) CreatePool(name, driver string, attrs map[string]string) error { 587 conn.AddCall("CreatePool", name, driver, attrs) 588 return conn.NextErr() 589 } 590 591 func (conn *StubClient) CreateVolume(pool, volume string, config map[string]string) error { 592 conn.AddCall("CreateVolume", pool, volume, config) 593 return conn.NextErr() 594 } 595 596 func (conn *StubClient) DeleteStoragePoolVolume(pool, volType, volume string) error { 597 conn.AddCall("DeleteStoragePoolVolume", pool, volType, volume) 598 return conn.NextErr() 599 } 600 601 func (conn *StubClient) GetStoragePoolVolume( 602 pool string, volType string, name string, 603 ) (*api.StorageVolume, string, error) { 604 conn.AddCall("GetStoragePoolVolume", pool, volType, name) 605 if err := conn.NextErr(); err != nil { 606 return nil, "", err 607 } 608 for _, v := range conn.Volumes[pool] { 609 if v.Name == name { 610 return &v, "eTag", nil 611 } 612 } 613 return nil, "", errors.NotFoundf("volume %q in pool %q", name, pool) 614 } 615 616 func (conn *StubClient) GetStoragePoolVolumes(pool string) ([]api.StorageVolume, error) { 617 conn.AddCall("GetStoragePoolVolumes", pool) 618 if err := conn.NextErr(); err != nil { 619 return nil, err 620 } 621 return conn.Volumes[pool], nil 622 } 623 624 func (conn *StubClient) UpdateStoragePoolVolume( 625 pool string, volType string, name string, volume api.StorageVolumePut, ETag string, 626 ) error { 627 conn.AddCall("UpdateStoragePoolVolume", pool, volType, name, volume, ETag) 628 return conn.NextErr() 629 } 630 631 func (conn *StubClient) AliveContainers(prefix string) ([]lxd.Container, error) { 632 conn.AddCall("AliveContainers", prefix) 633 if err := conn.NextErr(); err != nil { 634 return nil, err 635 } 636 return conn.Containers, nil 637 } 638 639 func (conn *StubClient) ContainerAddresses(name string) ([]network.Address, error) { 640 conn.AddCall("ContainerAddresses", name) 641 if err := conn.NextErr(); err != nil { 642 return nil, err 643 } 644 645 return []network.Address{{ 646 Value: "10.0.0.1", 647 Type: network.IPv4Address, 648 Scope: network.ScopeCloudLocal, 649 }}, nil 650 } 651 652 func (conn *StubClient) RemoveContainer(name string) error { 653 conn.AddCall("RemoveContainer", name) 654 return conn.NextErr() 655 } 656 657 func (conn *StubClient) RemoveContainers(names []string) error { 658 conn.AddCall("RemoveContainers", names) 659 return conn.NextErr() 660 } 661 662 func (conn *StubClient) WriteContainer(container *lxd.Container) error { 663 conn.AddCall("WriteContainer", container) 664 return conn.NextErr() 665 } 666 667 func (conn *StubClient) CreateProfileWithConfig(name string, cfg map[string]string) error { 668 conn.AddCall("CreateProfileWithConfig", name, cfg) 669 return conn.NextErr() 670 } 671 672 func (conn *StubClient) CreateProfile(post api.ProfilesPost) error { 673 conn.AddCall("CreateProfile", post) 674 return conn.NextErr() 675 } 676 677 func (conn *StubClient) ServerCertificate() string { 678 conn.AddCall("ServerCertificate") 679 return conn.ServerCert 680 } 681 682 func (conn *StubClient) HostArch() string { 683 conn.AddCall("HostArch") 684 return conn.ServerHostArch 685 } 686 687 func (conn *StubClient) EnableHTTPSListener() error { 688 conn.AddCall("EnableHTTPSListener") 689 return conn.NextErr() 690 } 691 692 func (conn *StubClient) GetNICsFromProfile(profName string) (map[string]map[string]string, error) { 693 conn.AddCall("GetNICsFromProfile", profName) 694 return conn.Profile.Devices, conn.NextErr() 695 } 696 697 func (conn *StubClient) IsClustered() bool { 698 conn.AddCall("IsClustered") 699 return true 700 } 701 702 func (conn *StubClient) Name() string { 703 conn.AddCall("Name") 704 return "server" 705 } 706 707 // TODO (manadart 2018-07-20): This exists to satisfy the testing stub 708 // interface. It is temporary, pending replacement with mocks and 709 // should not be called in tests. 710 func (conn *StubClient) UseTargetServer(name string) (*lxd.Server, error) { 711 conn.AddCall("UseTargetServer", name) 712 return nil, conn.NextErr() 713 } 714 715 func (conn *StubClient) GetClusterMembers() (members []api.ClusterMember, err error) { 716 conn.AddCall("GetClusterMembers") 717 return nil, conn.NextErr() 718 } 719 720 type MockClock struct { 721 clock.Clock 722 now time.Time 723 } 724 725 func (m *MockClock) Now() time.Time { 726 return m.now 727 } 728 729 func (m *MockClock) After(delay time.Duration) <-chan time.Time { 730 return time.After(time.Millisecond) 731 } 732 733 // TODO (manadart 2018-07-20): All of the above logic should ultimately be 734 // replaced by what follows (in some form). The stub usage will be abandoned 735 // and replaced by mocks. 736 737 type EnvironSuite struct { 738 testing.BaseSuite 739 } 740 741 func (s *EnvironSuite) NewEnviron(c *gc.C, svr Server, cfgEdit map[string]interface{}) environs.Environ { 742 cfg, err := testing.ModelConfig(c).Apply(ConfigAttrs) 743 c.Assert(err, jc.ErrorIsNil) 744 745 if cfgEdit != nil { 746 var err error 747 cfg, err = cfg.Apply(cfgEdit) 748 c.Assert(err, jc.ErrorIsNil) 749 } 750 751 eCfg, err := newValidConfig(cfg) 752 c.Assert(err, jc.ErrorIsNil) 753 754 namespace, err := instance.NewNamespace(cfg.UUID()) 755 c.Assert(err, jc.ErrorIsNil) 756 757 return &environ{ 758 server: svr, 759 ecfg: eCfg, 760 namespace: namespace, 761 } 762 } 763 764 func (s *EnvironSuite) GetStartInstanceArgs(c *gc.C, series string) environs.StartInstanceParams { 765 tools := []*coretools.Tools{ 766 { 767 Version: version.Binary{Arch: arch.AMD64, Series: series}, 768 URL: "https://example.org/amd", 769 }, 770 { 771 Version: version.Binary{Arch: arch.ARM64, Series: series}, 772 URL: "https://example.org/arm", 773 }, 774 } 775 776 cons := constraints.Value{} 777 iConfig, err := instancecfg.NewBootstrapInstanceConfig(testing.FakeControllerConfig(), cons, cons, series, "") 778 c.Assert(err, jc.ErrorIsNil) 779 780 return environs.StartInstanceParams{ 781 ControllerUUID: iConfig.Controller.Config.ControllerUUID(), 782 InstanceConfig: iConfig, 783 Tools: tools, 784 Constraints: cons, 785 } 786 }