github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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 "strings" 8 9 "github.com/juju/errors" 10 jc "github.com/juju/testing/checkers" 11 "github.com/juju/utils/set" 12 gc "gopkg.in/check.v1" 13 "launchpad.net/gwacl" 14 15 "github.com/juju/juju/constraints" 16 "github.com/juju/juju/environs/imagemetadata" 17 "github.com/juju/juju/environs/instances" 18 "github.com/juju/juju/environs/testing" 19 ) 20 21 type instanceTypeSuite struct { 22 providerSuite 23 } 24 25 var _ = gc.Suite(&instanceTypeSuite{}) 26 27 // setDummyStorage injects the local provider's fake storage implementation 28 // into the given environment, so that tests can manipulate storage as if it 29 // were real. 30 func (s *instanceTypeSuite) setDummyStorage(c *gc.C, env *azureEnviron) { 31 closer, storage, _ := testing.CreateLocalTestStorage(c) 32 env.storage = storage 33 s.AddCleanup(func(c *gc.C) { closer.Close() }) 34 } 35 36 func (*instanceTypeSuite) TestDefaultToBaselineSpecSetsMimimumMem(c *gc.C) { 37 c.Check( 38 *defaultToBaselineSpec(constraints.Value{}).Mem, 39 gc.Equals, 40 uint64(defaultMem)) 41 } 42 43 func (*instanceTypeSuite) TestDefaultToBaselineSpecLeavesOriginalIntact(c *gc.C) { 44 original := constraints.Value{} 45 defaultToBaselineSpec(original) 46 c.Check(original.Mem, gc.IsNil) 47 } 48 49 func (*instanceTypeSuite) TestDefaultToBaselineSpecLeavesLowerMemIntact(c *gc.C) { 50 const low = 100 * gwacl.MB 51 var value uint64 = low 52 c.Check( 53 defaultToBaselineSpec(constraints.Value{Mem: &value}).Mem, 54 gc.Equals, 55 &value) 56 c.Check(value, gc.Equals, uint64(low)) 57 } 58 59 func (*instanceTypeSuite) TestDefaultToBaselineSpecLeavesHigherMemIntact(c *gc.C) { 60 const high = 100 * gwacl.MB 61 var value uint64 = high 62 c.Check( 63 defaultToBaselineSpec(constraints.Value{Mem: &value}).Mem, 64 gc.Equals, 65 &value) 66 c.Check(value, gc.Equals, uint64(high)) 67 } 68 69 func (s *instanceTypeSuite) TestSelectMachineTypeReturnsErrorIfNoMatch(c *gc.C) { 70 var lots uint64 = 1000000000000 71 env := s.setupEnvWithDummyMetadata(c) 72 _, err := selectMachineType(env, constraints.Value{Mem: &lots}) 73 c.Assert(err, gc.NotNil) 74 c.Check(err, gc.ErrorMatches, `no instance types in West US matching constraints "mem=1000000000000M"`) 75 } 76 77 func (s *instanceTypeSuite) TestSelectMachineTypeReturnsCheapestMatch(c *gc.C) { 78 var desiredCores uint64 = 50 79 80 s.PatchValue(&getAvailableRoleSizes, func(*azureEnviron) (set.Strings, error) { 81 return set.NewStrings("Panda", "LFA", "Lambo", "Veyron"), nil 82 }) 83 84 costs := map[string]uint64{ 85 "Panda": 10, 86 "LFA": 200, 87 "Lambo": 100, 88 "Veyron": 500, 89 } 90 s.PatchValue(&roleSizeCost, func(region, roleSize string) (uint64, error) { 91 return costs[roleSize], nil 92 }) 93 s.PatchValue(&gwacl.RoleSizes, []gwacl.RoleSize{ 94 // Cheap, but not up to our requirements. 95 {Name: "Panda", CpuCores: desiredCores / 2}, 96 // Exactly what we need, but not the cheapest match. 97 {Name: "LFA", CpuCores: desiredCores}, 98 // Much more power than we need, but actually cheaper. 99 {Name: "Lambo", CpuCores: 2 * desiredCores}, 100 // Way out of our league. 101 {Name: "Veyron", CpuCores: 10 * desiredCores}, 102 }) 103 104 env := s.setupEnvWithDummyMetadata(c) 105 choice, err := selectMachineType(env, constraints.Value{CpuCores: &desiredCores}) 106 c.Assert(err, jc.ErrorIsNil) 107 108 // Out of these options, selectMachineType picks not the first; not 109 // the cheapest; not the biggest; not the last; but the cheapest type 110 // of machine that meets requirements. 111 c.Check(choice.Name, gc.Equals, "Lambo") 112 } 113 114 func (s *instanceTypeSuite) setupEnvWithDummyMetadata(c *gc.C) *azureEnviron { 115 envAttrs := makeAzureConfigMap(c) 116 envAttrs["location"] = "West US" 117 env := makeEnvironWithConfig(c, envAttrs) 118 s.setDummyStorage(c, env) 119 images := []*imagemetadata.ImageMetadata{ 120 { 121 Id: "image-id", 122 VirtType: "Hyper-V", 123 Arch: "amd64", 124 RegionName: "West US", 125 Endpoint: "https://management.core.windows.net/", 126 }, 127 } 128 s.makeTestMetadata(c, "precise", "West US", images) 129 return env 130 } 131 132 func (s *instanceTypeSuite) TestFindMatchingImagesReturnsErrorIfNoneFound(c *gc.C) { 133 env := s.setupEnvWithDummyMetadata(c) 134 _, err := findMatchingImages(env, "West US", "saucy", []string{"amd64"}) 135 c.Assert(err, gc.NotNil) 136 c.Assert(err, gc.ErrorMatches, "no OS images found for location .*") 137 } 138 139 func (s *instanceTypeSuite) TestFindMatchingImagesReturnsReleasedImages(c *gc.C) { 140 env := s.setupEnvWithDummyMetadata(c) 141 images, err := findMatchingImages(env, "West US", "precise", []string{"amd64"}) 142 c.Assert(err, jc.ErrorIsNil) 143 c.Assert(images, gc.HasLen, 1) 144 c.Check(images[0].Id, gc.Equals, "image-id") 145 } 146 147 func (s *instanceTypeSuite) TestFindMatchingImagesReturnsDailyImages(c *gc.C) { 148 envAttrs := makeAzureConfigMap(c) 149 envAttrs["image-stream"] = "daily" 150 envAttrs["location"] = "West US" 151 env := makeEnvironWithConfig(c, envAttrs) 152 s.setDummyStorage(c, env) 153 images := []*imagemetadata.ImageMetadata{ 154 { 155 Id: "image-id", 156 VirtType: "Hyper-V", 157 Arch: "amd64", 158 RegionName: "West US", 159 Endpoint: "https://management.core.windows.net/", 160 Stream: "daily", 161 }, 162 } 163 s.makeTestMetadata(c, "precise", "West US", images) 164 images, err := findMatchingImages(env, "West US", "precise", []string{"amd64"}) 165 c.Assert(err, jc.ErrorIsNil) 166 c.Assert(images, gc.HasLen, 1) 167 c.Assert(images[0].Id, gc.Equals, "image-id") 168 } 169 170 func (s *instanceTypeSuite) TestNewInstanceTypeConvertsRoleSize(c *gc.C) { 171 const expectedRegion = "expected" 172 s.PatchValue(&roleSizeCost, func(region, roleSize string) (uint64, error) { 173 c.Assert(region, gc.Equals, expectedRegion) 174 return 999999500, nil 175 }) 176 roleSize := gwacl.RoleSize{ 177 Name: "Outrageous", 178 CpuCores: 128, 179 Mem: 4 * gwacl.TB, 180 OSDiskSpace: 48 * gwacl.TB, 181 TempDiskSpace: 50 * gwacl.TB, 182 MaxDataDisks: 20, 183 } 184 vtype := "Hyper-V" 185 expectation := instances.InstanceType{ 186 Id: roleSize.Name, 187 Name: roleSize.Name, 188 CpuCores: roleSize.CpuCores, 189 Mem: roleSize.Mem, 190 RootDisk: roleSize.OSDiskSpace, 191 Cost: 999999500, 192 VirtType: &vtype, 193 } 194 instType, err := newInstanceType(roleSize, expectedRegion) 195 c.Assert(err, jc.ErrorIsNil) 196 c.Assert(instType, gc.DeepEquals, expectation) 197 } 198 199 func (s *instanceTypeSuite) TestListInstanceTypesAGVNetRoleSizeFiltering(c *gc.C) { 200 // Old environments with a virtual network tied to an affinity group 201 // will cause D and G series to be filtered out. 202 expectation := make([]instances.InstanceType, 0, len(gwacl.RoleSizes)) 203 for _, roleSize := range gwacl.RoleSizes { 204 if strings.HasPrefix(roleSize.Name, "Basic_") { 205 continue 206 } 207 if strings.HasPrefix(roleSize.Name, "Standard_") { 208 continue 209 } 210 instanceType, err := newInstanceType(roleSize, "West US") 211 c.Assert(err, jc.ErrorIsNil) 212 instanceType.Arches = []string{"amd64"} 213 expectation = append(expectation, instanceType) 214 } 215 216 s.PatchValue(&getVirtualNetwork, func(*azureEnviron) (*gwacl.VirtualNetworkSite, error) { 217 return &gwacl.VirtualNetworkSite{Name: "vnet", AffinityGroup: "ag"}, nil 218 }) 219 env := s.setupEnvWithDummyMetadata(c) 220 types, err := listInstanceTypes(env) 221 c.Assert(err, jc.ErrorIsNil) 222 c.Assert(types, gc.DeepEquals, expectation) 223 } 224 225 func (s *instanceTypeSuite) TestListInstanceTypesNoVNetNoRoleSizeFiltering(c *gc.C) { 226 // If there's no virtual network yet, we'll create one with a 227 // location rather than an affinity group; thus we do not limit 228 // which instance types are available. 229 expectation := make([]instances.InstanceType, 0, len(gwacl.RoleSizes)) 230 for _, roleSize := range gwacl.RoleSizes { 231 if strings.HasPrefix(roleSize.Name, "Basic_") { 232 continue 233 } 234 instanceType, err := newInstanceType(roleSize, "West US") 235 c.Assert(err, jc.ErrorIsNil) 236 instanceType.Arches = []string{"amd64"} 237 expectation = append(expectation, instanceType) 238 } 239 240 s.PatchValue(&getVirtualNetwork, func(*azureEnviron) (*gwacl.VirtualNetworkSite, error) { 241 return nil, errors.NotFoundf("virtual network") 242 }) 243 env := s.setupEnvWithDummyMetadata(c) 244 types, err := listInstanceTypes(env) 245 c.Assert(err, jc.ErrorIsNil) 246 c.Assert(types, gc.DeepEquals, expectation) 247 } 248 249 func (s *instanceTypeSuite) TestListInstanceTypesLocationFiltering(c *gc.C) { 250 available := set.NewStrings("Standard_D1") 251 s.PatchValue(&getAvailableRoleSizes, func(*azureEnviron) (set.Strings, error) { 252 return available, nil 253 }) 254 255 // If there's no virtual network yet, we'll create one with a 256 // location rather than an affinity group; thus we do not limit 257 // which instance types are available. 258 expectation := make([]instances.InstanceType, 0, len(gwacl.RoleSizes)) 259 for _, roleSize := range gwacl.RoleSizes { 260 if !available.Contains(roleSize.Name) { 261 continue 262 } 263 instanceType, err := newInstanceType(roleSize, "West US") 264 c.Assert(err, jc.ErrorIsNil) 265 instanceType.Arches = []string{"amd64"} 266 expectation = append(expectation, instanceType) 267 } 268 269 env := s.setupEnvWithDummyMetadata(c) 270 types, err := listInstanceTypes(env) 271 c.Assert(err, jc.ErrorIsNil) 272 c.Assert(types, gc.DeepEquals, expectation) 273 } 274 275 func (s *instanceTypeSuite) TestListInstanceTypesMaintainsOrder(c *gc.C) { 276 expectation := make([]instances.InstanceType, 0, len(gwacl.RoleSizes)) 277 for _, roleSize := range gwacl.RoleSizes { 278 if strings.HasPrefix(roleSize.Name, "Basic_") { 279 continue 280 } 281 instanceType, err := newInstanceType(roleSize, "West US") 282 c.Assert(err, jc.ErrorIsNil) 283 instanceType.Arches = []string{"amd64"} 284 expectation = append(expectation, instanceType) 285 } 286 287 env := s.setupEnvWithDummyMetadata(c) 288 types, err := listInstanceTypes(env) 289 c.Assert(err, jc.ErrorIsNil) 290 c.Assert(types, gc.DeepEquals, expectation) 291 } 292 293 func (s *instanceTypeSuite) TestFindInstanceSpecFailsImpossibleRequest(c *gc.C) { 294 impossibleConstraint := &instances.InstanceConstraint{ 295 Series: "precise", 296 Arches: []string{"axp"}, 297 } 298 299 env := s.setupEnvWithDummyMetadata(c) 300 _, err := findInstanceSpec(env, impossibleConstraint) 301 c.Assert(err, gc.NotNil) 302 c.Check(err, gc.ErrorMatches, "no OS images found for .*") 303 } 304 305 var findInstanceSpecTests = []struct { 306 series string 307 cons string 308 itype string 309 }{ 310 { 311 series: "precise", 312 cons: "mem=7G cpu-cores=2", 313 itype: "Standard_D2", 314 }, { 315 series: "precise", 316 cons: "instance-type=ExtraLarge", 317 }, 318 } 319 320 func (s *instanceTypeSuite) TestFindInstanceSpec(c *gc.C) { 321 env := s.setupEnvWithDummyMetadata(c) 322 for i, t := range findInstanceSpecTests { 323 c.Logf("test %d", i) 324 325 cons := constraints.MustParse(t.cons) 326 constraints := &instances.InstanceConstraint{ 327 Region: "West US", 328 Series: t.series, 329 Arches: []string{"amd64"}, 330 Constraints: cons, 331 } 332 333 // Find a matching instance type and image. 334 spec, err := findInstanceSpec(env, constraints) 335 c.Assert(err, jc.ErrorIsNil) 336 337 // We got the instance type we described in our constraints, and 338 // the image returned by (the fake) simplestreams. 339 if cons.HasInstanceType() { 340 c.Check(spec.InstanceType.Name, gc.Equals, *cons.InstanceType) 341 } else { 342 c.Check(spec.InstanceType.Name, gc.Equals, t.itype) 343 } 344 c.Check(spec.Image.Id, gc.Equals, "image-id") 345 } 346 } 347 348 func (s *instanceTypeSuite) TestFindInstanceSpecFindsMatch(c *gc.C) { 349 env := s.setupEnvWithDummyMetadata(c) 350 351 // We'll tailor our constraints to describe one particular Azure 352 // instance type: 353 aim := roleSizeByName("Large") 354 constraints := &instances.InstanceConstraint{ 355 Region: "West US", 356 Series: "precise", 357 Arches: []string{"amd64"}, 358 Constraints: constraints.Value{ 359 CpuCores: &aim.CpuCores, 360 Mem: &aim.Mem, 361 }, 362 } 363 364 // Find a matching instance type and image. 365 spec, err := findInstanceSpec(env, constraints) 366 c.Assert(err, jc.ErrorIsNil) 367 368 // We got the instance type we described in our constraints, and 369 // the image returned by (the fake) simplestreams. 370 c.Check(spec.InstanceType.Name, gc.Equals, aim.Name) 371 c.Check(spec.Image.Id, gc.Equals, "image-id") 372 } 373 374 func (s *instanceTypeSuite) TestFindInstanceSpecSetsBaseline(c *gc.C) { 375 env := s.setupEnvWithDummyMetadata(c) 376 377 // findInstanceSpec sets baseline constraints, so that it won't pick 378 // ExtraSmall (which is too small for routine tasks) if you fail to 379 // set sufficient hardware constraints. 380 anyInstanceType := &instances.InstanceConstraint{ 381 Region: "West US", 382 Series: "precise", 383 Arches: []string{"amd64"}, 384 } 385 386 spec, err := findInstanceSpec(env, anyInstanceType) 387 c.Assert(err, jc.ErrorIsNil) 388 389 c.Check(spec.InstanceType.Name, gc.Equals, "Small") 390 } 391 392 func (s *instanceTypeSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 393 env := s.setupEnvWithDummyMetadata(c) 394 cons := constraints.MustParse("instance-type=Large") 395 placement := "" 396 err := env.PrecheckInstance("precise", cons, placement) 397 c.Assert(err, jc.ErrorIsNil) 398 } 399 400 func (s *instanceTypeSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 401 env := s.setupEnvWithDummyMetadata(c) 402 cons := constraints.MustParse("instance-type=Super") 403 placement := "" 404 err := env.PrecheckInstance("precise", cons, placement) 405 c.Assert(err, gc.ErrorMatches, `invalid instance type "Super"`) 406 }