github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/environs/instances/instancetype_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instances
     5  
     6  import (
     7  	"sort"
     8  
     9  	jc "github.com/juju/testing/checkers"
    10  	gc "gopkg.in/check.v1"
    11  
    12  	"github.com/juju/juju/constraints"
    13  	"github.com/juju/juju/testing"
    14  )
    15  
    16  type instanceTypeSuite struct {
    17  	testing.BaseSuite
    18  }
    19  
    20  var _ = gc.Suite(&instanceTypeSuite{})
    21  
    22  var hvm = "hvm"
    23  
    24  // The instance types below do not necessarily reflect reality and are just
    25  // defined here for ease of testing special cases.
    26  var instanceTypes = []InstanceType{
    27  	{
    28  		Name:     "m1.small",
    29  		Arches:   []string{"amd64", "armhf"},
    30  		CpuCores: 1,
    31  		CpuPower: CpuPower(100),
    32  		Mem:      1740,
    33  		Cost:     60,
    34  		RootDisk: 8192,
    35  	}, {
    36  		Name:     "m1.medium",
    37  		Arches:   []string{"amd64", "armhf"},
    38  		CpuCores: 1,
    39  		CpuPower: CpuPower(200),
    40  		Mem:      3840,
    41  		Cost:     120,
    42  		RootDisk: 16384,
    43  	}, {
    44  		Name:     "m1.large",
    45  		Arches:   []string{"amd64"},
    46  		CpuCores: 2,
    47  		CpuPower: CpuPower(400),
    48  		Mem:      7680,
    49  		Cost:     240,
    50  		RootDisk: 32768,
    51  	}, {
    52  		Name:     "m1.xlarge",
    53  		Arches:   []string{"amd64"},
    54  		CpuCores: 4,
    55  		CpuPower: CpuPower(800),
    56  		Mem:      15360,
    57  		Cost:     480,
    58  	},
    59  	{
    60  		Name:     "t1.micro",
    61  		Arches:   []string{"amd64", "armhf"},
    62  		CpuCores: 1,
    63  		CpuPower: CpuPower(20),
    64  		Mem:      613,
    65  		Cost:     20,
    66  		RootDisk: 4096,
    67  	},
    68  	{
    69  		Name:     "c1.medium",
    70  		Arches:   []string{"amd64", "armhf"},
    71  		CpuCores: 2,
    72  		CpuPower: CpuPower(500),
    73  		Mem:      1740,
    74  		Cost:     145,
    75  		RootDisk: 8192,
    76  	}, {
    77  		Name:     "c1.xlarge",
    78  		Arches:   []string{"amd64"},
    79  		CpuCores: 8,
    80  		CpuPower: CpuPower(2000),
    81  		Mem:      7168,
    82  		Cost:     580,
    83  	},
    84  	{
    85  		Name:     "cc1.4xlarge",
    86  		Arches:   []string{"amd64"},
    87  		CpuCores: 8,
    88  		CpuPower: CpuPower(3350),
    89  		Mem:      23552,
    90  		Cost:     1300,
    91  		VirtType: &hvm,
    92  	}, {
    93  		Name:     "cc2.8xlarge",
    94  		Arches:   []string{"amd64"},
    95  		CpuCores: 16,
    96  		CpuPower: CpuPower(8800),
    97  		Mem:      61952,
    98  		Cost:     2400,
    99  		VirtType: &hvm,
   100  	}, {
   101  		Name:       "dep.small",
   102  		Arches:     []string{"amd64"},
   103  		CpuCores:   1,
   104  		CpuPower:   CpuPower(100),
   105  		Mem:        1740,
   106  		Cost:       60,
   107  		Deprecated: true,
   108  	}, {
   109  		Name:       "dep.medium",
   110  		Arches:     []string{"amd64"},
   111  		CpuCores:   2,
   112  		CpuPower:   CpuPower(200),
   113  		Mem:        4096,
   114  		Cost:       80,
   115  		Deprecated: true,
   116  	},
   117  }
   118  
   119  var getInstanceTypesTest = []struct {
   120  	about          string
   121  	cons           string
   122  	itypesToUse    []InstanceType
   123  	expectedItypes []string
   124  	arches         []string
   125  }{
   126  	{
   127  		about: "cores",
   128  		cons:  "cores=2",
   129  		expectedItypes: []string{
   130  			"c1.medium", "m1.large", "m1.xlarge", "c1.xlarge", "cc1.4xlarge",
   131  			"cc2.8xlarge",
   132  		},
   133  	}, {
   134  		about:          "cpu-power",
   135  		cons:           "cpu-power=2000",
   136  		expectedItypes: []string{"c1.xlarge", "cc1.4xlarge", "cc2.8xlarge"},
   137  	}, {
   138  		about: "mem",
   139  		cons:  "mem=4G",
   140  		expectedItypes: []string{
   141  			"m1.large", "m1.xlarge", "c1.xlarge", "cc1.4xlarge", "cc2.8xlarge",
   142  		},
   143  	}, {
   144  		about: "root-disk",
   145  		cons:  "root-disk=16G",
   146  		expectedItypes: []string{
   147  			"m1.medium", "m1.large", "m1.xlarge", "c1.xlarge", "cc1.4xlarge", "cc2.8xlarge",
   148  		},
   149  	}, {
   150  		about:          "arches filtered by constraint",
   151  		cons:           "cpu-power=100 arch=armhf",
   152  		expectedItypes: []string{"m1.small", "m1.medium", "c1.medium"},
   153  		arches:         []string{"armhf"},
   154  	},
   155  	{
   156  		about: "enough memory for mongodb if mem not specified",
   157  		cons:  "cores=4",
   158  		itypesToUse: []InstanceType{
   159  			{Id: "5", Name: "it-5", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 2},
   160  			{Id: "4", Name: "it-4", Arches: []string{"amd64"}, Mem: 2048, CpuCores: 4},
   161  			{Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 4},
   162  			{Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256, CpuCores: 4},
   163  			{Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4},
   164  		},
   165  		expectedItypes: []string{"it-3", "it-4"},
   166  	},
   167  	{
   168  		about: "small mem specified, use that even though less than needed for mongodb",
   169  		cons:  "mem=300M",
   170  		itypesToUse: []InstanceType{
   171  			{Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 2048},
   172  			{Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256},
   173  			{Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512},
   174  		},
   175  		expectedItypes: []string{"it-1", "it-3"},
   176  	},
   177  	{
   178  		about: "mem specified and match found",
   179  		cons:  "mem=4G arch=amd64",
   180  		itypesToUse: []InstanceType{
   181  			{Id: "4", Name: "it-4", Arches: []string{"armhf"}, Mem: 8096},
   182  			{Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 4096},
   183  			{Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 2048},
   184  			{Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512},
   185  		},
   186  		expectedItypes: []string{"it-3"},
   187  	},
   188  	{
   189  		about: "instance-type specified and match found",
   190  		cons:  "instance-type=it-3",
   191  		itypesToUse: []InstanceType{
   192  			{Id: "4", Name: "it-4", Arches: []string{"amd64"}, Mem: 8096},
   193  			{Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 4096},
   194  			{Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 2048},
   195  			{Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512},
   196  		},
   197  		expectedItypes: []string{"it-3"},
   198  	},
   199  	{
   200  		about: "largest mem available matching other constraints if mem not specified",
   201  		cons:  "cores=4",
   202  		itypesToUse: []InstanceType{
   203  			{Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 2},
   204  			{Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 256, CpuCores: 4},
   205  			{Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4},
   206  		},
   207  		expectedItypes: []string{"it-1"},
   208  	},
   209  	{
   210  		about: "largest mem available matching other constraints if mem not specified, cost is tie breaker",
   211  		cons:  "cores=4",
   212  		itypesToUse: []InstanceType{
   213  			{Id: "4", Name: "it-4", Arches: []string{"amd64"}, Mem: 1024, CpuCores: 2},
   214  			{Id: "3", Name: "it-3", Arches: []string{"amd64"}, Mem: 256, CpuCores: 4},
   215  			{Id: "2", Name: "it-2", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4, Cost: 50},
   216  			{Id: "1", Name: "it-1", Arches: []string{"amd64"}, Mem: 512, CpuCores: 4, Cost: 100},
   217  		},
   218  		expectedItypes: []string{"it-2"},
   219  	}, {
   220  		about:          "virt-type filtered by constraint",
   221  		cons:           "virt-type=hvm",
   222  		expectedItypes: []string{"cc1.4xlarge", "cc2.8xlarge"},
   223  		itypesToUse:    nil,
   224  	}, {
   225  		about:          "deprecated image type requested by name",
   226  		cons:           "instance-type=dep.small",
   227  		expectedItypes: []string{"dep.small"},
   228  	}, {
   229  		about:          "deprecated image type requested by name with constraints",
   230  		cons:           "instance-type=dep.small cpu-power=100",
   231  		expectedItypes: []string{"dep.small"},
   232  	},
   233  }
   234  
   235  func (s *instanceTypeSuite) TestGetMatchingInstanceTypes(c *gc.C) {
   236  	for i, t := range getInstanceTypesTest {
   237  		c.Logf("test %d: %s", i, t.about)
   238  		itypesToUse := t.itypesToUse
   239  		if itypesToUse == nil {
   240  			itypesToUse = instanceTypes
   241  		}
   242  		itypes, err := MatchingInstanceTypes(itypesToUse, "test", constraints.MustParse(t.cons))
   243  		c.Assert(err, jc.ErrorIsNil)
   244  		names := make([]string, len(itypes))
   245  		for i, itype := range itypes {
   246  			if len(t.arches) > 0 {
   247  				c.Check(itype.Arches, gc.DeepEquals, filterArches(itype.Arches, t.arches))
   248  			} else {
   249  				c.Check(len(itype.Arches) > 0, jc.IsTrue)
   250  			}
   251  			names[i] = itype.Name
   252  		}
   253  		c.Check(names, gc.DeepEquals, t.expectedItypes)
   254  	}
   255  }
   256  
   257  func (s *instanceTypeSuite) TestGetMatchingInstanceTypesErrors(c *gc.C) {
   258  	_, err := MatchingInstanceTypes(nil, "test", constraints.MustParse("cpu-power=9001"))
   259  	c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "cpu-power=9001"`)
   260  
   261  	_, err = MatchingInstanceTypes(instanceTypes, "test", constraints.MustParse("arch=i386 mem=8G"))
   262  	c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "arch=i386 mem=8192M"`)
   263  
   264  	_, err = MatchingInstanceTypes(instanceTypes, "test", constraints.MustParse("cores=9000"))
   265  	c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "cores=9000"`)
   266  
   267  	_, err = MatchingInstanceTypes(instanceTypes, "test", constraints.MustParse("mem=90000M"))
   268  	c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "mem=90000M"`)
   269  
   270  	_, err = MatchingInstanceTypes(instanceTypes, "test", constraints.MustParse("instance-type=dep.medium mem=8G"))
   271  	c.Check(err, gc.ErrorMatches, `no instance types in test matching constraints "instance-type=dep.medium mem=8192M"`)
   272  }
   273  
   274  var instanceTypeMatchTests = []struct {
   275  	cons   string
   276  	itype  string
   277  	arches []string
   278  }{
   279  	{"", "m1.small", []string{"amd64", "armhf"}},
   280  	{"", "m1.large", []string{"amd64"}},
   281  	{"cpu-power=100", "m1.small", []string{"amd64", "armhf"}},
   282  	{"arch=amd64", "m1.small", []string{"amd64"}},
   283  	{"cores=3", "m1.xlarge", []string{"amd64"}},
   284  	{"cpu-power=", "t1.micro", []string{"amd64", "armhf"}},
   285  	{"cpu-power=500", "c1.medium", []string{"amd64", "armhf"}},
   286  	{"cpu-power=2000", "c1.xlarge", []string{"amd64"}},
   287  	{"cpu-power=2001", "cc1.4xlarge", []string{"amd64"}},
   288  	{"mem=2G", "m1.medium", []string{"amd64", "armhf"}},
   289  
   290  	{"arch=i386", "m1.small", nil},
   291  	{"cpu-power=100", "t1.micro", nil},
   292  	{"cpu-power=9001", "cc2.8xlarge", nil},
   293  	{"mem=1G", "t1.micro", nil},
   294  	{"arch=armhf", "c1.xlarge", nil},
   295  }
   296  
   297  func (s *instanceTypeSuite) TestMatch(c *gc.C) {
   298  	for i, t := range instanceTypeMatchTests {
   299  		c.Logf("test %d", i)
   300  		cons := constraints.MustParse(t.cons)
   301  		var itype InstanceType
   302  		for _, itype = range instanceTypes {
   303  			if itype.Name == t.itype {
   304  				break
   305  			}
   306  		}
   307  		c.Assert(itype.Name, gc.Not(gc.Equals), "")
   308  		itype, match := itype.match(cons)
   309  		if len(t.arches) > 0 {
   310  			c.Check(match, jc.IsTrue)
   311  			expect := itype
   312  			expect.Arches = t.arches
   313  			c.Check(itype, gc.DeepEquals, expect)
   314  		} else {
   315  			c.Check(match, jc.IsFalse)
   316  			c.Check(itype, gc.DeepEquals, InstanceType{})
   317  		}
   318  	}
   319  }
   320  
   321  var byCostTests = []struct {
   322  	about          string
   323  	itypesToUse    []InstanceType
   324  	expectedItypes []string
   325  }{
   326  	{
   327  		about: "default to lowest cost",
   328  		itypesToUse: []InstanceType{
   329  			{Id: "2", Name: "it-2", CpuCores: 2, Mem: 4096, Cost: 240},
   330  			{Id: "1", Name: "it-1", CpuCores: 1, Mem: 2048, Cost: 241},
   331  		},
   332  		expectedItypes: []string{
   333  			"it-2", "it-1",
   334  		},
   335  	}, {
   336  		about: "when no cost associated, pick lowest ram",
   337  		itypesToUse: []InstanceType{
   338  			{Id: "2", Name: "it-2", CpuCores: 2, Mem: 4096},
   339  			{Id: "1", Name: "it-1", CpuCores: 1, Mem: 2048},
   340  		},
   341  		expectedItypes: []string{
   342  			"it-1", "it-2",
   343  		},
   344  	}, {
   345  		about: "when cost is the same, pick lowest ram",
   346  		itypesToUse: []InstanceType{
   347  			{Id: "2", Name: "it-2", CpuCores: 2, Mem: 4096, Cost: 240},
   348  			{Id: "1", Name: "it-1", CpuCores: 1, Mem: 2048, Cost: 240},
   349  		},
   350  		expectedItypes: []string{
   351  			"it-1", "it-2",
   352  		},
   353  	}, {
   354  		about: "when cost and ram is the same, pick lowest cpu power",
   355  		itypesToUse: []InstanceType{
   356  			{Id: "2", Name: "it-2", CpuCores: 2, CpuPower: CpuPower(200)},
   357  			{Id: "1", Name: "it-1", CpuCores: 1, CpuPower: CpuPower(100)},
   358  		},
   359  		expectedItypes: []string{
   360  			"it-1", "it-2",
   361  		},
   362  	}, {
   363  		about: "when cpu power is the same, pick the lowest cores",
   364  		itypesToUse: []InstanceType{
   365  			{Id: "2", Name: "it-2", CpuCores: 2, CpuPower: CpuPower(200)},
   366  			{Id: "1", Name: "it-1", CpuCores: 1, CpuPower: CpuPower(200)},
   367  		},
   368  		expectedItypes: []string{
   369  			"it-1", "it-2",
   370  		},
   371  	}, {
   372  		about: "when cpu power is missing in side a, pick the lowest cores",
   373  		itypesToUse: []InstanceType{
   374  			{Id: "2", Name: "it-2", CpuCores: 2, CpuPower: CpuPower(200)},
   375  			{Id: "1", Name: "it-1", CpuCores: 1},
   376  		},
   377  		expectedItypes: []string{
   378  			"it-1", "it-2",
   379  		},
   380  	}, {
   381  		about: "when cpu power is missing in side b, pick the lowest cores",
   382  		itypesToUse: []InstanceType{
   383  			{Id: "2", Name: "it-2", CpuCores: 2},
   384  			{Id: "1", Name: "it-1", CpuCores: 1, CpuPower: CpuPower(200)},
   385  		},
   386  		expectedItypes: []string{
   387  			"it-1", "it-2",
   388  		},
   389  	}, {
   390  		about: "when cpu cores is the same, pick the lowest root disk size",
   391  		itypesToUse: []InstanceType{
   392  			{Id: "2", Name: "it-2", CpuCores: 1, RootDisk: 8192},
   393  			{Id: "1", Name: "it-1", CpuCores: 1, RootDisk: 4096},
   394  		},
   395  		expectedItypes: []string{
   396  			"it-1", "it-2",
   397  		},
   398  	},
   399  }
   400  
   401  func (s *instanceTypeSuite) TestSortByCost(c *gc.C) {
   402  	for i, t := range byCostTests {
   403  		c.Logf("test %d: %s", i, t.about)
   404  		sort.Sort(byCost(t.itypesToUse))
   405  		names := make([]string, len(t.itypesToUse))
   406  		for i, itype := range t.itypesToUse {
   407  			names[i] = itype.Name
   408  		}
   409  		c.Check(names, gc.DeepEquals, t.expectedItypes)
   410  	}
   411  }