github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/vsphere/environ_broker_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package vsphere_test
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"path"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/juju/testing"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils/arch"
    16  	"github.com/juju/version"
    17  	"github.com/vmware/govmomi/vim25/mo"
    18  	"github.com/vmware/govmomi/vim25/soap"
    19  	"golang.org/x/net/context"
    20  	gc "gopkg.in/check.v1"
    21  
    22  	"github.com/juju/juju/cloudconfig/instancecfg"
    23  	"github.com/juju/juju/core/constraints"
    24  	"github.com/juju/juju/core/instance"
    25  	"github.com/juju/juju/core/status"
    26  	"github.com/juju/juju/environs"
    27  	"github.com/juju/juju/environs/config"
    28  	callcontext "github.com/juju/juju/environs/context"
    29  	"github.com/juju/juju/provider/common"
    30  	"github.com/juju/juju/provider/vsphere"
    31  	"github.com/juju/juju/provider/vsphere/internal/ovatest"
    32  	"github.com/juju/juju/provider/vsphere/internal/vsphereclient"
    33  	coretesting "github.com/juju/juju/testing"
    34  	coretools "github.com/juju/juju/tools"
    35  )
    36  
    37  type environBrokerSuite struct {
    38  	EnvironFixture
    39  	statusCallbackStub testing.Stub
    40  }
    41  
    42  var _ = gc.Suite(&environBrokerSuite{})
    43  
    44  func (s *environBrokerSuite) SetUpTest(c *gc.C) {
    45  	s.EnvironFixture.SetUpTest(c)
    46  	s.statusCallbackStub.ResetCalls()
    47  
    48  	s.client.computeResources = []*mo.ComputeResource{
    49  		newComputeResource("z1"),
    50  		newComputeResource("z2"),
    51  	}
    52  
    53  	s.client.createdVirtualMachine = buildVM("new-vm").vm()
    54  }
    55  
    56  func (s *environBrokerSuite) createStartInstanceArgs(c *gc.C) environs.StartInstanceParams {
    57  	var cons constraints.Value
    58  	instanceConfig, err := instancecfg.NewBootstrapInstanceConfig(
    59  		coretesting.FakeControllerConfig(), cons, cons, "trusty", "",
    60  	)
    61  	c.Assert(err, jc.ErrorIsNil)
    62  
    63  	setInstanceConfigAuthorizedKeys(c, instanceConfig)
    64  	tools := setInstanceConfigTools(c, instanceConfig)
    65  
    66  	return environs.StartInstanceParams{
    67  		AvailabilityZone: "z1",
    68  		ControllerUUID:   instanceConfig.Controller.Config.ControllerUUID(),
    69  		InstanceConfig:   instanceConfig,
    70  		Tools:            tools,
    71  		Constraints:      cons,
    72  		StatusCallback: func(status status.Status, info string, data map[string]interface{}) error {
    73  			s.statusCallbackStub.AddCall("StatusCallback", status, info, data)
    74  			return s.statusCallbackStub.NextErr()
    75  		},
    76  	}
    77  }
    78  
    79  func setInstanceConfigTools(c *gc.C, instanceConfig *instancecfg.InstanceConfig) coretools.List {
    80  	tools := []*coretools.Tools{{
    81  		Version: version.Binary{
    82  			Number: version.MustParse("1.2.3"),
    83  			Arch:   arch.AMD64,
    84  			Series: "trusty",
    85  		},
    86  		URL: "https://example.org",
    87  	}}
    88  	err := instanceConfig.SetTools(tools[:1])
    89  	c.Assert(err, jc.ErrorIsNil)
    90  	return tools
    91  }
    92  
    93  func setInstanceConfigAuthorizedKeys(c *gc.C, instanceConfig *instancecfg.InstanceConfig) {
    94  	config := fakeConfig(c)
    95  	instanceConfig.AuthorizedKeys = config.AuthorizedKeys()
    96  }
    97  
    98  func (s *environBrokerSuite) TestStartInstance(c *gc.C) {
    99  	startInstArgs := s.createStartInstanceArgs(c)
   100  	startInstArgs.InstanceConfig.Tags = map[string]string{
   101  		"k0": "v0",
   102  		"k1": "v1",
   103  	}
   104  
   105  	result, err := s.env.StartInstance(s.callCtx, startInstArgs)
   106  	c.Assert(err, jc.ErrorIsNil)
   107  	c.Assert(result, gc.NotNil)
   108  	c.Assert(result.Instance, gc.NotNil)
   109  	c.Assert(result.Instance.Id(), gc.Equals, instance.Id("new-vm"))
   110  
   111  	s.client.CheckCallNames(c, "ComputeResources", "CreateVirtualMachine", "Close")
   112  	call := s.client.Calls()[1]
   113  	c.Assert(call.Args, gc.HasLen, 2)
   114  	c.Assert(call.Args[0], gc.Implements, new(context.Context))
   115  	c.Assert(call.Args[1], gc.FitsTypeOf, vsphereclient.CreateVirtualMachineParams{})
   116  
   117  	createVMArgs := call.Args[1].(vsphereclient.CreateVirtualMachineParams)
   118  	c.Assert(createVMArgs.UserData, gc.Not(gc.Equals), "")
   119  	c.Assert(createVMArgs.ReadOVA, gc.NotNil)
   120  	readOVA := createVMArgs.ReadOVA
   121  	createVMArgs.UserData = ""
   122  	createVMArgs.Constraints = constraints.Value{}
   123  	createVMArgs.UpdateProgress = nil
   124  	createVMArgs.Clock = nil
   125  	createVMArgs.ReadOVA = nil
   126  	createVMArgs.NetworkDevices = []vsphereclient.NetworkDevice{}
   127  	c.Assert(createVMArgs, jc.DeepEquals, vsphereclient.CreateVirtualMachineParams{
   128  		Name:                   "juju-f75cba-0",
   129  		Folder:                 `Juju Controller (deadbeef-1bad-500d-9000-4b1d0d06f00d)/Model "testmodel" (2d02eeac-9dbb-11e4-89d3-123b93f75cba)`,
   130  		VMDKDirectory:          "juju-vmdks/deadbeef-1bad-500d-9000-4b1d0d06f00d",
   131  		Series:                 startInstArgs.Tools.OneSeries(),
   132  		OVASHA256:              ovatest.FakeOVASHA256(),
   133  		Metadata:               startInstArgs.InstanceConfig.Tags,
   134  		ComputeResource:        s.client.computeResources[0],
   135  		UpdateProgressInterval: 5 * time.Second,
   136  		EnableDiskUUID:         true,
   137  	})
   138  
   139  	ovaLocation, ovaReadCloser, err := readOVA()
   140  	c.Assert(err, jc.ErrorIsNil)
   141  	defer ovaReadCloser.Close()
   142  	c.Assert(
   143  		ovaLocation, gc.Equals,
   144  		s.imageServer.URL+"/server/releases/trusty/release-20150305/ubuntu-14.04-server-cloudimg-amd64.ova",
   145  	)
   146  	ovaBody, err := ioutil.ReadAll(ovaReadCloser)
   147  	c.Assert(err, jc.ErrorIsNil)
   148  	c.Assert(ovaBody, jc.DeepEquals, ovatest.FakeOVAContents())
   149  }
   150  
   151  func (s *environBrokerSuite) TestStartInstanceNetwork(c *gc.C) {
   152  	env, err := s.provider.Open(environs.OpenParams{
   153  		Cloud: fakeCloudSpec(),
   154  		Config: fakeConfig(c, coretesting.Attrs{
   155  			"primary-network":    "foo",
   156  			"external-network":   "bar",
   157  			"image-metadata-url": s.imageServer.URL,
   158  		}),
   159  	})
   160  	c.Assert(err, jc.ErrorIsNil)
   161  
   162  	result, err := env.StartInstance(s.callCtx, s.createStartInstanceArgs(c))
   163  	c.Assert(err, jc.ErrorIsNil)
   164  	c.Assert(result, gc.NotNil)
   165  
   166  	call := s.client.Calls()[1]
   167  	createVMArgs := call.Args[1].(vsphereclient.CreateVirtualMachineParams)
   168  	c.Assert(createVMArgs.NetworkDevices, gc.HasLen, 2)
   169  	c.Assert(createVMArgs.NetworkDevices[0].Network, gc.Equals, "foo")
   170  	c.Assert(createVMArgs.NetworkDevices[1].Network, gc.Equals, "bar")
   171  }
   172  
   173  func (s *environBrokerSuite) TestStartInstanceLongModelName(c *gc.C) {
   174  	env, err := s.provider.Open(environs.OpenParams{
   175  		Cloud: fakeCloudSpec(),
   176  		Config: fakeConfig(c, coretesting.Attrs{
   177  			"name":               "supercalifragilisticexpialidocious",
   178  			"image-metadata-url": s.imageServer.URL,
   179  		}),
   180  	})
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	startInstArgs := s.createStartInstanceArgs(c)
   183  	_, err = env.StartInstance(s.callCtx, startInstArgs)
   184  	c.Assert(err, jc.ErrorIsNil)
   185  	call := s.client.Calls()[1]
   186  	createVMArgs := call.Args[1].(vsphereclient.CreateVirtualMachineParams)
   187  	// The model name in the folder name should be truncated
   188  	// so that the final part of the model name is 80 characters.
   189  	c.Assert(path.Base(createVMArgs.Folder), gc.HasLen, 80)
   190  	c.Assert(createVMArgs.Folder, gc.Equals,
   191  		`Juju Controller (deadbeef-1bad-500d-9000-4b1d0d06f00d)/Model "supercalifragilisticexpialidociou" (2d02eeac-9dbb-11e4-89d3-123b93f75cba)`,
   192  	)
   193  }
   194  
   195  func (s *environBrokerSuite) TestStartInstanceDiskUUIDDisabled(c *gc.C) {
   196  	env, err := s.provider.Open(environs.OpenParams{
   197  		Cloud: fakeCloudSpec(),
   198  		Config: fakeConfig(c, coretesting.Attrs{
   199  			"enable-disk-uuid":   false,
   200  			"image-metadata-url": s.imageServer.URL,
   201  		}),
   202  	})
   203  	c.Assert(err, jc.ErrorIsNil)
   204  
   205  	result, err := env.StartInstance(s.callCtx, s.createStartInstanceArgs(c))
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	c.Assert(result, gc.NotNil)
   208  
   209  	call := s.client.Calls()[1]
   210  	createVMArgs := call.Args[1].(vsphereclient.CreateVirtualMachineParams)
   211  	c.Assert(createVMArgs.EnableDiskUUID, gc.Equals, false)
   212  }
   213  
   214  func (s *environBrokerSuite) TestStartInstanceWithUnsupportedConstraints(c *gc.C) {
   215  	startInstArgs := s.createStartInstanceArgs(c)
   216  	startInstArgs.Tools[0].Version.Arch = "someArch"
   217  	_, err := s.env.StartInstance(s.callCtx, startInstArgs)
   218  	c.Assert(err, gc.ErrorMatches, "no matching images found for given constraints: .*")
   219  	c.Assert(err, jc.Satisfies, environs.IsAvailabilityZoneIndependent)
   220  }
   221  
   222  // if tools for multiple architectures are available, provider should filter tools by arch of the selected image
   223  func (s *environBrokerSuite) TestStartInstanceFilterToolByArch(c *gc.C) {
   224  	startInstArgs := s.createStartInstanceArgs(c)
   225  	tools := []*coretools.Tools{{
   226  		Version: version.Binary{Arch: arch.I386, Series: "trusty"},
   227  		URL:     "https://example.org",
   228  	}, {
   229  		Version: version.Binary{Arch: arch.AMD64, Series: "trusty"},
   230  		URL:     "https://example.org",
   231  	}}
   232  
   233  	// Setting tools to I386, but provider should update them to AMD64,
   234  	// because our fake simplestream server returns only an AMD64 image.
   235  	startInstArgs.Tools = tools
   236  	err := startInstArgs.InstanceConfig.SetTools(coretools.List{
   237  		tools[0],
   238  	})
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	res, err := s.env.StartInstance(s.callCtx, startInstArgs)
   242  	c.Assert(err, jc.ErrorIsNil)
   243  	c.Assert(*res.Hardware.Arch, gc.Equals, arch.AMD64)
   244  	c.Assert(startInstArgs.InstanceConfig.AgentVersion().Arch, gc.Equals, arch.AMD64)
   245  }
   246  
   247  func (s *environBrokerSuite) TestStartInstanceDefaultConstraintsApplied(c *gc.C) {
   248  	startInstArgs := s.createStartInstanceArgs(c)
   249  	res, err := s.env.StartInstance(s.callCtx, startInstArgs)
   250  	c.Assert(err, jc.ErrorIsNil)
   251  
   252  	var (
   253  		arch     = "amd64"
   254  		rootDisk = common.MinRootDiskSizeGiB("trusty") * 1024
   255  	)
   256  	c.Assert(res.Hardware, jc.DeepEquals, &instance.HardwareCharacteristics{
   257  		Arch:     &arch,
   258  		RootDisk: &rootDisk,
   259  	})
   260  }
   261  
   262  func (s *environBrokerSuite) TestStartInstanceCustomConstraintsApplied(c *gc.C) {
   263  	var (
   264  		cpuCores uint64 = 4
   265  		cpuPower uint64 = 2001
   266  		mem      uint64 = 2002
   267  		rootDisk uint64 = 10003
   268  	)
   269  	startInstArgs := s.createStartInstanceArgs(c)
   270  	startInstArgs.Constraints.CpuCores = &cpuCores
   271  	startInstArgs.Constraints.CpuPower = &cpuPower
   272  	startInstArgs.Constraints.Mem = &mem
   273  	startInstArgs.Constraints.RootDisk = &rootDisk
   274  
   275  	res, err := s.env.StartInstance(s.callCtx, startInstArgs)
   276  	c.Assert(err, jc.ErrorIsNil)
   277  
   278  	arch := "amd64"
   279  	c.Assert(res.Hardware, jc.DeepEquals, &instance.HardwareCharacteristics{
   280  		Arch:     &arch,
   281  		CpuCores: &cpuCores,
   282  		CpuPower: &cpuPower,
   283  		Mem:      &mem,
   284  		RootDisk: &rootDisk,
   285  	})
   286  }
   287  
   288  func (s *environBrokerSuite) TestStartInstanceCallsFinishMachineConfig(c *gc.C) {
   289  	startInstArgs := s.createStartInstanceArgs(c)
   290  	s.PatchValue(&vsphere.FinishInstanceConfig, func(mcfg *instancecfg.InstanceConfig, cfg *config.Config) (err error) {
   291  		return errors.New("FinishMachineConfig called")
   292  	})
   293  	_, err := s.env.StartInstance(s.callCtx, startInstArgs)
   294  	c.Assert(err, gc.ErrorMatches, "FinishMachineConfig called")
   295  }
   296  
   297  func (s *environBrokerSuite) TestStartInstanceDefaultDiskSizeIsUsedForSmallConstraintValue(c *gc.C) {
   298  	startInstArgs := s.createStartInstanceArgs(c)
   299  	rootDisk := uint64(1000)
   300  	startInstArgs.Constraints.RootDisk = &rootDisk
   301  	res, err := s.env.StartInstance(s.callCtx, startInstArgs)
   302  	c.Assert(err, jc.ErrorIsNil)
   303  	c.Assert(*res.Hardware.RootDisk, gc.Equals, common.MinRootDiskSizeGiB("trusty")*uint64(1024))
   304  }
   305  
   306  func (s *environBrokerSuite) TestStartInstanceSelectZone(c *gc.C) {
   307  	startInstArgs := s.createStartInstanceArgs(c)
   308  	startInstArgs.AvailabilityZone = "z2"
   309  	_, err := s.env.StartInstance(s.callCtx, startInstArgs)
   310  	c.Assert(err, jc.ErrorIsNil)
   311  
   312  	s.client.CheckCallNames(c, "ComputeResources", "CreateVirtualMachine", "Close")
   313  	call := s.client.Calls()[1]
   314  	c.Assert(call.Args, gc.HasLen, 2)
   315  	c.Assert(call.Args[0], gc.Implements, new(context.Context))
   316  	c.Assert(call.Args[1], gc.FitsTypeOf, vsphereclient.CreateVirtualMachineParams{})
   317  
   318  	createVMArgs := call.Args[1].(vsphereclient.CreateVirtualMachineParams)
   319  	c.Assert(createVMArgs.ComputeResource, jc.DeepEquals, s.client.computeResources[1])
   320  }
   321  
   322  func (s *environBrokerSuite) TestStartInstanceFailsWithAvailabilityZone(c *gc.C) {
   323  	s.client.SetErrors(nil, errors.New("nope"))
   324  	startInstArgs := s.createStartInstanceArgs(c)
   325  	_, err := s.env.StartInstance(s.callCtx, startInstArgs)
   326  	c.Assert(err, gc.Not(jc.Satisfies), environs.IsAvailabilityZoneIndependent)
   327  
   328  	s.client.CheckCallNames(c, "ComputeResources", "CreateVirtualMachine", "Close")
   329  	createVMCall1 := s.client.Calls()[1]
   330  	createVMArgs1 := createVMCall1.Args[1].(vsphereclient.CreateVirtualMachineParams)
   331  	c.Assert(createVMArgs1.ComputeResource, jc.DeepEquals, s.client.computeResources[0])
   332  }
   333  
   334  func (s *environBrokerSuite) TestStartInstanceDatastore(c *gc.C) {
   335  	cfg := s.env.Config()
   336  	cfg, err := cfg.Apply(map[string]interface{}{
   337  		"datastore": "datastore0",
   338  	})
   339  	c.Assert(err, jc.ErrorIsNil)
   340  	err = s.env.SetConfig(cfg)
   341  	c.Assert(err, jc.ErrorIsNil)
   342  
   343  	_, err = s.env.StartInstance(s.callCtx, s.createStartInstanceArgs(c))
   344  	c.Assert(err, jc.ErrorIsNil)
   345  
   346  	call := s.client.Calls()[1]
   347  	createVMArgs := call.Args[1].(vsphereclient.CreateVirtualMachineParams)
   348  	c.Assert(createVMArgs.Datastore, gc.Equals, "datastore0")
   349  }
   350  
   351  func (s *environBrokerSuite) TestStopInstances(c *gc.C) {
   352  	err := s.env.StopInstances(s.callCtx, "vm-0", "vm-1")
   353  	c.Assert(err, jc.ErrorIsNil)
   354  
   355  	var paths []string
   356  	s.client.CheckCallNames(c, "RemoveVirtualMachines", "RemoveVirtualMachines", "Close")
   357  	for i := 0; i < 2; i++ {
   358  		args := s.client.Calls()[i].Args
   359  		paths = append(paths, args[1].(string))
   360  	}
   361  
   362  	// NOTE(axw) we must use SameContents, not DeepEquals, because
   363  	// we run the RemoveVirtualMachines calls concurrently.
   364  	c.Assert(paths, jc.SameContents, []string{
   365  		`Juju Controller (*)/Model "testmodel" (2d02eeac-9dbb-11e4-89d3-123b93f75cba)/vm-0`,
   366  		`Juju Controller (*)/Model "testmodel" (2d02eeac-9dbb-11e4-89d3-123b93f75cba)/vm-1`,
   367  	})
   368  }
   369  
   370  func (s *environBrokerSuite) TestStopInstancesOneFailure(c *gc.C) {
   371  	s.client.SetErrors(errors.New("bah"))
   372  	err := s.env.StopInstances(s.callCtx, "vm-0", "vm-1")
   373  
   374  	s.client.CheckCallNames(c, "RemoveVirtualMachines", "RemoveVirtualMachines", "Close")
   375  	vmName := path.Base(s.client.Calls()[0].Args[1].(string))
   376  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf("failed to stop instance %s: bah", vmName))
   377  }
   378  
   379  func (s *environBrokerSuite) TestStopInstancesMultipleFailures(c *gc.C) {
   380  	err1 := errors.New("bah")
   381  	err2 := errors.New("bleh")
   382  	s.client.SetErrors(err1, err2)
   383  	err := s.env.StopInstances(s.callCtx, "vm-0", "vm-1")
   384  
   385  	s.client.CheckCallNames(c, "RemoveVirtualMachines", "RemoveVirtualMachines", "Close")
   386  	vmName1 := path.Base(s.client.Calls()[0].Args[1].(string))
   387  	if vmName1 == "vm-1" {
   388  		err1, err2 = err2, err1
   389  	}
   390  	c.Assert(err, gc.ErrorMatches, fmt.Sprintf(
   391  		`failed to stop instances \[vm-0 vm-1\]: \[%s %s\]`,
   392  		err1, err2,
   393  	))
   394  }
   395  
   396  func (s *environBrokerSuite) TestStartInstanceLoginErrorInvalidatesCreds(c *gc.C) {
   397  	s.dialStub.SetErrors(soap.WrapSoapFault(&soap.Fault{
   398  		Code:   "ServerFaultCode",
   399  		String: "You passed an incorrect user name or password, bucko.",
   400  	}))
   401  	var passedReason string
   402  	ctx := &callcontext.CloudCallContext{
   403  		InvalidateCredentialFunc: func(reason string) error {
   404  			passedReason = reason
   405  			return nil
   406  		},
   407  	}
   408  	_, err := s.env.StartInstance(ctx, s.createStartInstanceArgs(c))
   409  	c.Assert(err, gc.ErrorMatches, "dialing client: ServerFaultCode: You passed an incorrect user name or password, bucko.")
   410  	c.Assert(passedReason, gc.Equals, "cloud denied access")
   411  }
   412  
   413  func (s *environBrokerSuite) TestStartInstancePermissionError(c *gc.C) {
   414  	AssertInvalidatesCredential(c, s.client, func(ctx callcontext.ProviderCallContext) error {
   415  		_, err := s.env.StartInstance(ctx, s.createStartInstanceArgs(c))
   416  		return err
   417  	})
   418  }
   419  
   420  func (s *environBrokerSuite) TestStopInstancesPermissionError(c *gc.C) {
   421  	AssertInvalidatesCredential(c, s.client, func(ctx callcontext.ProviderCallContext) error {
   422  		return s.env.StopInstances(ctx, "vm-0")
   423  	})
   424  }