github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/provider/azure/instancetype_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 gc "launchpad.net/gocheck" 8 "launchpad.net/gwacl" 9 10 "github.com/juju/juju/constraints" 11 "github.com/juju/juju/environs" 12 "github.com/juju/juju/environs/imagemetadata" 13 "github.com/juju/juju/environs/instances" 14 "github.com/juju/juju/environs/simplestreams" 15 "github.com/juju/juju/environs/testing" 16 ) 17 18 type instanceTypeSuite struct { 19 providerSuite 20 } 21 22 var _ = gc.Suite(&instanceTypeSuite{}) 23 24 func (s *instanceTypeSuite) SetUpTest(c *gc.C) { 25 s.providerSuite.SetUpTest(c) 26 s.PatchValue(&imagemetadata.DefaultBaseURL, "") 27 s.PatchValue(&signedImageDataOnly, false) 28 } 29 30 // setDummyStorage injects the local provider's fake storage implementation 31 // into the given environment, so that tests can manipulate storage as if it 32 // were real. 33 func (s *instanceTypeSuite) setDummyStorage(c *gc.C, env *azureEnviron) { 34 closer, storage, _ := testing.CreateLocalTestStorage(c) 35 env.storage = storage 36 s.AddCleanup(func(c *gc.C) { closer.Close() }) 37 } 38 39 func (*instanceTypeSuite) TestNewPreferredTypesAcceptsNil(c *gc.C) { 40 types := newPreferredTypes(nil) 41 42 c.Check(types, gc.HasLen, 0) 43 c.Check(types.Len(), gc.Equals, 0) 44 } 45 46 func (*instanceTypeSuite) TestNewPreferredTypesRepresentsInput(c *gc.C) { 47 availableTypes := []gwacl.RoleSize{{Name: "Humongous", Cost: 123}} 48 49 types := newPreferredTypes(availableTypes) 50 51 c.Assert(types, gc.HasLen, len(availableTypes)) 52 c.Check(types[0], gc.Equals, &availableTypes[0]) 53 c.Check(types.Len(), gc.Equals, len(availableTypes)) 54 } 55 56 func (*instanceTypeSuite) TestNewPreferredTypesSortsByCost(c *gc.C) { 57 availableTypes := []gwacl.RoleSize{ 58 {Name: "Excessive", Cost: 12}, 59 {Name: "Ridiculous", Cost: 99}, 60 {Name: "Modest", Cost: 3}, 61 } 62 63 types := newPreferredTypes(availableTypes) 64 65 c.Assert(types, gc.HasLen, len(availableTypes)) 66 // We end up with machine types sorted by ascending cost. 67 c.Check(types[0].Name, gc.Equals, "Modest") 68 c.Check(types[1].Name, gc.Equals, "Excessive") 69 c.Check(types[2].Name, gc.Equals, "Ridiculous") 70 } 71 72 func (*instanceTypeSuite) TestLessComparesCost(c *gc.C) { 73 types := preferredTypes{ 74 {Name: "Cheap", Cost: 1}, 75 {Name: "Posh", Cost: 200}, 76 } 77 78 c.Check(types.Less(0, 1), gc.Equals, true) 79 c.Check(types.Less(1, 0), gc.Equals, false) 80 } 81 82 func (*instanceTypeSuite) TestSwapSwitchesEntries(c *gc.C) { 83 types := preferredTypes{ 84 {Name: "First"}, 85 {Name: "Last"}, 86 } 87 88 types.Swap(0, 1) 89 90 c.Check(types[0].Name, gc.Equals, "Last") 91 c.Check(types[1].Name, gc.Equals, "First") 92 } 93 94 func (*instanceTypeSuite) TestSwapIsCommutative(c *gc.C) { 95 types := preferredTypes{ 96 {Name: "First"}, 97 {Name: "Last"}, 98 } 99 100 types.Swap(1, 0) 101 102 c.Check(types[0].Name, gc.Equals, "Last") 103 c.Check(types[1].Name, gc.Equals, "First") 104 } 105 106 func (*instanceTypeSuite) TestSwapLeavesOtherEntriesIntact(c *gc.C) { 107 types := preferredTypes{ 108 {Name: "A"}, 109 {Name: "B"}, 110 {Name: "C"}, 111 {Name: "D"}, 112 } 113 114 types.Swap(1, 2) 115 116 c.Check(types[0].Name, gc.Equals, "A") 117 c.Check(types[1].Name, gc.Equals, "C") 118 c.Check(types[2].Name, gc.Equals, "B") 119 c.Check(types[3].Name, gc.Equals, "D") 120 } 121 122 func (*instanceTypeSuite) TestSufficesAcceptsNilRequirement(c *gc.C) { 123 types := preferredTypes{} 124 c.Check(types.suffices(0, nil), gc.Equals, true) 125 } 126 127 func (*instanceTypeSuite) TestSufficesAcceptsMetRequirement(c *gc.C) { 128 types := preferredTypes{} 129 var expectation uint64 = 100 130 c.Check(types.suffices(expectation+1, &expectation), gc.Equals, true) 131 } 132 133 func (*instanceTypeSuite) TestSufficesAcceptsExactRequirement(c *gc.C) { 134 types := preferredTypes{} 135 var expectation uint64 = 100 136 c.Check(types.suffices(expectation+1, &expectation), gc.Equals, true) 137 } 138 139 func (*instanceTypeSuite) TestSufficesRejectsUnmetRequirement(c *gc.C) { 140 types := preferredTypes{} 141 var expectation uint64 = 100 142 c.Check(types.suffices(expectation-1, &expectation), gc.Equals, false) 143 } 144 145 func (*instanceTypeSuite) TestSatisfiesComparesCPUCores(c *gc.C) { 146 types := preferredTypes{} 147 var desiredCores uint64 = 5 148 constraint := constraints.Value{CpuCores: &desiredCores} 149 150 // A machine with fewer cores than required does not satisfy... 151 machine := gwacl.RoleSize{CpuCores: desiredCores - 1} 152 c.Check(types.satisfies(&machine, constraint), gc.Equals, false) 153 // ...Even if it would, given more cores. 154 machine.CpuCores = desiredCores 155 c.Check(types.satisfies(&machine, constraint), gc.Equals, true) 156 } 157 158 func (*instanceTypeSuite) TestSatisfiesComparesMem(c *gc.C) { 159 types := preferredTypes{} 160 var desiredMem uint64 = 37 161 constraint := constraints.Value{Mem: &desiredMem} 162 163 // A machine with less memory than required does not satisfy... 164 machine := gwacl.RoleSize{Mem: desiredMem - 1} 165 c.Check(types.satisfies(&machine, constraint), gc.Equals, false) 166 // ...Even if it would, given more memory. 167 machine.Mem = desiredMem 168 c.Check(types.satisfies(&machine, constraint), gc.Equals, true) 169 } 170 171 func (*instanceTypeSuite) TestDefaultToBaselineSpecSetsMimimumMem(c *gc.C) { 172 c.Check( 173 *defaultToBaselineSpec(constraints.Value{}).Mem, 174 gc.Equals, 175 uint64(defaultMem)) 176 } 177 178 func (*instanceTypeSuite) TestDefaultToBaselineSpecLeavesOriginalIntact(c *gc.C) { 179 original := constraints.Value{} 180 defaultToBaselineSpec(original) 181 c.Check(original.Mem, gc.IsNil) 182 } 183 184 func (*instanceTypeSuite) TestDefaultToBaselineSpecLeavesLowerMemIntact(c *gc.C) { 185 const low = 100 * gwacl.MB 186 var value uint64 = low 187 c.Check( 188 defaultToBaselineSpec(constraints.Value{Mem: &value}).Mem, 189 gc.Equals, 190 &value) 191 c.Check(value, gc.Equals, uint64(low)) 192 } 193 194 func (*instanceTypeSuite) TestDefaultToBaselineSpecLeavesHigherMemIntact(c *gc.C) { 195 const high = 100 * gwacl.MB 196 var value uint64 = high 197 c.Check( 198 defaultToBaselineSpec(constraints.Value{Mem: &value}).Mem, 199 gc.Equals, 200 &value) 201 c.Check(value, gc.Equals, uint64(high)) 202 } 203 204 func (*instanceTypeSuite) TestSelectMachineTypeReturnsErrorIfNoMatch(c *gc.C) { 205 var lots uint64 = 1000000000000 206 _, err := selectMachineType(nil, constraints.Value{Mem: &lots}) 207 c.Assert(err, gc.NotNil) 208 c.Check(err, gc.ErrorMatches, "no machine type matches constraints mem=100000*[MGT]") 209 } 210 211 func (*instanceTypeSuite) TestSelectMachineTypeReturnsCheapestMatch(c *gc.C) { 212 var desiredCores uint64 = 50 213 availableTypes := []gwacl.RoleSize{ 214 // Cheap, but not up to our requirements. 215 {Name: "Panda", CpuCores: desiredCores / 2, Cost: 10}, 216 // Exactly what we need, but not the cheapest match. 217 {Name: "LFA", CpuCores: desiredCores, Cost: 200}, 218 // Much more power than we need, but actually cheaper. 219 {Name: "Lambo", CpuCores: 2 * desiredCores, Cost: 100}, 220 // Way out of our league. 221 {Name: "Veyron", CpuCores: 10 * desiredCores, Cost: 500}, 222 } 223 224 choice, err := selectMachineType(availableTypes, constraints.Value{CpuCores: &desiredCores}) 225 c.Assert(err, gc.IsNil) 226 227 // Out of these options, selectMachineType picks not the first; not 228 // the cheapest; not the biggest; not the last; but the cheapest type 229 // of machine that meets requirements. 230 c.Check(choice.Name, gc.Equals, "Lambo") 231 } 232 233 func (s *instanceTypeSuite) setupEnvWithDummyMetadata(c *gc.C) *azureEnviron { 234 envAttrs := makeAzureConfigMap(c) 235 envAttrs["location"] = "West US" 236 env := makeEnvironWithConfig(c, envAttrs) 237 s.setDummyStorage(c, env) 238 images := []*imagemetadata.ImageMetadata{ 239 { 240 Id: "image-id", 241 VirtType: "Hyper-V", 242 Arch: "amd64", 243 RegionName: "West US", 244 Endpoint: "https://management.core.windows.net/", 245 }, 246 } 247 makeTestMetadata(c, env, "precise", "West US", images) 248 return env 249 } 250 251 func (s *instanceTypeSuite) TestFindMatchingImagesReturnsErrorIfNoneFound(c *gc.C) { 252 env := s.setupEnvWithDummyMetadata(c) 253 _, err := findMatchingImages(env, "West US", "saucy", []string{"amd64"}) 254 c.Assert(err, gc.NotNil) 255 c.Assert(err, gc.ErrorMatches, "no OS images found for location .*") 256 } 257 258 func (s *instanceTypeSuite) TestFindMatchingImagesReturnsReleasedImages(c *gc.C) { 259 env := s.setupEnvWithDummyMetadata(c) 260 images, err := findMatchingImages(env, "West US", "precise", []string{"amd64"}) 261 c.Assert(err, gc.IsNil) 262 c.Assert(images, gc.HasLen, 1) 263 c.Check(images[0].Id, gc.Equals, "image-id") 264 } 265 266 func (s *instanceTypeSuite) TestFindMatchingImagesReturnsDailyImages(c *gc.C) { 267 envAttrs := makeAzureConfigMap(c) 268 envAttrs["image-stream"] = "daily" 269 envAttrs["location"] = "West US" 270 env := makeEnvironWithConfig(c, envAttrs) 271 s.setDummyStorage(c, env) 272 images := []*imagemetadata.ImageMetadata{ 273 { 274 Id: "image-id", 275 VirtType: "Hyper-V", 276 Arch: "amd64", 277 RegionName: "West US", 278 Endpoint: "https://management.core.windows.net/", 279 Stream: "daily", 280 }, 281 } 282 makeTestMetadata(c, env, "precise", "West US", images) 283 images, err := findMatchingImages(env, "West US", "precise", []string{"amd64"}) 284 c.Assert(err, gc.IsNil) 285 c.Assert(images, gc.HasLen, 1) 286 c.Assert(images[0].Id, gc.Equals, "image-id") 287 } 288 289 func (*instanceTypeSuite) TestNewInstanceTypeConvertsRoleSize(c *gc.C) { 290 roleSize := gwacl.RoleSize{ 291 Name: "Outrageous", 292 CpuCores: 128, 293 Mem: 4 * gwacl.TB, 294 OSDiskSpaceCloud: 48 * gwacl.TB, 295 OSDiskSpaceVirt: 50 * gwacl.TB, 296 MaxDataDisks: 20, 297 Cost: 999999500, 298 } 299 vtype := "Hyper-V" 300 var cpupower uint64 = 100 301 expectation := instances.InstanceType{ 302 Id: roleSize.Name, 303 Name: roleSize.Name, 304 CpuCores: roleSize.CpuCores, 305 Mem: roleSize.Mem, 306 RootDisk: roleSize.OSDiskSpaceVirt, 307 Cost: roleSize.Cost, 308 VirtType: &vtype, 309 CpuPower: &cpupower, 310 } 311 c.Assert(newInstanceType(roleSize), gc.DeepEquals, expectation) 312 } 313 314 func (s *instanceTypeSuite) TestListInstanceTypesAcceptsNil(c *gc.C) { 315 env := s.setupEnvWithDummyMetadata(c) 316 types, err := listInstanceTypes(env, nil) 317 c.Assert(err, gc.IsNil) 318 c.Check(types, gc.HasLen, 0) 319 } 320 321 func (s *instanceTypeSuite) TestListInstanceTypesMaintainsOrder(c *gc.C) { 322 roleSizes := []gwacl.RoleSize{ 323 {Name: "Biggish"}, 324 {Name: "Tiny"}, 325 {Name: "Huge"}, 326 {Name: "Miniscule"}, 327 } 328 329 expectation := make([]instances.InstanceType, len(roleSizes)) 330 for index, roleSize := range roleSizes { 331 expectation[index] = newInstanceType(roleSize) 332 expectation[index].Arches = []string{"amd64"} 333 } 334 335 env := s.setupEnvWithDummyMetadata(c) 336 types, err := listInstanceTypes(env, roleSizes) 337 c.Assert(err, gc.IsNil) 338 c.Assert(types, gc.DeepEquals, expectation) 339 } 340 341 func (*instanceTypeSuite) TestFindInstanceSpecFailsImpossibleRequest(c *gc.C) { 342 impossibleConstraint := &instances.InstanceConstraint{ 343 Series: "precise", 344 Arches: []string{"axp"}, 345 } 346 347 env := makeEnviron(c) 348 _, err := findInstanceSpec(env, impossibleConstraint) 349 c.Assert(err, gc.NotNil) 350 c.Check(err, gc.ErrorMatches, "no OS images found for .*") 351 } 352 353 func makeTestMetadata(c *gc.C, env environs.Environ, series, location string, im []*imagemetadata.ImageMetadata) { 354 cloudSpec := simplestreams.CloudSpec{ 355 Region: location, 356 Endpoint: "https://management.core.windows.net/", 357 } 358 err := imagemetadata.MergeAndWriteMetadata(series, im, &cloudSpec, env.Storage()) 359 c.Assert(err, gc.IsNil) 360 } 361 362 var findInstanceSpecTests = []struct { 363 series string 364 cons string 365 itype string 366 }{ 367 { 368 series: "precise", 369 cons: "mem=7G cpu-cores=2", 370 itype: "Large", 371 }, { 372 series: "precise", 373 cons: "instance-type=ExtraLarge", 374 }, 375 } 376 377 func (s *instanceTypeSuite) TestFindInstanceSpec(c *gc.C) { 378 env := s.setupEnvWithDummyMetadata(c) 379 for i, t := range findInstanceSpecTests { 380 c.Logf("test %d", i) 381 382 cons := constraints.MustParse(t.cons) 383 constraints := &instances.InstanceConstraint{ 384 Region: "West US", 385 Series: t.series, 386 Arches: []string{"amd64"}, 387 Constraints: cons, 388 } 389 390 // Find a matching instance type and image. 391 spec, err := findInstanceSpec(env, constraints) 392 c.Assert(err, gc.IsNil) 393 394 // We got the instance type we described in our constraints, and 395 // the image returned by (the fake) simplestreams. 396 if cons.HasInstanceType() { 397 c.Check(spec.InstanceType.Name, gc.Equals, *cons.InstanceType) 398 } else { 399 c.Check(spec.InstanceType.Name, gc.Equals, t.itype) 400 } 401 c.Check(spec.Image.Id, gc.Equals, "image-id") 402 } 403 } 404 405 func (s *instanceTypeSuite) TestFindInstanceSpecFindsMatch(c *gc.C) { 406 env := s.setupEnvWithDummyMetadata(c) 407 408 // We'll tailor our constraints to describe one particular Azure 409 // instance type: 410 aim := gwacl.RoleNameMap["Large"] 411 constraints := &instances.InstanceConstraint{ 412 Region: "West US", 413 Series: "precise", 414 Arches: []string{"amd64"}, 415 Constraints: constraints.Value{ 416 CpuCores: &aim.CpuCores, 417 Mem: &aim.Mem, 418 }, 419 } 420 421 // Find a matching instance type and image. 422 spec, err := findInstanceSpec(env, constraints) 423 c.Assert(err, gc.IsNil) 424 425 // We got the instance type we described in our constraints, and 426 // the image returned by (the fake) simplestreams. 427 c.Check(spec.InstanceType.Name, gc.Equals, aim.Name) 428 c.Check(spec.Image.Id, gc.Equals, "image-id") 429 } 430 431 func (s *instanceTypeSuite) TestFindInstanceSpecSetsBaseline(c *gc.C) { 432 env := s.setupEnvWithDummyMetadata(c) 433 434 // findInstanceSpec sets baseline constraints, so that it won't pick 435 // ExtraSmall (which is too small for routine tasks) if you fail to 436 // set sufficient hardware constraints. 437 anyInstanceType := &instances.InstanceConstraint{ 438 Region: "West US", 439 Series: "precise", 440 Arches: []string{"amd64"}, 441 } 442 443 spec, err := findInstanceSpec(env, anyInstanceType) 444 c.Assert(err, gc.IsNil) 445 446 c.Check(spec.InstanceType.Name, gc.Equals, "Small") 447 } 448 449 func (s *instanceTypeSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 450 env := s.setupEnvWithDummyMetadata(c) 451 cons := constraints.MustParse("instance-type=Large") 452 placement := "" 453 err := env.PrecheckInstance("precise", cons, placement) 454 c.Assert(err, gc.IsNil) 455 } 456 457 func (s *instanceTypeSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 458 env := s.setupEnvWithDummyMetadata(c) 459 cons := constraints.MustParse("instance-type=Super") 460 placement := "" 461 err := env.PrecheckInstance("precise", cons, placement) 462 c.Assert(err, gc.ErrorMatches, `invalid Azure instance "Super" specified`) 463 }