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 }