github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/provider/lxd/testing_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build go1.3
     5  
     6  package lxd
     7  
     8  import (
     9  	"crypto/tls"
    10  	"encoding/pem"
    11  	"os"
    12  
    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  	gc "gopkg.in/check.v1"
    18  
    19  	"github.com/juju/juju/cloudconfig/instancecfg"
    20  	"github.com/juju/juju/cloudconfig/providerinit"
    21  	"github.com/juju/juju/constraints"
    22  	"github.com/juju/juju/environs"
    23  	"github.com/juju/juju/environs/config"
    24  	"github.com/juju/juju/environs/tags"
    25  	"github.com/juju/juju/instance"
    26  	"github.com/juju/juju/network"
    27  	"github.com/juju/juju/testing"
    28  	coretools "github.com/juju/juju/tools"
    29  	"github.com/juju/juju/tools/lxdclient"
    30  	"github.com/juju/version"
    31  )
    32  
    33  // These values are stub LXD client credentials for use in tests.
    34  const (
    35  	PublicKey = `-----BEGIN CERTIFICATE-----
    36  ...
    37  ...
    38  ...
    39  ...
    40  ...
    41  ...
    42  ...
    43  ...
    44  ...
    45  ...
    46  ...
    47  ...
    48  ...
    49  ...
    50  -----END CERTIFICATE-----
    51  `
    52  	PrivateKey = `-----BEGIN PRIVATE KEY-----
    53  ...
    54  ...
    55  ...
    56  ...
    57  ...
    58  ...
    59  ...
    60  ...
    61  ...
    62  ...
    63  ...
    64  ...
    65  ...
    66  ...
    67  -----END PRIVATE KEY-----
    68  `
    69  )
    70  
    71  // These are stub config values for use in tests.
    72  var (
    73  	ConfigAttrs = testing.FakeConfig().Merge(testing.Attrs{
    74  		"type":            "lxd",
    75  		"namespace":       "",
    76  		"remote-url":      "",
    77  		"client-cert":     "",
    78  		"client-key":      "",
    79  		"server-cert":     "",
    80  		"uuid":            "2d02eeac-9dbb-11e4-89d3-123b93f75cba",
    81  		"controller-uuid": "bfef02f1-932a-425a-a102-62175dcabd1d",
    82  	})
    83  )
    84  
    85  // We test these here since they are not exported.
    86  var (
    87  	_ environs.Environ  = (*environ)(nil)
    88  	_ instance.Instance = (*environInstance)(nil)
    89  )
    90  
    91  type BaseSuiteUnpatched struct {
    92  	gitjujutesting.IsolationSuite
    93  
    94  	osPathOrig string
    95  
    96  	Config    *config.Config
    97  	EnvConfig *environConfig
    98  	Env       *environ
    99  	Prefix    string
   100  
   101  	Addresses     []network.Address
   102  	Instance      *environInstance
   103  	RawInstance   *lxdclient.Instance
   104  	InstName      string
   105  	Hardware      *lxdclient.InstanceHardware
   106  	HWC           *instance.HardwareCharacteristics
   107  	Metadata      map[string]string
   108  	StartInstArgs environs.StartInstanceParams
   109  	//InstanceType  instances.InstanceType
   110  
   111  	Ports []network.PortRange
   112  }
   113  
   114  func (s *BaseSuiteUnpatched) SetUpSuite(c *gc.C) {
   115  	s.osPathOrig = os.Getenv("PATH")
   116  	if s.osPathOrig == "" {
   117  		// TODO(ericsnow) This shouldn't happen. However, an undiagnosed
   118  		// bug in testing.IsolationSuite is causing $PATH to remain unset
   119  		// sometimes.  Once that is cleared up this special-case can go
   120  		// away.
   121  		s.osPathOrig =
   122  			"/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
   123  	}
   124  	s.IsolationSuite.SetUpSuite(c)
   125  }
   126  
   127  func (s *BaseSuiteUnpatched) SetUpTest(c *gc.C) {
   128  	s.IsolationSuite.SetUpTest(c)
   129  
   130  	s.initEnv(c)
   131  	s.initInst(c)
   132  	s.initNet(c)
   133  }
   134  
   135  func (s *BaseSuiteUnpatched) initEnv(c *gc.C) {
   136  	s.Env = &environ{
   137  		name: "lxd",
   138  	}
   139  	cfg := s.NewConfig(c, nil)
   140  	s.setConfig(c, cfg)
   141  }
   142  
   143  func (s *BaseSuiteUnpatched) initInst(c *gc.C) {
   144  	tools := []*coretools.Tools{
   145  		{
   146  			Version: version.Binary{Arch: arch.AMD64, Series: "trusty"},
   147  			URL:     "https://example.org/amd",
   148  		},
   149  		{
   150  			Version: version.Binary{Arch: arch.ARM64, Series: "trusty"},
   151  			URL:     "https://example.org/arm",
   152  		},
   153  	}
   154  
   155  	cons := constraints.Value{
   156  	// nothing
   157  	}
   158  
   159  	instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(cons, cons, "trusty", "")
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	err = instanceConfig.SetTools(coretools.List{
   163  		tools[0],
   164  	})
   165  	c.Assert(err, jc.ErrorIsNil)
   166  	instanceConfig.AuthorizedKeys = s.Config.AuthorizedKeys()
   167  
   168  	userData, err := providerinit.ComposeUserData(instanceConfig, nil, lxdRenderer{})
   169  	c.Assert(err, jc.ErrorIsNil)
   170  
   171  	s.Hardware = &lxdclient.InstanceHardware{
   172  		Architecture: arch.ARM64,
   173  		NumCores:     1,
   174  		MemoryMB:     3750,
   175  	}
   176  	var archName string = arch.ARM64
   177  	var numCores uint64 = 1
   178  	var memoryMB uint64 = 3750
   179  	s.HWC = &instance.HardwareCharacteristics{
   180  		Arch:     &archName,
   181  		CpuCores: &numCores,
   182  		Mem:      &memoryMB,
   183  	}
   184  
   185  	s.Metadata = map[string]string{ // userdata
   186  		tags.JujuIsController: "true",
   187  		tags.JujuController:   s.Config.ControllerUUID(),
   188  		tags.JujuModel:        s.Config.UUID(),
   189  		metadataKeyCloudInit:  string(userData),
   190  	}
   191  	s.Addresses = []network.Address{{
   192  		Value: "10.0.0.1",
   193  		Type:  network.IPv4Address,
   194  		Scope: network.ScopeCloudLocal,
   195  	}}
   196  	s.Instance = s.NewInstance(c, "spam")
   197  	s.RawInstance = s.Instance.raw
   198  	s.InstName = s.Prefix + "machine-spam"
   199  
   200  	s.StartInstArgs = environs.StartInstanceParams{
   201  		InstanceConfig: instanceConfig,
   202  		Tools:          tools,
   203  		Constraints:    cons,
   204  	}
   205  }
   206  
   207  func (s *BaseSuiteUnpatched) initNet(c *gc.C) {
   208  	s.Ports = []network.PortRange{{
   209  		FromPort: 80,
   210  		ToPort:   80,
   211  		Protocol: "tcp",
   212  	}}
   213  }
   214  
   215  func (s *BaseSuiteUnpatched) setConfig(c *gc.C, cfg *config.Config) {
   216  	s.Config = cfg
   217  	ecfg, err := newValidConfig(cfg, configDefaults)
   218  	c.Assert(err, jc.ErrorIsNil)
   219  	s.EnvConfig = ecfg
   220  	uuid := cfg.UUID()
   221  	s.Env.uuid = uuid
   222  	s.Env.ecfg = s.EnvConfig
   223  	s.Prefix = "juju-" + uuid + "-"
   224  }
   225  
   226  func (s *BaseSuiteUnpatched) NewConfig(c *gc.C, updates testing.Attrs) *config.Config {
   227  	if updates == nil {
   228  		updates = make(testing.Attrs)
   229  	}
   230  	var err error
   231  	cfg := testing.ModelConfig(c)
   232  	cfg, err = cfg.Apply(ConfigAttrs)
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	if raw := updates[cfgNamespace]; raw == nil || raw.(string) == "" {
   235  		updates[cfgNamespace] = cfg.Name()
   236  	}
   237  	cfg, err = cfg.Apply(updates)
   238  	c.Assert(err, jc.ErrorIsNil)
   239  	return cfg
   240  }
   241  
   242  func (s *BaseSuiteUnpatched) UpdateConfig(c *gc.C, attrs map[string]interface{}) {
   243  	cfg, err := s.Config.Apply(attrs)
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	s.setConfig(c, cfg)
   246  }
   247  
   248  func (s *BaseSuiteUnpatched) NewRawInstance(c *gc.C, name string) *lxdclient.Instance {
   249  	metadata := make(map[string]string)
   250  	for k, v := range s.Metadata {
   251  		metadata[k] = v
   252  	}
   253  	summary := lxdclient.InstanceSummary{
   254  		Name:     name,
   255  		Status:   lxdclient.StatusRunning,
   256  		Hardware: *s.Hardware,
   257  		Metadata: metadata,
   258  	}
   259  	instanceSpec := lxdclient.InstanceSpec{
   260  		Name:      name,
   261  		Profiles:  []string{},
   262  		Ephemeral: false,
   263  		Metadata:  metadata,
   264  	}
   265  	return lxdclient.NewInstance(summary, &instanceSpec)
   266  }
   267  
   268  func (s *BaseSuiteUnpatched) NewInstance(c *gc.C, name string) *environInstance {
   269  	raw := s.NewRawInstance(c, name)
   270  	return newInstance(raw, s.Env)
   271  }
   272  
   273  func (s *BaseSuiteUnpatched) IsRunningLocally(c *gc.C) bool {
   274  	restore := gitjujutesting.PatchEnvPathPrepend(s.osPathOrig)
   275  	defer restore()
   276  
   277  	running, err := lxdclient.IsRunningLocally()
   278  	c.Assert(err, jc.ErrorIsNil)
   279  	return running
   280  }
   281  
   282  type BaseSuite struct {
   283  	BaseSuiteUnpatched
   284  
   285  	Stub       *gitjujutesting.Stub
   286  	Client     *StubClient
   287  	Firewaller *stubFirewaller
   288  	Common     *stubCommon
   289  	Policy     *stubPolicy
   290  }
   291  
   292  func (s *BaseSuite) SetUpSuite(c *gc.C) {
   293  	s.BaseSuiteUnpatched.SetUpSuite(c)
   294  	// Do this *before* s.initEnv() gets called in BaseSuiteUnpatched.SetUpTest
   295  	s.PatchValue(&asNonLocal, s.asNonLocal)
   296  }
   297  
   298  func (s *BaseSuite) SetUpTest(c *gc.C) {
   299  	s.BaseSuiteUnpatched.SetUpTest(c)
   300  
   301  	s.Stub = &gitjujutesting.Stub{}
   302  	s.Client = &StubClient{Stub: s.Stub}
   303  	s.Firewaller = &stubFirewaller{stub: s.Stub}
   304  	s.Common = &stubCommon{stub: s.Stub}
   305  	s.Policy = &stubPolicy{stub: s.Stub}
   306  
   307  	// Patch out all expensive external deps.
   308  	s.Env.raw = &rawProvider{
   309  		lxdInstances:   s.Client,
   310  		lxdImages:      s.Client,
   311  		Firewaller:     s.Firewaller,
   312  		policyProvider: s.Policy,
   313  	}
   314  	s.Env.base = s.Common
   315  }
   316  
   317  func (s *BaseSuite) CheckNoAPI(c *gc.C) {
   318  	s.Stub.CheckCalls(c, nil)
   319  }
   320  
   321  func (s *BaseSuite) asNonLocal(clientCfg lxdclient.Config) (lxdclient.Config, error) {
   322  	if s.Stub == nil {
   323  		return clientCfg, nil
   324  	}
   325  	s.Stub.AddCall("asNonLocal", clientCfg)
   326  	if err := s.Stub.NextErr(); err != nil {
   327  		return clientCfg, errors.Trace(err)
   328  	}
   329  
   330  	return clientCfg, nil
   331  }
   332  
   333  func NewBaseConfig(c *gc.C) *config.Config {
   334  	var err error
   335  	cfg := testing.ModelConfig(c)
   336  
   337  	cfg, err = cfg.Apply(ConfigAttrs)
   338  	c.Assert(err, jc.ErrorIsNil)
   339  
   340  	cfg, err = cfg.Apply(map[string]interface{}{
   341  		// Default the namespace to the env name.
   342  		cfgNamespace: cfg.Name(),
   343  	})
   344  	c.Assert(err, jc.ErrorIsNil)
   345  
   346  	return cfg
   347  }
   348  
   349  func NewCustomBaseConfig(c *gc.C, updates map[string]interface{}) *config.Config {
   350  	if updates == nil {
   351  		updates = make(testing.Attrs)
   352  	}
   353  
   354  	cfg := NewBaseConfig(c)
   355  
   356  	cfg, err := cfg.Apply(updates)
   357  	c.Assert(err, jc.ErrorIsNil)
   358  
   359  	return cfg
   360  }
   361  
   362  type ConfigValues struct {
   363  	Namespace  string
   364  	RemoteURL  string
   365  	ClientCert string
   366  	ClientKey  string
   367  	ServerCert string
   368  }
   369  
   370  func (cv ConfigValues) CheckCert(c *gc.C) {
   371  	certPEM := []byte(cv.ClientCert)
   372  	keyPEM := []byte(cv.ClientKey)
   373  
   374  	_, err := tls.X509KeyPair(certPEM, keyPEM)
   375  	c.Check(err, jc.ErrorIsNil)
   376  
   377  	block, remainder := pem.Decode(certPEM)
   378  	c.Check(block.Type, gc.Equals, "CERTIFICATE")
   379  	c.Check(remainder, gc.HasLen, 0)
   380  
   381  	block, remainder = pem.Decode(keyPEM)
   382  	c.Check(block.Type, gc.Equals, "RSA PRIVATE KEY")
   383  	c.Check(remainder, gc.HasLen, 0)
   384  
   385  	if cv.ServerCert != "" {
   386  		block, remainder = pem.Decode([]byte(cv.ServerCert))
   387  		c.Check(block.Type, gc.Equals, "CERTIFICATE")
   388  		c.Check(remainder, gc.HasLen, 1)
   389  	}
   390  }
   391  
   392  type Config struct {
   393  	*environConfig
   394  }
   395  
   396  func NewConfig(cfg *config.Config) *Config {
   397  	ecfg := newConfig(cfg)
   398  	return &Config{ecfg}
   399  }
   400  
   401  func NewValidConfig(cfg *config.Config) (*Config, error) {
   402  	ecfg, err := newValidConfig(cfg, nil)
   403  	return &Config{ecfg}, err
   404  }
   405  
   406  func NewValidDefaultConfig(cfg *config.Config) (*Config, error) {
   407  	ecfg, err := newValidConfig(cfg, configDefaults)
   408  	return &Config{ecfg}, err
   409  }
   410  
   411  func (ecfg *Config) Values(c *gc.C) (ConfigValues, map[string]interface{}) {
   412  	c.Assert(ecfg.attrs, jc.DeepEquals, ecfg.UnknownAttrs())
   413  
   414  	var values ConfigValues
   415  	extras := make(map[string]interface{})
   416  	for k, v := range ecfg.attrs {
   417  		switch k {
   418  		case cfgNamespace:
   419  			values.Namespace = v.(string)
   420  		case cfgRemoteURL:
   421  			values.RemoteURL = v.(string)
   422  		case cfgClientCert:
   423  			values.ClientCert = v.(string)
   424  		case cfgClientKey:
   425  			values.ClientKey = v.(string)
   426  		case cfgServerPEMCert:
   427  			values.ServerCert = v.(string)
   428  		default:
   429  			extras[k] = v
   430  		}
   431  	}
   432  	return values, extras
   433  }
   434  
   435  func (ecfg *Config) Apply(c *gc.C, updates map[string]interface{}) *Config {
   436  	cfg, err := ecfg.Config.Apply(updates)
   437  	c.Assert(err, jc.ErrorIsNil)
   438  	return NewConfig(cfg)
   439  }
   440  
   441  func (ecfg *Config) Validate() error {
   442  	return ecfg.validate()
   443  }
   444  
   445  func (ecfg *Config) ClientConfig() (lxdclient.Config, error) {
   446  	return ecfg.clientConfig()
   447  }
   448  
   449  func (ecfg *Config) UpdateForClientConfig(clientCfg lxdclient.Config) (*Config, error) {
   450  	updated, err := ecfg.updateForClientConfig(clientCfg)
   451  	return &Config{updated}, err
   452  }
   453  
   454  type stubCommon struct {
   455  	stub *gitjujutesting.Stub
   456  
   457  	BootstrapResult *environs.BootstrapResult
   458  }
   459  
   460  func (sc *stubCommon) BootstrapEnv(ctx environs.BootstrapContext, params environs.BootstrapParams) (*environs.BootstrapResult, error) {
   461  	sc.stub.AddCall("Bootstrap", ctx, params)
   462  	if err := sc.stub.NextErr(); err != nil {
   463  		return nil, errors.Trace(err)
   464  	}
   465  
   466  	return sc.BootstrapResult, nil
   467  }
   468  
   469  func (sc *stubCommon) DestroyEnv() error {
   470  	sc.stub.AddCall("Destroy")
   471  	if err := sc.stub.NextErr(); err != nil {
   472  		return errors.Trace(err)
   473  	}
   474  
   475  	return nil
   476  }
   477  
   478  type stubPolicy struct {
   479  	stub *gitjujutesting.Stub
   480  
   481  	Arches []string
   482  }
   483  
   484  func (s *stubPolicy) SupportedArchitectures() ([]string, error) {
   485  	s.stub.AddCall("SupportedArchitectures")
   486  	if err := s.stub.NextErr(); err != nil {
   487  		return nil, errors.Trace(err)
   488  	}
   489  
   490  	return s.Arches, nil
   491  }
   492  
   493  type StubClient struct {
   494  	*gitjujutesting.Stub
   495  
   496  	Insts []lxdclient.Instance
   497  	Inst  *lxdclient.Instance
   498  }
   499  
   500  func (conn *StubClient) Instances(prefix string, statuses ...string) ([]lxdclient.Instance, error) {
   501  	conn.AddCall("Instances", prefix, statuses)
   502  	if err := conn.NextErr(); err != nil {
   503  		return nil, errors.Trace(err)
   504  	}
   505  
   506  	return conn.Insts, nil
   507  }
   508  
   509  func (conn *StubClient) AddInstance(spec lxdclient.InstanceSpec) (*lxdclient.Instance, error) {
   510  	conn.AddCall("AddInstance", spec)
   511  	if err := conn.NextErr(); err != nil {
   512  		return nil, errors.Trace(err)
   513  	}
   514  
   515  	return conn.Inst, nil
   516  }
   517  
   518  func (conn *StubClient) RemoveInstances(prefix string, ids ...string) error {
   519  	conn.AddCall("RemoveInstances", prefix, ids)
   520  	if err := conn.NextErr(); err != nil {
   521  		return errors.Trace(err)
   522  	}
   523  
   524  	return nil
   525  }
   526  
   527  func (conn *StubClient) EnsureImageExists(series string, _ []lxdclient.Remote, _ func(string)) error {
   528  	conn.AddCall("EnsureImageExists", series)
   529  	if err := conn.NextErr(); err != nil {
   530  		return errors.Trace(err)
   531  	}
   532  
   533  	return nil
   534  }
   535  
   536  func (conn *StubClient) Addresses(name string) ([]network.Address, error) {
   537  	conn.AddCall("Addresses", name)
   538  	if err := conn.NextErr(); err != nil {
   539  		return nil, errors.Trace(err)
   540  	}
   541  
   542  	return []network.Address{network.Address{
   543  		Value: "10.0.0.1",
   544  		Type:  network.IPv4Address,
   545  		Scope: network.ScopeCloudLocal,
   546  	}}, nil
   547  }
   548  
   549  // TODO(ericsnow) Move stubFirewaller to environs/testing or provider/common/testing.
   550  
   551  type stubFirewaller struct {
   552  	stub *gitjujutesting.Stub
   553  
   554  	PortRanges []network.PortRange
   555  }
   556  
   557  func (fw *stubFirewaller) Ports(fwname string) ([]network.PortRange, error) {
   558  	fw.stub.AddCall("Ports", fwname)
   559  	if err := fw.stub.NextErr(); err != nil {
   560  		return nil, errors.Trace(err)
   561  	}
   562  
   563  	return fw.PortRanges, nil
   564  }
   565  
   566  func (fw *stubFirewaller) OpenPorts(fwname string, ports ...network.PortRange) error {
   567  	fw.stub.AddCall("OpenPorts", fwname, ports)
   568  	if err := fw.stub.NextErr(); err != nil {
   569  		return errors.Trace(err)
   570  	}
   571  
   572  	return nil
   573  }
   574  
   575  func (fw *stubFirewaller) ClosePorts(fwname string, ports ...network.PortRange) error {
   576  	fw.stub.AddCall("ClosePorts", fwname, ports)
   577  	if err := fw.stub.NextErr(); err != nil {
   578  		return errors.Trace(err)
   579  	}
   580  
   581  	return nil
   582  }