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  }