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  }