github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/constraintsvalidation_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state_test
     5  
     6  import (
     7  	"regexp"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/mgo/v3/bson"
    11  	"github.com/juju/mgo/v3/txn"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "gopkg.in/check.v1"
    14  
    15  	"github.com/juju/juju/core/constraints"
    16  	"github.com/juju/juju/state"
    17  )
    18  
    19  type constraintsValidationSuite struct {
    20  	ConnSuite
    21  }
    22  
    23  var _ = gc.Suite(&constraintsValidationSuite{})
    24  
    25  func (s *constraintsValidationSuite) SetUpTest(c *gc.C) {
    26  	s.ConnSuite.SetUpTest(c)
    27  	s.policy.GetConstraintsValidator = func() (constraints.Validator, error) {
    28  		validator := constraints.NewValidator()
    29  		validator.RegisterConflicts(
    30  			[]string{constraints.InstanceType},
    31  			[]string{constraints.Mem, constraints.Arch},
    32  		)
    33  		validator.RegisterUnsupported([]string{constraints.CpuPower})
    34  		return validator, nil
    35  	}
    36  }
    37  
    38  func (s *constraintsValidationSuite) addOneMachine(c *gc.C, cons constraints.Value) (*state.Machine, error) {
    39  	return s.State.AddOneMachine(state.MachineTemplate{
    40  		Base:        state.UbuntuBase("12.10"),
    41  		Jobs:        []state.MachineJob{state.JobHostUnits},
    42  		Constraints: cons,
    43  	})
    44  }
    45  
    46  var setConstraintsTests = []struct {
    47  	about        string
    48  	consToSet    string
    49  	consFallback string
    50  
    51  	effectiveModelCons       string // model constraints after setting consFallback
    52  	effectiveApplicationCons string // application constraints after setting consToSet
    53  	effectiveUnitCons        string // unit constraints after setting consToSet on the application
    54  	effectiveMachineCons     string // machine constraints after setting consToSet
    55  }{{
    56  	about:        "(implicitly) empty constraints are OK and stored as empty",
    57  	consToSet:    "",
    58  	consFallback: "",
    59  
    60  	effectiveModelCons:       "",
    61  	effectiveApplicationCons: "",
    62  	effectiveUnitCons:        "arch=amd64",
    63  	effectiveMachineCons:     "",
    64  }, {
    65  	about:        "(implicitly) empty fallback constraints never override set constraints",
    66  	consToSet:    "instance-type=foo-42 cpu-power=9001 spaces=bar zones=az1,az2",
    67  	consFallback: "",
    68  
    69  	effectiveModelCons: "", // model constraints are stored as empty.
    70  	// i.e. there are no fallbacks and all the following cases are the same.
    71  	effectiveApplicationCons: "instance-type=foo-42 cpu-power=9001 spaces=bar zones=az1,az2",
    72  	effectiveUnitCons:        "instance-type=foo-42 cpu-power=9001 spaces=bar zones=az1,az2",
    73  	effectiveMachineCons:     "instance-type=foo-42 cpu-power=9001 spaces=bar zones=az1,az2",
    74  }, {
    75  	about:        "(implicitly) empty constraints never override explicitly set fallbacks",
    76  	consToSet:    "",
    77  	consFallback: "arch=amd64 cores=42 mem=2G tags=foo",
    78  
    79  	effectiveModelCons:       "arch=amd64 cores=42 mem=2G tags=foo",
    80  	effectiveApplicationCons: "", // set as given.
    81  	effectiveUnitCons:        "arch=amd64 cores=42 mem=2G tags=foo",
    82  	// set as given, then merged with fallbacks; since consToSet is
    83  	// empty, the effective values inherit everything from fallbacks;
    84  	// like the unit, but only because the application constraints are
    85  	// also empty.
    86  	effectiveMachineCons: "arch=amd64 cores=42 mem=2G tags=foo",
    87  }, {
    88  	about:        "(explicitly) empty constraints are OK and stored as given",
    89  	consToSet:    "cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
    90  	consFallback: "",
    91  
    92  	effectiveModelCons:       "",
    93  	effectiveApplicationCons: "cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
    94  	effectiveUnitCons:        "arch=amd64 cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
    95  	effectiveMachineCons:     "cores= cpu-power= instance-type= root-disk= tags= spaces=", // container= is dropped
    96  }, {
    97  	about:        "(explicitly) empty fallback constraints are OK and stored as given",
    98  	consToSet:    "",
    99  	consFallback: "cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
   100  
   101  	effectiveModelCons:       "cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
   102  	effectiveApplicationCons: "",
   103  	effectiveUnitCons:        "arch=amd64 cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
   104  	effectiveMachineCons:     "cores= cpu-power= instance-type= root-disk= tags= spaces=", // container= is dropped
   105  }, {
   106  	about:                    "(explicitly) empty constraints and fallbacks are OK and stored as given",
   107  	consToSet:                "arch= mem= cores= container=",
   108  	consFallback:             "cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
   109  	effectiveModelCons:       "cores= cpu-power= root-disk= instance-type= container= tags= spaces=",
   110  	effectiveApplicationCons: "arch= mem= cores= container=",
   111  	effectiveUnitCons:        "arch=amd64 container= cores= cpu-power= mem= root-disk= tags= spaces=",
   112  	effectiveMachineCons:     "arch= cores= cpu-power= mem= root-disk= tags= spaces=", // container= is dropped
   113  }, {
   114  	about:        "(explicitly) empty constraints override set fallbacks for deployment and provisioning",
   115  	consToSet:    "cores= arch= spaces= cpu-power=",
   116  	consFallback: "cores=42 arch=amd64 tags=foo spaces=default,^dmz mem=4G",
   117  
   118  	effectiveModelCons:       "cores=42 arch=amd64 tags=foo spaces=default,^dmz mem=4G",
   119  	effectiveApplicationCons: "cores= arch= spaces= cpu-power=",
   120  	effectiveUnitCons:        "arch=amd64 cores= cpu-power= mem=4G tags=foo spaces=",
   121  	effectiveMachineCons:     "arch= cores= cpu-power= mem=4G tags=foo spaces=",
   122  	// we're also checking if m.SetConstraints() does the same with
   123  	// regards to the effective constraints as AddMachine(), because
   124  	// some of these tests proved they had different behavior (i.e.
   125  	// container= was not set to empty)
   126  }, {
   127  	about:        "non-empty constraints always override empty or unset fallbacks",
   128  	consToSet:    "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   129  	consFallback: "cores= arch= tags=",
   130  
   131  	effectiveModelCons:       "cores= arch= tags=",
   132  	effectiveApplicationCons: "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   133  	effectiveUnitCons:        "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   134  	effectiveMachineCons:     "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   135  }, {
   136  	about:        "non-empty constraints always override set fallbacks",
   137  	consToSet:    "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   138  	consFallback: "cores=12 root-disk=10G arch=s390x  tags=bar",
   139  
   140  	effectiveModelCons:       "cores=12 root-disk=10G arch=s390x  tags=bar",
   141  	effectiveApplicationCons: "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   142  	effectiveUnitCons:        "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   143  	effectiveMachineCons:     "cores=42 root-disk=20G arch=amd64 tags=foo,bar",
   144  }, {
   145  	about:        "non-empty constraints override conflicting set fallbacks",
   146  	consToSet:    "mem=8G arch=amd64 cores=4 tags=bar",
   147  	consFallback: "instance-type=small cpu-power=1000", // instance-type conflicts mem, arch
   148  
   149  	effectiveModelCons:       "instance-type=small cpu-power=1000",
   150  	effectiveApplicationCons: "mem=8G arch=amd64 cores=4 tags=bar",
   151  	// both of the following contain the explicitly set constraints after
   152  	// resolving any conflicts with fallbacks (by dropping them).
   153  	effectiveUnitCons:    "mem=8G arch=amd64 cores=4 tags=bar cpu-power=1000",
   154  	effectiveMachineCons: "mem=8G arch=amd64 cores=4 tags=bar cpu-power=1000",
   155  }, {
   156  	about:        "set fallbacks are overridden the same way for provisioning and deployment",
   157  	consToSet:    "tags= cpu-power= spaces=bar",
   158  	consFallback: "tags=foo cpu-power=42",
   159  
   160  	// a variation of the above case showing there's no difference
   161  	// between deployment (application, unit) and provisioning (machine)
   162  	// constraints when it comes to effective values.
   163  	effectiveModelCons:       "tags=foo cpu-power=42",
   164  	effectiveApplicationCons: "cpu-power= tags= spaces=bar",
   165  	effectiveUnitCons:        "arch=amd64 cpu-power= tags= spaces=bar",
   166  	effectiveMachineCons:     "cpu-power= tags= spaces=bar",
   167  }, {
   168  	about:        "container type can only be used for deployment, not provisioning",
   169  	consToSet:    "container=kvm arch=amd64",
   170  	consFallback: "container=lxd mem=8G",
   171  
   172  	// application deployment constraints are transformed into machine
   173  	// provisioning constraints, and the container type only makes
   174  	// sense currently as a deployment constraint, so it's cleared
   175  	// when merging application/model deployment constraints into
   176  	// effective machine provisioning constraints.
   177  	effectiveModelCons:       "container=lxd mem=8G",
   178  	effectiveApplicationCons: "container=kvm arch=amd64",
   179  	effectiveUnitCons:        "container=kvm mem=8G arch=amd64",
   180  	effectiveMachineCons:     "mem=8G arch=amd64",
   181  }, {
   182  	about:        "specify image virt-type when deploying applications on multi-hypervisor aware openstack",
   183  	consToSet:    "virt-type=kvm",
   184  	consFallback: "",
   185  
   186  	// application deployment constraints are transformed into machine
   187  	// provisioning constraints. Unit constraints must also have virt-type set
   188  	// to ensure consistency in scalability.
   189  	effectiveModelCons:       "",
   190  	effectiveApplicationCons: "virt-type=kvm",
   191  	effectiveUnitCons:        "arch=amd64 virt-type=kvm",
   192  	effectiveMachineCons:     "virt-type=kvm",
   193  }, {
   194  	about:        "ensure model and application constraints are separate",
   195  	consToSet:    "virt-type=kvm",
   196  	consFallback: "mem=2G",
   197  
   198  	// application deployment constraints are transformed into machine
   199  	// provisioning constraints. Unit constraints must also have virt-type set
   200  	// to ensure consistency in scalability.
   201  	effectiveModelCons:       "mem=2G",
   202  	effectiveApplicationCons: "virt-type=kvm",
   203  	effectiveUnitCons:        "arch=amd64 mem=2G virt-type=kvm",
   204  	effectiveMachineCons:     "mem=2G virt-type=kvm",
   205  }}
   206  
   207  func (s *constraintsValidationSuite) TestMachineConstraints(c *gc.C) {
   208  	for i, t := range setConstraintsTests {
   209  		c.Logf(
   210  			"test %d: %s\nconsToSet: %q\nconsFallback: %q\n",
   211  			i, t.about, t.consToSet, t.consFallback,
   212  		)
   213  		// Set fallbacks as model constraints and verify them.
   214  		err := s.State.SetModelConstraints(constraints.MustParse(t.consFallback))
   215  		c.Check(err, jc.ErrorIsNil)
   216  		econs, err := s.State.ModelConstraints()
   217  		c.Check(err, jc.ErrorIsNil)
   218  		c.Check(econs, jc.DeepEquals, constraints.MustParse(t.effectiveModelCons))
   219  		// Set the machine provisioning constraints.
   220  		m, err := s.addOneMachine(c, constraints.MustParse(t.consToSet))
   221  		c.Check(err, jc.ErrorIsNil)
   222  		// New machine provisioning constraints get merged with the fallbacks.
   223  		cons, err := m.Constraints()
   224  		c.Check(err, jc.ErrorIsNil)
   225  		c.Check(cons, jc.DeepEquals, constraints.MustParse(t.effectiveMachineCons))
   226  		// Changing them should result in the same result before provisioning.
   227  		err = m.SetConstraints(constraints.MustParse(t.consToSet))
   228  		c.Check(err, jc.ErrorIsNil)
   229  		cons, err = m.Constraints()
   230  		c.Check(err, jc.ErrorIsNil)
   231  		c.Check(cons, jc.DeepEquals, constraints.MustParse(t.effectiveMachineCons))
   232  	}
   233  }
   234  
   235  func (s *constraintsValidationSuite) TestApplicationConstraints(c *gc.C) {
   236  	charm := s.AddTestingCharm(c, "wordpress")
   237  	application := s.AddTestingApplication(c, "wordpress", charm)
   238  	for i, t := range setConstraintsTests {
   239  		c.Logf(
   240  			"test %d: %s\nconsToSet: %q\nconsFallback: %q\n",
   241  			i, t.about, t.consToSet, t.consFallback,
   242  		)
   243  		// Set fallbacks as model constraints and verify them.
   244  		err := s.State.SetModelConstraints(constraints.MustParse(t.consFallback))
   245  		c.Check(err, jc.ErrorIsNil)
   246  		econs, err := s.State.ModelConstraints()
   247  		c.Check(err, jc.ErrorIsNil)
   248  		c.Check(econs, jc.DeepEquals, constraints.MustParse(t.effectiveModelCons))
   249  		// Set the application deployment constraints.
   250  		err = application.SetConstraints(constraints.MustParse(t.consToSet))
   251  		c.Check(err, jc.ErrorIsNil)
   252  		u, err := application.AddUnit(state.AddUnitParams{})
   253  		c.Check(err, jc.ErrorIsNil)
   254  		// New unit deployment constraints get merged with the fallbacks.
   255  		ucons, err := u.Constraints()
   256  		c.Check(err, jc.ErrorIsNil)
   257  		c.Check(*ucons, jc.DeepEquals, constraints.MustParse(t.effectiveUnitCons))
   258  		// Application constraints remain as set.
   259  		scons, err := application.Constraints()
   260  		c.Check(err, jc.ErrorIsNil)
   261  		c.Check(scons, jc.DeepEquals, constraints.MustParse(t.effectiveApplicationCons))
   262  	}
   263  }
   264  
   265  type applicationConstraintsSuite struct {
   266  	ConnSuite
   267  
   268  	applicationName string
   269  	testCharm       *state.Charm
   270  }
   271  
   272  var _ = gc.Suite(&applicationConstraintsSuite{})
   273  
   274  func (s *applicationConstraintsSuite) SetUpTest(c *gc.C) {
   275  	s.ConnSuite.SetUpTest(c)
   276  	s.policy.GetConstraintsValidator = func() (constraints.Validator, error) {
   277  		validator := constraints.NewValidator()
   278  		validator.RegisterVocabulary(constraints.VirtType, []string{"kvm"})
   279  		return validator, nil
   280  	}
   281  	s.applicationName = "wordpress"
   282  	s.testCharm = s.AddTestingCharm(c, s.applicationName)
   283  }
   284  
   285  func (s *applicationConstraintsSuite) TestAddApplicationInvalidConstraints(c *gc.C) {
   286  	cons := constraints.MustParse("virt-type=blah")
   287  	_, err := s.State.AddApplication(state.AddApplicationArgs{
   288  		Name: s.applicationName,
   289  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   290  			OS:      "ubuntu",
   291  			Channel: "20.04/stable",
   292  		}},
   293  		Charm:       s.testCharm,
   294  		Constraints: cons,
   295  	})
   296  	c.Assert(errors.Cause(err), gc.ErrorMatches, regexp.QuoteMeta("invalid constraint value: virt-type=blah\nvalid values are: [kvm]"))
   297  }
   298  
   299  func (s *applicationConstraintsSuite) TestAddApplicationValidConstraints(c *gc.C) {
   300  	cons := constraints.MustParse("virt-type=kvm")
   301  	application, err := s.State.AddApplication(state.AddApplicationArgs{
   302  		Name: s.applicationName,
   303  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   304  			OS:      "ubuntu",
   305  			Channel: "20.04/stable",
   306  		}},
   307  		Charm:       s.testCharm,
   308  		Constraints: cons,
   309  	})
   310  	c.Assert(err, jc.ErrorIsNil)
   311  	c.Assert(application, gc.NotNil)
   312  }
   313  
   314  func (s *applicationConstraintsSuite) TestConstraintsRetrieval(c *gc.C) {
   315  	posCons := constraints.MustParse("arch=amd64 spaces=db")
   316  	application, err := s.State.AddApplication(state.AddApplicationArgs{
   317  		Name: s.applicationName,
   318  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   319  			OS:      "ubuntu",
   320  			Channel: "20.04/stable",
   321  		}},
   322  		Charm:       s.testCharm,
   323  		Constraints: posCons,
   324  	})
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	c.Assert(application, gc.NotNil)
   327  
   328  	negCons := constraints.MustParse("arch=amd64 spaces=^db2")
   329  	negApplication, err := s.State.AddApplication(state.AddApplicationArgs{
   330  		Name: "unimportant",
   331  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   332  			OS:      "ubuntu",
   333  			Channel: "20.04/stable",
   334  		}},
   335  		Charm:       s.testCharm,
   336  		Constraints: negCons,
   337  	})
   338  	c.Assert(err, jc.ErrorIsNil)
   339  	c.Assert(negApplication, gc.NotNil)
   340  
   341  	cons, err := s.State.AllConstraints()
   342  	c.Assert(err, jc.ErrorIsNil)
   343  
   344  	vals := make([]string, len(cons))
   345  	for i, cons := range cons {
   346  		c.Log(cons.ID())
   347  		vals[i] = cons.Value().String()
   348  	}
   349  	// In addition to the application constraints, there is a single empty
   350  	// constraints document for the model.
   351  	c.Check(vals, jc.SameContents, []string{posCons.String(), negCons.String(), ""})
   352  
   353  	cons, err = s.State.ConstraintsBySpaceName("db")
   354  	c.Assert(err, jc.ErrorIsNil)
   355  	c.Assert(cons, gc.HasLen, 1)
   356  	c.Check(cons[0].Value(), jc.DeepEquals, posCons)
   357  
   358  	cons, err = s.State.ConstraintsBySpaceName("db2")
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	c.Assert(cons, gc.HasLen, 1)
   361  	c.Check(cons[0].Value(), jc.DeepEquals, negCons)
   362  }
   363  
   364  func (s *applicationConstraintsSuite) TestConstraintsSpaceNameChangeOps(c *gc.C) {
   365  	posCons := constraints.MustParse("spaces=db")
   366  	application, err := s.State.AddApplication(state.AddApplicationArgs{
   367  		Name: s.applicationName,
   368  		CharmOrigin: &state.CharmOrigin{Platform: &state.Platform{
   369  			OS:      "ubuntu",
   370  			Channel: "20.04/stable",
   371  		}},
   372  		Charm:       s.testCharm,
   373  		Constraints: posCons,
   374  	})
   375  	c.Assert(err, jc.ErrorIsNil)
   376  	c.Assert(application, gc.NotNil)
   377  
   378  	cons, err := s.State.ConstraintsBySpaceName("db")
   379  	c.Assert(err, jc.ErrorIsNil)
   380  	c.Assert(cons, gc.HasLen, 1)
   381  
   382  	ops := cons[0].ChangeSpaceNameOps("db", "external")
   383  	c.Assert(ops, gc.HasLen, 1)
   384  	op := ops[0]
   385  	c.Check(op.C, gc.Equals, "constraints")
   386  	c.Check(op.Assert, gc.Equals, txn.DocExists)
   387  
   388  	bd, ok := op.Update.(bson.D)
   389  	c.Assert(ok, jc.IsTrue)
   390  	c.Assert(bd, gc.HasLen, 1)
   391  }