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