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