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