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