github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/core/constraints/validation_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  	"regexp"
     8  
     9  	jc "github.com/juju/testing/checkers"
    10  	gc "gopkg.in/check.v1"
    11  
    12  	"github.com/juju/juju/core/constraints"
    13  )
    14  
    15  type validationSuite struct{}
    16  
    17  var _ = gc.Suite(&validationSuite{})
    18  
    19  var validationTests = []struct {
    20  	desc        string
    21  	cons        string
    22  	unsupported []string
    23  	vocab       map[string][]interface{}
    24  	reds        []string
    25  	blues       []string
    26  	err         string
    27  }{
    28  	{
    29  		desc: "base good",
    30  		cons: "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4",
    31  	},
    32  	{
    33  		desc:        "unsupported",
    34  		cons:        "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 tags=foo",
    35  		unsupported: []string{"tags"},
    36  	},
    37  	{
    38  		desc:        "multiple unsupported",
    39  		cons:        "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4 instance-type=foo",
    40  		unsupported: []string{"cpu-power", "instance-type"},
    41  	},
    42  	{
    43  		desc:        "Ambiguous constraint errors take precedence over unsupported errors.",
    44  		cons:        "root-disk=8G mem=4G cores=4 instance-type=foo",
    45  		reds:        []string{"mem", "arch"},
    46  		blues:       []string{"instance-type"},
    47  		unsupported: []string{"cores"},
    48  		err:         `ambiguous constraints: "instance-type" overlaps with "mem"`,
    49  	},
    50  	{
    51  		desc: "red conflicts",
    52  		cons: "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo",
    53  		reds: []string{"mem", "arch"},
    54  	},
    55  	{
    56  		desc:  "blue conflicts",
    57  		cons:  "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo",
    58  		blues: []string{"mem", "arch"},
    59  	},
    60  	{
    61  		desc:  "red and blue conflicts",
    62  		cons:  "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo",
    63  		reds:  []string{"mem", "arch"},
    64  		blues: []string{"instance-type"},
    65  		err:   `ambiguous constraints: "arch" overlaps with "instance-type"`,
    66  	},
    67  	{
    68  		desc:  "ambiguous constraints red to blue",
    69  		cons:  "root-disk=8G mem=4G arch=amd64 cores=4 instance-type=foo",
    70  		reds:  []string{"instance-type"},
    71  		blues: []string{"mem", "arch"},
    72  		err:   `ambiguous constraints: "arch" overlaps with "instance-type"`,
    73  	},
    74  	{
    75  		desc:  "ambiguous constraints blue to red",
    76  		cons:  "root-disk=8G mem=4G cores=4 instance-type=foo",
    77  		reds:  []string{"mem", "arch"},
    78  		blues: []string{"instance-type"},
    79  		err:   `ambiguous constraints: "instance-type" overlaps with "mem"`,
    80  	},
    81  	{
    82  		desc:  "arch vocab",
    83  		cons:  "arch=amd64 mem=4G cores=4",
    84  		vocab: map[string][]interface{}{"arch": {"amd64", "i386"}},
    85  	},
    86  	{
    87  		desc:  "cores vocab",
    88  		cons:  "mem=4G cores=4",
    89  		vocab: map[string][]interface{}{"cores": {2, 4, 8}},
    90  	},
    91  	{
    92  		desc:  "instance-type vocab",
    93  		cons:  "mem=4G instance-type=foo",
    94  		vocab: map[string][]interface{}{"instance-type": {"foo", "bar"}},
    95  	},
    96  	{
    97  		desc:  "tags vocab",
    98  		cons:  "mem=4G tags=foo,bar",
    99  		vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}},
   100  	},
   101  	{
   102  		desc:  "invalid arch vocab",
   103  		cons:  "arch=i386 mem=4G cores=4",
   104  		vocab: map[string][]interface{}{"arch": {"amd64"}},
   105  		err:   "invalid constraint value: arch=i386\nvalid values are:.*",
   106  	},
   107  	{
   108  		desc:  "invalid cores vocab",
   109  		cons:  "mem=4G cores=5",
   110  		vocab: map[string][]interface{}{"cores": {2, 4, 8}},
   111  		err:   "invalid constraint value: cores=5\nvalid values are:.*",
   112  	},
   113  	{
   114  		desc:  "invalid instance-type vocab",
   115  		cons:  "mem=4G instance-type=foo",
   116  		vocab: map[string][]interface{}{"instance-type": {"bar"}},
   117  		err:   "invalid constraint value: instance-type=foo\nvalid values are:.*",
   118  	},
   119  	{
   120  		desc:  "invalid tags vocab",
   121  		cons:  "mem=4G tags=foo,other",
   122  		vocab: map[string][]interface{}{"tags": {"foo", "bar", "another"}},
   123  		err:   "invalid constraint value: tags=other\nvalid values are:.*",
   124  	},
   125  	{
   126  		desc: "instance-type and arch",
   127  		cons: "arch=i386 mem=4G instance-type=foo",
   128  		vocab: map[string][]interface{}{
   129  			"instance-type": {"foo", "bar"},
   130  			"arch":          {"amd64", "i386"}},
   131  	},
   132  	{
   133  		desc:  "virt-type",
   134  		cons:  "virt-type=bar",
   135  		vocab: map[string][]interface{}{"virt-type": {"bar"}},
   136  	},
   137  }
   138  
   139  func (s *validationSuite) TestValidation(c *gc.C) {
   140  	for i, t := range validationTests {
   141  		c.Logf("test %d: %s", i, t.desc)
   142  		validator := constraints.NewValidator()
   143  		validator.RegisterUnsupported(t.unsupported)
   144  		validator.RegisterConflicts(t.reds, t.blues)
   145  		for a, v := range t.vocab {
   146  			validator.RegisterVocabulary(a, v)
   147  		}
   148  		cons := constraints.MustParse(t.cons)
   149  		unsupported, err := validator.Validate(cons)
   150  		if t.err == "" {
   151  			c.Assert(err, jc.ErrorIsNil)
   152  			c.Assert(unsupported, jc.SameContents, t.unsupported)
   153  		} else {
   154  			c.Assert(err, gc.ErrorMatches, t.err)
   155  		}
   156  	}
   157  }
   158  
   159  var mergeTests = []struct {
   160  	desc         string
   161  	consFallback string
   162  	cons         string
   163  	unsupported  []string
   164  	reds         []string
   165  	blues        []string
   166  	expected     string
   167  }{
   168  	{
   169  		desc: "empty all round",
   170  	}, {
   171  		desc:     "container with empty fallback",
   172  		cons:     "container=lxd",
   173  		expected: "container=lxd",
   174  	}, {
   175  		desc:         "container from fallback",
   176  		consFallback: "container=lxd",
   177  		expected:     "container=lxd",
   178  	}, {
   179  		desc:     "arch with empty fallback",
   180  		cons:     "arch=amd64",
   181  		expected: "arch=amd64",
   182  	}, {
   183  		desc:         "arch with ignored fallback",
   184  		cons:         "arch=amd64",
   185  		consFallback: "arch=i386",
   186  		expected:     "arch=amd64",
   187  	}, {
   188  		desc:         "arch from fallback",
   189  		consFallback: "arch=i386",
   190  		expected:     "arch=i386",
   191  	}, {
   192  		desc:     "instance type with empty fallback",
   193  		cons:     "instance-type=foo",
   194  		expected: "instance-type=foo",
   195  	}, {
   196  		desc:         "instance type with ignored fallback",
   197  		cons:         "instance-type=foo",
   198  		consFallback: "instance-type=bar",
   199  		expected:     "instance-type=foo",
   200  	}, {
   201  		desc:         "instance type from fallback",
   202  		consFallback: "instance-type=foo",
   203  		expected:     "instance-type=foo",
   204  	}, {
   205  		desc:     "cores with empty fallback",
   206  		cons:     "cores=2",
   207  		expected: "cores=2",
   208  	}, {
   209  		desc:         "cores with ignored fallback",
   210  		cons:         "cores=4",
   211  		consFallback: "cores=8",
   212  		expected:     "cores=4",
   213  	}, {
   214  		desc:         "cores from fallback",
   215  		consFallback: "cores=8",
   216  		expected:     "cores=8",
   217  	}, {
   218  		desc:     "cpu-power with empty fallback",
   219  		cons:     "cpu-power=100",
   220  		expected: "cpu-power=100",
   221  	}, {
   222  		desc:         "cpu-power with ignored fallback",
   223  		cons:         "cpu-power=100",
   224  		consFallback: "cpu-power=200",
   225  		expected:     "cpu-power=100",
   226  	}, {
   227  		desc:         "cpu-power from fallback",
   228  		consFallback: "cpu-power=200",
   229  		expected:     "cpu-power=200",
   230  	}, {
   231  		desc:     "tags with empty fallback",
   232  		cons:     "tags=foo,bar",
   233  		expected: "tags=foo,bar",
   234  	}, {
   235  		desc:         "tags with ignored fallback",
   236  		cons:         "tags=foo,bar",
   237  		consFallback: "tags=baz",
   238  		expected:     "tags=foo,bar",
   239  	}, {
   240  		desc:         "tags from fallback",
   241  		consFallback: "tags=foo,bar",
   242  		expected:     "tags=foo,bar",
   243  	}, {
   244  		desc:         "tags initial empty",
   245  		cons:         "tags=",
   246  		consFallback: "tags=foo,bar",
   247  		expected:     "tags=",
   248  	}, {
   249  		desc:     "mem with empty fallback",
   250  		cons:     "mem=4G",
   251  		expected: "mem=4G",
   252  	}, {
   253  		desc:         "mem with ignored fallback",
   254  		cons:         "mem=4G",
   255  		consFallback: "mem=8G",
   256  		expected:     "mem=4G",
   257  	}, {
   258  		desc:         "mem from fallback",
   259  		consFallback: "mem=8G",
   260  		expected:     "mem=8G",
   261  	}, {
   262  		desc:     "root-disk with empty fallback",
   263  		cons:     "root-disk=4G",
   264  		expected: "root-disk=4G",
   265  	}, {
   266  		desc:         "root-disk with ignored fallback",
   267  		cons:         "root-disk=4G",
   268  		consFallback: "root-disk=8G",
   269  		expected:     "root-disk=4G",
   270  	}, {
   271  		desc:         "root-disk from fallback",
   272  		consFallback: "root-disk=8G",
   273  		expected:     "root-disk=8G",
   274  	}, {
   275  		desc:         "non-overlapping mix",
   276  		cons:         "root-disk=8G mem=4G arch=amd64",
   277  		consFallback: "cpu-power=1000 cores=4",
   278  		expected:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4",
   279  	}, {
   280  		desc:         "overlapping mix",
   281  		cons:         "root-disk=8G mem=4G arch=amd64",
   282  		consFallback: "cpu-power=1000 cores=4 mem=8G",
   283  		expected:     "root-disk=8G mem=4G arch=amd64 cpu-power=1000 cores=4",
   284  	}, {
   285  		desc:         "fallback only, no conflicts",
   286  		consFallback: "root-disk=8G cores=4 instance-type=foo",
   287  		reds:         []string{"mem", "arch"},
   288  		blues:        []string{"instance-type"},
   289  		expected:     "root-disk=8G cores=4 instance-type=foo",
   290  	}, {
   291  		desc:     "no fallback, no conflicts",
   292  		cons:     "root-disk=8G cores=4 instance-type=foo",
   293  		reds:     []string{"mem", "arch"},
   294  		blues:    []string{"instance-type"},
   295  		expected: "root-disk=8G cores=4 instance-type=foo",
   296  	}, {
   297  		desc:         "conflict value from override",
   298  		consFallback: "root-disk=8G instance-type=foo",
   299  		cons:         "root-disk=8G cores=4 instance-type=bar",
   300  		reds:         []string{"mem", "arch"},
   301  		blues:        []string{"instance-type"},
   302  		expected:     "root-disk=8G cores=4 instance-type=bar",
   303  	}, {
   304  		desc:         "unsupported attributes ignored",
   305  		consFallback: "root-disk=8G instance-type=foo",
   306  		cons:         "root-disk=8G cores=4 instance-type=bar",
   307  		reds:         []string{"mem", "arch"},
   308  		blues:        []string{"instance-type"},
   309  		unsupported:  []string{"instance-type"},
   310  		expected:     "root-disk=8G cores=4 instance-type=bar",
   311  	}, {
   312  		desc:         "red conflict masked from fallback",
   313  		consFallback: "root-disk=8G mem=4G",
   314  		cons:         "root-disk=8G cores=4 instance-type=bar",
   315  		reds:         []string{"mem", "arch"},
   316  		blues:        []string{"instance-type"},
   317  		expected:     "root-disk=8G cores=4 instance-type=bar",
   318  	}, {
   319  		desc:         "second red conflict masked from fallback",
   320  		consFallback: "root-disk=8G arch=amd64",
   321  		cons:         "root-disk=8G cores=4 instance-type=bar",
   322  		reds:         []string{"mem", "arch"},
   323  		blues:        []string{"instance-type"},
   324  		expected:     "root-disk=8G cores=4 instance-type=bar",
   325  	}, {
   326  		desc:         "blue conflict masked from fallback",
   327  		consFallback: "root-disk=8G cores=4 instance-type=bar",
   328  		cons:         "root-disk=8G mem=4G",
   329  		reds:         []string{"mem", "arch"},
   330  		blues:        []string{"instance-type"},
   331  		expected:     "root-disk=8G cores=4 mem=4G",
   332  	}, {
   333  		desc:         "both red conflicts used, blue mased from fallback",
   334  		consFallback: "root-disk=8G cores=4 instance-type=bar",
   335  		cons:         "root-disk=8G arch=amd64 mem=4G",
   336  		reds:         []string{"mem", "arch"},
   337  		blues:        []string{"instance-type"},
   338  		expected:     "root-disk=8G cores=4 arch=amd64 mem=4G",
   339  	},
   340  }
   341  
   342  func (s *validationSuite) TestMerge(c *gc.C) {
   343  	for i, t := range mergeTests {
   344  		c.Logf("test %d: %s", i, t.desc)
   345  		validator := constraints.NewValidator()
   346  		validator.RegisterConflicts(t.reds, t.blues)
   347  		consFallback := constraints.MustParse(t.consFallback)
   348  		cons := constraints.MustParse(t.cons)
   349  		merged, err := validator.Merge(consFallback, cons)
   350  		c.Assert(err, jc.ErrorIsNil)
   351  		expected := constraints.MustParse(t.expected)
   352  		c.Check(merged, gc.DeepEquals, expected)
   353  	}
   354  }
   355  
   356  func (s *validationSuite) TestMergeError(c *gc.C) {
   357  	validator := constraints.NewValidator()
   358  	validator.RegisterConflicts([]string{"instance-type"}, []string{"mem"})
   359  	consFallback := constraints.MustParse("instance-type=foo mem=4G")
   360  	cons := constraints.MustParse("cores=2")
   361  	_, err := validator.Merge(consFallback, cons)
   362  	c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
   363  	_, err = validator.Merge(cons, consFallback)
   364  	c.Assert(err, gc.ErrorMatches, `ambiguous constraints: "instance-type" overlaps with "mem"`)
   365  }
   366  
   367  func (s *validationSuite) TestUpdateVocabulary(c *gc.C) {
   368  	validator := constraints.NewValidator()
   369  	attributeName := "arch"
   370  	originalValues := []string{"amd64"}
   371  	validator.RegisterVocabulary(attributeName, originalValues)
   372  
   373  	cons := constraints.MustParse("arch=amd64")
   374  	_, err := validator.Validate(cons)
   375  	c.Assert(err, jc.ErrorIsNil)
   376  
   377  	cons2 := constraints.MustParse("arch=ppc64el")
   378  	_, err = validator.Validate(cons2)
   379  	c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta(`invalid constraint value: arch=ppc64el
   380  valid values are: [amd64]`))
   381  
   382  	additionalValues := []string{"ppc64el"}
   383  	validator.UpdateVocabulary(attributeName, additionalValues)
   384  
   385  	_, err = validator.Validate(cons)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  	_, err = validator.Validate(cons2)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  }