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