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  }