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 }