github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/constraints/constraints_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package constraints_test
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"strings"
    10  
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  	goyaml "gopkg.in/yaml.v2"
    14  
    15  	"github.com/juju/juju/core/constraints"
    16  	"github.com/juju/juju/core/instance"
    17  )
    18  
    19  type ConstraintsSuite struct{}
    20  
    21  var _ = gc.Suite(&ConstraintsSuite{})
    22  
    23  var parseConstraintsTests = []struct {
    24  	summary string
    25  	args    []string
    26  	err     string
    27  }{
    28  	// Simple errors.
    29  	{
    30  		summary: "nothing at all",
    31  	}, {
    32  		summary: "empty",
    33  		args:    []string{"     "},
    34  	}, {
    35  		summary: "complete nonsense",
    36  		args:    []string{"cheese"},
    37  		err:     `malformed constraint "cheese"`,
    38  	}, {
    39  		summary: "missing name",
    40  		args:    []string{"=cheese"},
    41  		err:     `malformed constraint "=cheese"`,
    42  	}, {
    43  		summary: "unknown constraint",
    44  		args:    []string{"cheese=edam"},
    45  		err:     `unknown constraint "cheese"`,
    46  	},
    47  
    48  	// "container" in detail.
    49  	{
    50  		summary: "set container empty",
    51  		args:    []string{"container="},
    52  	}, {
    53  		summary: "set container to none",
    54  		args:    []string{"container=none"},
    55  	}, {
    56  		summary: "set container lxd",
    57  		args:    []string{"container=lxd"},
    58  	}, {
    59  		summary: "set nonsense container",
    60  		args:    []string{"container=foo"},
    61  		err:     `bad "container" constraint: invalid container type "foo"`,
    62  	}, {
    63  		summary: "double set container together",
    64  		args:    []string{"container=lxd container=lxd"},
    65  		err:     `bad "container" constraint: already set`,
    66  	}, {
    67  		summary: "double set container separately",
    68  		args:    []string{"container=lxd", "container="},
    69  		err:     `bad "container" constraint: already set`,
    70  	},
    71  
    72  	// "arch" in detail.
    73  	{
    74  		summary: "set arch empty",
    75  		args:    []string{"arch="},
    76  	}, {
    77  		summary: "set arch amd64",
    78  		args:    []string{"arch=amd64"},
    79  	}, {
    80  		summary: "set arch i386",
    81  		args:    []string{"arch=i386"},
    82  	}, {
    83  		summary: "set arch armhf",
    84  		args:    []string{"arch=armhf"},
    85  	}, {
    86  		summary: "set nonsense arch 1",
    87  		args:    []string{"arch=cheese"},
    88  		err:     `bad "arch" constraint: "cheese" not recognized`,
    89  	}, {
    90  		summary: "set nonsense arch 2",
    91  		args:    []string{"arch=123.45"},
    92  		err:     `bad "arch" constraint: "123.45" not recognized`,
    93  	}, {
    94  		summary: "double set arch together",
    95  		args:    []string{"arch=amd64 arch=amd64"},
    96  		err:     `bad "arch" constraint: already set`,
    97  	}, {
    98  		summary: "double set arch separately",
    99  		args:    []string{"arch=armhf", "arch="},
   100  		err:     `bad "arch" constraint: already set`,
   101  	},
   102  
   103  	// "cores" in detail.
   104  	{
   105  		summary: "set cores empty",
   106  		args:    []string{"cores="},
   107  	}, {
   108  		summary: "set cores zero",
   109  		args:    []string{"cores=0"},
   110  	}, {
   111  		summary: "set cores",
   112  		args:    []string{"cores=4"},
   113  	}, {
   114  		summary: "set nonsense cores 1",
   115  		args:    []string{"cores=cheese"},
   116  		err:     `bad "cores" constraint: must be a non-negative integer`,
   117  	}, {
   118  		summary: "set nonsense cores 2",
   119  		args:    []string{"cores=-1"},
   120  		err:     `bad "cores" constraint: must be a non-negative integer`,
   121  	}, {
   122  		summary: "set nonsense cores 3",
   123  		args:    []string{"cores=123.45"},
   124  		err:     `bad "cores" constraint: must be a non-negative integer`,
   125  	}, {
   126  		summary: "double set cores together",
   127  		args:    []string{"cores=128 cores=1"},
   128  		err:     `bad "cores" constraint: already set`,
   129  	}, {
   130  		summary: "double set cores separately",
   131  		args:    []string{"cores=128", "cores=1"},
   132  		err:     `bad "cores" constraint: already set`,
   133  	},
   134  
   135  	// "cpu-cores"
   136  	{
   137  		summary: "set cpu-cores",
   138  		args:    []string{"cpu-cores=4"},
   139  	},
   140  
   141  	// "cpu-power" in detail.
   142  	{
   143  		summary: "set cpu-power empty",
   144  		args:    []string{"cpu-power="},
   145  	}, {
   146  		summary: "set cpu-power zero",
   147  		args:    []string{"cpu-power=0"},
   148  	}, {
   149  		summary: "set cpu-power",
   150  		args:    []string{"cpu-power=44"},
   151  	}, {
   152  		summary: "set nonsense cpu-power 1",
   153  		args:    []string{"cpu-power=cheese"},
   154  		err:     `bad "cpu-power" constraint: must be a non-negative integer`,
   155  	}, {
   156  		summary: "set nonsense cpu-power 2",
   157  		args:    []string{"cpu-power=-1"},
   158  		err:     `bad "cpu-power" constraint: must be a non-negative integer`,
   159  	}, {
   160  		summary: "double set cpu-power together",
   161  		args:    []string{"  cpu-power=300 cpu-power=1700 "},
   162  		err:     `bad "cpu-power" constraint: already set`,
   163  	}, {
   164  		summary: "double set cpu-power separately",
   165  		args:    []string{"cpu-power=300  ", "  cpu-power=1700"},
   166  		err:     `bad "cpu-power" constraint: already set`,
   167  	},
   168  
   169  	// "mem" in detail.
   170  	{
   171  		summary: "set mem empty",
   172  		args:    []string{"mem="},
   173  	}, {
   174  		summary: "set mem zero",
   175  		args:    []string{"mem=0"},
   176  	}, {
   177  		summary: "set mem without suffix",
   178  		args:    []string{"mem=512"},
   179  	}, {
   180  		summary: "set mem with M suffix",
   181  		args:    []string{"mem=512M"},
   182  	}, {
   183  		summary: "set mem with G suffix",
   184  		args:    []string{"mem=1.5G"},
   185  	}, {
   186  		summary: "set mem with T suffix",
   187  		args:    []string{"mem=36.2T"},
   188  	}, {
   189  		summary: "set mem with P suffix",
   190  		args:    []string{"mem=18.9P"},
   191  	}, {
   192  		summary: "set nonsense mem 1",
   193  		args:    []string{"mem=cheese"},
   194  		err:     `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   195  	}, {
   196  		summary: "set nonsense mem 2",
   197  		args:    []string{"mem=-1"},
   198  		err:     `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   199  	}, {
   200  		summary: "set nonsense mem 3",
   201  		args:    []string{"mem=32Y"},
   202  		err:     `bad "mem" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   203  	}, {
   204  		summary: "double set mem together",
   205  		args:    []string{"mem=1G  mem=2G"},
   206  		err:     `bad "mem" constraint: already set`,
   207  	}, {
   208  		summary: "double set mem separately",
   209  		args:    []string{"mem=1G", "mem=2G"},
   210  		err:     `bad "mem" constraint: already set`,
   211  	},
   212  
   213  	// "root-disk" in detail.
   214  	{
   215  		summary: "set root-disk empty",
   216  		args:    []string{"root-disk="},
   217  	}, {
   218  		summary: "set root-disk zero",
   219  		args:    []string{"root-disk=0"},
   220  	}, {
   221  		summary: "set root-disk without suffix",
   222  		args:    []string{"root-disk=512"},
   223  	}, {
   224  		summary: "set root-disk with M suffix",
   225  		args:    []string{"root-disk=512M"},
   226  	}, {
   227  		summary: "set root-disk with G suffix",
   228  		args:    []string{"root-disk=1.5G"},
   229  	}, {
   230  		summary: "set root-disk with T suffix",
   231  		args:    []string{"root-disk=36.2T"},
   232  	}, {
   233  		summary: "set root-disk with P suffix",
   234  		args:    []string{"root-disk=18.9P"},
   235  	}, {
   236  		summary: "set nonsense root-disk 1",
   237  		args:    []string{"root-disk=cheese"},
   238  		err:     `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   239  	}, {
   240  		summary: "set nonsense root-disk 2",
   241  		args:    []string{"root-disk=-1"},
   242  		err:     `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   243  	}, {
   244  		summary: "set nonsense root-disk 3",
   245  		args:    []string{"root-disk=32Y"},
   246  		err:     `bad "root-disk" constraint: must be a non-negative float with optional M/G/T/P suffix`,
   247  	}, {
   248  		summary: "double set root-disk together",
   249  		args:    []string{"root-disk=1G  root-disk=2G"},
   250  		err:     `bad "root-disk" constraint: already set`,
   251  	}, {
   252  		summary: "double set root-disk separately",
   253  		args:    []string{"root-disk=1G", "root-disk=2G"},
   254  		err:     `bad "root-disk" constraint: already set`,
   255  	},
   256  
   257  	// tags
   258  	{
   259  		summary: "single tag",
   260  		args:    []string{"tags=foo"},
   261  	}, {
   262  		summary: "multiple tags",
   263  		args:    []string{"tags=foo,bar"},
   264  	}, {
   265  		summary: "no tags",
   266  		args:    []string{"tags="},
   267  	},
   268  
   269  	// spaces
   270  	{
   271  		summary: "single space",
   272  		args:    []string{"spaces=space1"},
   273  	}, {
   274  		summary: "multiple spaces - positive",
   275  		args:    []string{"spaces=space1,space2"},
   276  	}, {
   277  		summary: "multiple spaces - negative",
   278  		args:    []string{"spaces=^dmz,^public"},
   279  	}, {
   280  		summary: "multiple spaces - positive and negative",
   281  		args:    []string{"spaces=admin,^area52,dmz,^public"},
   282  	}, {
   283  		summary: "no spaces",
   284  		args:    []string{"spaces="},
   285  	},
   286  
   287  	// instance type
   288  	{
   289  		summary: "set instance type",
   290  		args:    []string{"instance-type=foo"},
   291  	}, {
   292  		summary: "instance type empty",
   293  		args:    []string{"instance-type="},
   294  	},
   295  
   296  	// "virt-type" in detail.
   297  	{
   298  		summary: "set virt-type empty",
   299  		args:    []string{"virt-type="},
   300  	}, {
   301  		summary: "set virt-type kvm",
   302  		args:    []string{"virt-type=kvm"},
   303  	}, {
   304  		summary: "set virt-type lxd",
   305  		args:    []string{"virt-type=lxd"},
   306  	}, {
   307  		summary: "double set virt-type together",
   308  		args:    []string{"virt-type=kvm virt-type=kvm"},
   309  		err:     `bad "virt-type" constraint: already set`,
   310  	}, {
   311  		summary: "double set virt-type separately",
   312  		args:    []string{"virt-type=kvm", "virt-type="},
   313  		err:     `bad "virt-type" constraint: already set`,
   314  	},
   315  
   316  	// Zones
   317  	{
   318  		summary: "single zone",
   319  		args:    []string{"zones=az1"},
   320  	}, {
   321  		summary: "multiple zones",
   322  		args:    []string{"zones=az1,az2"},
   323  	}, {
   324  		summary: "no zones",
   325  		args:    []string{"zones="},
   326  	},
   327  
   328  	// Everything at once.
   329  	{
   330  		summary: "kitchen sink together",
   331  		args: []string{
   332  			"root-disk=8G mem=2T  arch=i386  cores=4096 cpu-power=9001 container=lxd " +
   333  				"tags=foo,bar spaces=space1,^space2 instance-type=foo",
   334  			"virt-type=kvm zones=az1,az2"},
   335  	}, {
   336  		summary: "kitchen sink separately",
   337  		args: []string{
   338  			"root-disk=8G", "mem=2T", "cores=4096", "cpu-power=9001", "arch=armhf",
   339  			"container=lxd", "tags=foo,bar", "spaces=space1,^space2",
   340  			"instance-type=foo", "virt-type=kvm", "zones=az1,az2"},
   341  	},
   342  }
   343  
   344  func (s *ConstraintsSuite) TestParseConstraints(c *gc.C) {
   345  	// TODO(dimitern): This test is inadequate and needs to check for
   346  	// more than just the reparsed output of String() matches the
   347  	// expected.
   348  	for i, t := range parseConstraintsTests {
   349  		c.Logf("test %d: %s", i, t.summary)
   350  		cons0, err := constraints.Parse(t.args...)
   351  		if t.err == "" {
   352  			c.Assert(err, jc.ErrorIsNil)
   353  		} else {
   354  			c.Assert(err, gc.ErrorMatches, t.err)
   355  			continue
   356  		}
   357  		cons1, err := constraints.Parse(cons0.String())
   358  		c.Check(err, jc.ErrorIsNil)
   359  		c.Check(cons1, gc.DeepEquals, cons0)
   360  	}
   361  }
   362  
   363  func (s *ConstraintsSuite) TestParseAliases(c *gc.C) {
   364  	v, aliases, err := constraints.ParseWithAliases("cpu-cores=5 arch=amd64")
   365  	c.Assert(err, jc.ErrorIsNil)
   366  	c.Assert(v, gc.DeepEquals, constraints.Value{
   367  		CpuCores: uint64p(5),
   368  		Arch:     strp("amd64"),
   369  	})
   370  	c.Assert(aliases, gc.DeepEquals, map[string]string{
   371  		"cpu-cores": "cores",
   372  	})
   373  }
   374  
   375  func (s *ConstraintsSuite) TestMerge(c *gc.C) {
   376  	con1 := constraints.MustParse("arch=amd64 mem=4G")
   377  	con2 := constraints.MustParse("cores=42")
   378  	con3 := constraints.MustParse(
   379  		"root-disk=8G container=lxd spaces=space1,^space2",
   380  	)
   381  	merged, err := constraints.Merge(con1, con2)
   382  	c.Assert(err, jc.ErrorIsNil)
   383  	c.Assert(merged, jc.DeepEquals, constraints.MustParse("arch=amd64 mem=4G cores=42"))
   384  	merged, err = constraints.Merge(con1)
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	c.Assert(merged, jc.DeepEquals, con1)
   387  	merged, err = constraints.Merge(con1, con2, con3)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	c.Assert(merged, jc.DeepEquals, constraints.
   390  		MustParse("arch=amd64 mem=4G cores=42 root-disk=8G container=lxd spaces=space1,^space2"),
   391  	)
   392  	merged, err = constraints.Merge()
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	c.Assert(merged, jc.DeepEquals, constraints.Value{})
   395  	foo := "foo"
   396  	merged, err = constraints.Merge(constraints.Value{Arch: &foo}, con2)
   397  	c.Assert(err, gc.NotNil)
   398  	c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: "foo" not recognized`)
   399  	c.Assert(merged, jc.DeepEquals, constraints.Value{})
   400  	merged, err = constraints.Merge(con1, con1)
   401  	c.Assert(err, gc.NotNil)
   402  	c.Assert(err, gc.ErrorMatches, `bad "arch" constraint: already set`)
   403  	c.Assert(merged, jc.DeepEquals, constraints.Value{})
   404  }
   405  
   406  func (s *ConstraintsSuite) TestParseMissingTagsAndSpaces(c *gc.C) {
   407  	con := constraints.MustParse("arch=amd64 mem=4G cores=1 root-disk=8G")
   408  	c.Check(con.Tags, gc.IsNil)
   409  	c.Check(con.Spaces, gc.IsNil)
   410  }
   411  
   412  func (s *ConstraintsSuite) TestParseNoTagsNoSpaces(c *gc.C) {
   413  	con := constraints.MustParse(
   414  		"arch=amd64 mem=4G cores=1 root-disk=8G tags= spaces=",
   415  	)
   416  	c.Assert(con.Tags, gc.Not(gc.IsNil))
   417  	c.Assert(con.Spaces, gc.Not(gc.IsNil))
   418  	c.Check(*con.Tags, gc.HasLen, 0)
   419  	c.Check(*con.Spaces, gc.HasLen, 0)
   420  }
   421  
   422  func (s *ConstraintsSuite) TestIncludeExcludeAndHasSpaces(c *gc.C) {
   423  	con := constraints.MustParse("spaces=space1,^space2,space3,^space4")
   424  	c.Assert(con.Spaces, gc.Not(gc.IsNil))
   425  	c.Check(*con.Spaces, gc.HasLen, 4)
   426  	c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space1", "space3"})
   427  	c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space2", "space4"})
   428  	c.Check(con.HasSpaces(), jc.IsTrue)
   429  	con = constraints.MustParse("mem=4G")
   430  	c.Check(con.HasSpaces(), jc.IsFalse)
   431  	con = constraints.MustParse("mem=4G spaces=space-foo,^space-bar")
   432  	c.Check(con.IncludeSpaces(), jc.SameContents, []string{"space-foo"})
   433  	c.Check(con.ExcludeSpaces(), jc.SameContents, []string{"space-bar"})
   434  	c.Check(con.HasSpaces(), jc.IsTrue)
   435  }
   436  
   437  func (s *ConstraintsSuite) TestInvalidSpaces(c *gc.C) {
   438  	invalidNames := []string{
   439  		"%$pace", "^foo#2", "+", "tcp:ip",
   440  		"^^myspace", "^^^^^^^^", "space^x",
   441  		"&-foo", "space/3", "^bar=4", "&#!",
   442  	}
   443  	for _, name := range invalidNames {
   444  		con, err := constraints.Parse("spaces=" + name)
   445  		expectName := strings.TrimPrefix(name, "^")
   446  		expectErr := fmt.Sprintf(`bad "spaces" constraint: %q is not a valid space name`, expectName)
   447  		c.Check(err, gc.NotNil)
   448  		c.Check(err.Error(), gc.Equals, expectErr)
   449  		c.Check(con, jc.DeepEquals, constraints.Value{})
   450  	}
   451  }
   452  
   453  func (s *ConstraintsSuite) TestHasZones(c *gc.C) {
   454  	con := constraints.MustParse("zones=az1,az2,az3")
   455  	c.Assert(con.Zones, gc.Not(gc.IsNil))
   456  	c.Check(*con.Zones, gc.HasLen, 3)
   457  	c.Check(con.HasZones(), jc.IsTrue)
   458  
   459  	con = constraints.MustParse("zones=")
   460  	c.Check(con.HasZones(), jc.IsFalse)
   461  
   462  	con = constraints.MustParse("spaces=space1,^space2")
   463  	c.Check(con.HasZones(), jc.IsFalse)
   464  }
   465  
   466  func (s *ConstraintsSuite) TestIsEmpty(c *gc.C) {
   467  	con := constraints.Value{}
   468  	c.Check(&con, jc.Satisfies, constraints.IsEmpty)
   469  	con = constraints.MustParse("arch=amd64")
   470  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   471  	con = constraints.MustParse("")
   472  	c.Check(&con, jc.Satisfies, constraints.IsEmpty)
   473  	con = constraints.MustParse("tags=")
   474  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   475  	con = constraints.MustParse("spaces=")
   476  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   477  	con = constraints.MustParse("mem=")
   478  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   479  	con = constraints.MustParse("arch=")
   480  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   481  	con = constraints.MustParse("root-disk=")
   482  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   483  	con = constraints.MustParse("cpu-power=")
   484  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   485  	con = constraints.MustParse("cores=")
   486  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   487  	con = constraints.MustParse("container=")
   488  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   489  	con = constraints.MustParse("instance-type=")
   490  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   491  	con = constraints.MustParse("zones=")
   492  	c.Check(&con, gc.Not(jc.Satisfies), constraints.IsEmpty)
   493  }
   494  
   495  func uint64p(i uint64) *uint64 {
   496  	return &i
   497  }
   498  
   499  func strp(s string) *string {
   500  	return &s
   501  }
   502  
   503  func ctypep(ctype string) *instance.ContainerType {
   504  	res := instance.ContainerType(ctype)
   505  	return &res
   506  }
   507  
   508  type roundTrip struct {
   509  	Name  string
   510  	Value constraints.Value
   511  }
   512  
   513  var constraintsRoundtripTests = []roundTrip{
   514  	{"empty", constraints.Value{}},
   515  	{"Arch1", constraints.Value{Arch: strp("")}},
   516  	{"Arch2", constraints.Value{Arch: strp("amd64")}},
   517  	{"Container1", constraints.Value{Container: ctypep("")}},
   518  	{"Container2", constraints.Value{Container: ctypep("lxd")}},
   519  	{"Container3", constraints.Value{Container: nil}},
   520  	{"CpuCores1", constraints.Value{CpuCores: nil}},
   521  	{"CpuCores2", constraints.Value{CpuCores: uint64p(0)}},
   522  	{"CpuCores3", constraints.Value{CpuCores: uint64p(128)}},
   523  	{"CpuPower1", constraints.Value{CpuPower: nil}},
   524  	{"CpuPower2", constraints.Value{CpuPower: uint64p(0)}},
   525  	{"CpuPower3", constraints.Value{CpuPower: uint64p(250)}},
   526  	{"Mem1", constraints.Value{Mem: nil}},
   527  	{"Mem2", constraints.Value{Mem: uint64p(0)}},
   528  	{"Mem3", constraints.Value{Mem: uint64p(98765)}},
   529  	{"RootDisk1", constraints.Value{RootDisk: nil}},
   530  	{"RootDisk2", constraints.Value{RootDisk: uint64p(0)}},
   531  	{"RootDisk2", constraints.Value{RootDisk: uint64p(109876)}},
   532  	{"Tags1", constraints.Value{Tags: nil}},
   533  	{"Tags2", constraints.Value{Tags: &[]string{}}},
   534  	{"Tags3", constraints.Value{Tags: &[]string{"foo", "bar"}}},
   535  	{"Spaces1", constraints.Value{Spaces: nil}},
   536  	{"Spaces2", constraints.Value{Spaces: &[]string{}}},
   537  	{"Spaces3", constraints.Value{Spaces: &[]string{"space1", "^space2"}}},
   538  	{"InstanceType1", constraints.Value{InstanceType: strp("")}},
   539  	{"InstanceType2", constraints.Value{InstanceType: strp("foo")}},
   540  	{"Zones1", constraints.Value{Zones: nil}},
   541  	{"Zones2", constraints.Value{Zones: &[]string{}}},
   542  	{"Zones3", constraints.Value{Zones: &[]string{"az1", "az2"}}},
   543  	{"All", constraints.Value{
   544  		Arch:         strp("i386"),
   545  		Container:    ctypep("lxd"),
   546  		CpuCores:     uint64p(4096),
   547  		CpuPower:     uint64p(9001),
   548  		Mem:          uint64p(18000000000),
   549  		RootDisk:     uint64p(24000000000),
   550  		Tags:         &[]string{"foo", "bar"},
   551  		Spaces:       &[]string{"space1", "^space2"},
   552  		InstanceType: strp("foo"),
   553  		Zones:        &[]string{"az1", "az2"},
   554  	}},
   555  }
   556  
   557  func (s *ConstraintsSuite) TestRoundtripGnuflagValue(c *gc.C) {
   558  	for _, t := range constraintsRoundtripTests {
   559  		c.Logf("test %s", t.Name)
   560  		var cons constraints.Value
   561  		val := constraints.ConstraintsValue{&cons}
   562  		err := val.Set(t.Value.String())
   563  		c.Check(err, jc.ErrorIsNil)
   564  		c.Check(cons, jc.DeepEquals, t.Value)
   565  	}
   566  }
   567  
   568  func (s *ConstraintsSuite) TestRoundtripString(c *gc.C) {
   569  	for _, t := range constraintsRoundtripTests {
   570  		c.Logf("test %s", t.Name)
   571  		cons, err := constraints.Parse(t.Value.String())
   572  		c.Check(err, jc.ErrorIsNil)
   573  		c.Check(cons, jc.DeepEquals, t.Value)
   574  	}
   575  }
   576  
   577  func (s *ConstraintsSuite) TestRoundtripJson(c *gc.C) {
   578  	for _, t := range constraintsRoundtripTests {
   579  		c.Logf("test %s", t.Name)
   580  		data, err := json.Marshal(t.Value)
   581  		c.Assert(err, jc.ErrorIsNil)
   582  		var cons constraints.Value
   583  		err = json.Unmarshal(data, &cons)
   584  		c.Check(err, jc.ErrorIsNil)
   585  		c.Check(cons, jc.DeepEquals, t.Value)
   586  	}
   587  }
   588  
   589  func (s *ConstraintsSuite) TestRoundtripYaml(c *gc.C) {
   590  	for _, t := range constraintsRoundtripTests {
   591  		c.Logf("test %s", t.Name)
   592  		data, err := goyaml.Marshal(t.Value)
   593  		c.Assert(err, jc.ErrorIsNil)
   594  		var cons constraints.Value
   595  		err = goyaml.Unmarshal(data, &cons)
   596  		c.Check(err, jc.ErrorIsNil)
   597  		c.Check(cons, jc.DeepEquals, t.Value)
   598  	}
   599  }
   600  
   601  var hasContainerTests = []struct {
   602  	constraints  string
   603  	hasContainer bool
   604  }{
   605  	{
   606  		hasContainer: false,
   607  	}, {
   608  		constraints:  "container=lxd",
   609  		hasContainer: true,
   610  	}, {
   611  		constraints:  "container=none",
   612  		hasContainer: false,
   613  	},
   614  }
   615  
   616  func (s *ConstraintsSuite) TestHasContainer(c *gc.C) {
   617  	for i, t := range hasContainerTests {
   618  		c.Logf("test %d", i)
   619  		cons := constraints.MustParse(t.constraints)
   620  		c.Check(cons.HasContainer(), gc.Equals, t.hasContainer)
   621  	}
   622  }
   623  
   624  func (s *ConstraintsSuite) TestHasInstanceType(c *gc.C) {
   625  	cons := constraints.MustParse("arch=amd64")
   626  	c.Check(cons.HasInstanceType(), jc.IsFalse)
   627  	cons = constraints.MustParse("arch=amd64 instance-type=foo")
   628  	c.Check(cons.HasInstanceType(), jc.IsTrue)
   629  }
   630  
   631  const initialWithoutCons = "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 tags=foo " +
   632  	"container=lxd instance-type=bar zones=az1,az2"
   633  
   634  var withoutTests = []struct {
   635  	initial string
   636  	without []string
   637  	final   string
   638  }{{
   639  	initial: initialWithoutCons,
   640  	without: []string{"root-disk"},
   641  	final:   "mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   642  }, {
   643  	initial: initialWithoutCons,
   644  	without: []string{"mem"},
   645  	final:   "root-disk=8G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   646  }, {
   647  	initial: initialWithoutCons,
   648  	without: []string{"arch"},
   649  	final:   "root-disk=8G mem=4G cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   650  }, {
   651  	initial: initialWithoutCons,
   652  	without: []string{"cpu-power"},
   653  	final:   "root-disk=8G mem=4G arch=amd64 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   654  }, {
   655  	initial: initialWithoutCons,
   656  	without: []string{"cores"},
   657  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   658  }, {
   659  	initial: initialWithoutCons,
   660  	without: []string{"tags"},
   661  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   662  }, {
   663  	initial: initialWithoutCons,
   664  	without: []string{"spaces"},
   665  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo container=lxd instance-type=bar zones=az1,az2",
   666  }, {
   667  	initial: initialWithoutCons,
   668  	without: []string{"container"},
   669  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 instance-type=bar zones=az1,az2",
   670  }, {
   671  	initial: initialWithoutCons,
   672  	without: []string{"instance-type"},
   673  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd zones=az1,az2",
   674  }, {
   675  	initial: initialWithoutCons,
   676  	without: []string{"root-disk", "mem", "arch"},
   677  	final:   "cpu-power=1000 cores=4 tags=foo spaces=space1,^space2 container=lxd instance-type=bar zones=az1,az2",
   678  }, {
   679  	initial: initialWithoutCons,
   680  	without: []string{"zones"},
   681  	final:   "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 spaces=space1,^space2 tags=foo container=lxd instance-type=bar",
   682  }}
   683  
   684  func (s *ConstraintsSuite) TestWithout(c *gc.C) {
   685  	for i, t := range withoutTests {
   686  		c.Logf("test %d", i)
   687  		initial := constraints.MustParse(t.initial)
   688  		final := constraints.Without(initial, t.without...)
   689  		c.Check(final, jc.DeepEquals, constraints.MustParse(t.final))
   690  	}
   691  }
   692  
   693  var hasAnyTests = []struct {
   694  	cons     string
   695  	attrs    []string
   696  	expected []string
   697  }{
   698  	{
   699  		cons:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 spaces=space1,^space2 cores=4",
   700  		attrs:    []string{"root-disk", "tags", "mem", "spaces"},
   701  		expected: []string{"root-disk", "mem", "spaces"},
   702  	},
   703  	{
   704  		cons:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4",
   705  		attrs:    []string{"root-disk", "tags", "mem"},
   706  		expected: []string{"root-disk", "mem"},
   707  	},
   708  	{
   709  		cons:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4",
   710  		attrs:    []string{"tags", "spaces"},
   711  		expected: []string{},
   712  	},
   713  }
   714  
   715  func (s *ConstraintsSuite) TestHasAny(c *gc.C) {
   716  	for i, t := range hasAnyTests {
   717  		c.Logf("test %d", i)
   718  		cons := constraints.MustParse(t.cons)
   719  		obtained := constraints.HasAny(cons, t.attrs...)
   720  		c.Check(obtained, jc.DeepEquals, t.expected)
   721  	}
   722  }