gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/servicestate/quota_control_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package servicestate_test 21 22 import ( 23 "fmt" 24 "path/filepath" 25 "time" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/dirs" 30 "github.com/snapcore/snapd/gadget/quantity" 31 "github.com/snapcore/snapd/overlord/configstate/config" 32 "github.com/snapcore/snapd/overlord/servicestate" 33 "github.com/snapcore/snapd/overlord/servicestate/servicestatetest" 34 "github.com/snapcore/snapd/overlord/snapstate" 35 "github.com/snapcore/snapd/overlord/state" 36 "github.com/snapcore/snapd/snap" 37 "github.com/snapcore/snapd/snap/snaptest" 38 "github.com/snapcore/snapd/snapdenv" 39 "github.com/snapcore/snapd/systemd" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 type quotaControlSuite struct { 44 baseServiceMgrTestSuite 45 } 46 47 var _ = Suite("aControlSuite{}) 48 49 func (s *quotaControlSuite) SetUpTest(c *C) { 50 s.baseServiceMgrTestSuite.SetUpTest(c) 51 52 // we don't need the EnsureSnapServices ensure loop to run by default 53 servicestate.MockEnsuredSnapServices(s.mgr, true) 54 55 // we enable quota-groups by default 56 s.state.Lock() 57 defer s.state.Unlock() 58 tr := config.NewTransaction(s.state) 59 tr.Set("core", "experimental.quota-groups", true) 60 tr.Commit() 61 62 // mock that we have a new enough version of systemd by default 63 r := servicestate.MockSystemdVersion(248) 64 s.AddCleanup(r) 65 } 66 67 type quotaGroupState struct { 68 MemoryLimit quantity.Size 69 SubGroups []string 70 ParentGroup string 71 Snaps []string 72 } 73 74 func checkQuotaState(c *C, st *state.State, exp map[string]quotaGroupState) { 75 m, err := servicestate.AllQuotas(st) 76 c.Assert(err, IsNil) 77 c.Assert(m, HasLen, len(exp)) 78 for name, grp := range m { 79 expGrp, ok := exp[name] 80 c.Assert(ok, Equals, true, Commentf("unexpected group %q in state", name)) 81 c.Assert(grp.MemoryLimit, Equals, expGrp.MemoryLimit) 82 c.Assert(grp.ParentGroup, Equals, expGrp.ParentGroup) 83 84 c.Assert(grp.Snaps, HasLen, len(expGrp.Snaps)) 85 if len(expGrp.Snaps) != 0 { 86 c.Assert(grp.Snaps, DeepEquals, expGrp.Snaps) 87 88 // also check on the service file states 89 for _, sn := range expGrp.Snaps { 90 // meh assume all services are named svc1 91 slicePath := name 92 if grp.ParentGroup != "" { 93 slicePath = grp.ParentGroup + "/" + name 94 } 95 checkSvcAndSliceState(c, sn+".svc1", slicePath, grp.MemoryLimit) 96 } 97 } 98 99 c.Assert(grp.SubGroups, HasLen, len(expGrp.SubGroups)) 100 if len(expGrp.SubGroups) != 0 { 101 c.Assert(grp.SubGroups, DeepEquals, expGrp.SubGroups) 102 } 103 } 104 } 105 106 func checkSvcAndSliceState(c *C, snapSvc string, slicePath string, sliceMem quantity.Size) { 107 slicePath = systemd.EscapeUnitNamePath(slicePath) 108 // make sure the service file exists 109 svcFileName := filepath.Join(dirs.SnapServicesDir, "snap."+snapSvc+".service") 110 c.Assert(svcFileName, testutil.FilePresent) 111 112 if sliceMem != 0 { 113 // the service file should mention this slice 114 c.Assert(svcFileName, testutil.FileContains, fmt.Sprintf("\nSlice=snap.%s.slice\n", slicePath)) 115 } else { 116 c.Assert(svcFileName, Not(testutil.FileContains), fmt.Sprintf("Slice=snap.%s.slice", slicePath)) 117 } 118 checkSliceState(c, slicePath, sliceMem) 119 } 120 121 func checkSliceState(c *C, sliceName string, sliceMem quantity.Size) { 122 sliceFileName := filepath.Join(dirs.SnapServicesDir, "snap."+sliceName+".slice") 123 if sliceMem != 0 { 124 c.Assert(sliceFileName, testutil.FilePresent) 125 c.Assert(sliceFileName, testutil.FileContains, fmt.Sprintf("\nMemoryMax=%s\n", sliceMem.String())) 126 } else { 127 c.Assert(sliceFileName, testutil.FileAbsent) 128 } 129 } 130 131 func systemctlCallsForSliceStart(name string) []expectedSystemctl { 132 name = systemd.EscapeUnitNamePath(name) 133 slice := "snap." + name + ".slice" 134 return []expectedSystemctl{ 135 {expArgs: []string{"start", slice}}, 136 } 137 } 138 139 func systemctlCallsForSliceStop(name string) []expectedSystemctl { 140 name = systemd.EscapeUnitNamePath(name) 141 slice := "snap." + name + ".slice" 142 return []expectedSystemctl{ 143 {expArgs: []string{"stop", slice}}, 144 { 145 expArgs: []string{"show", "--property=ActiveState", slice}, 146 output: "ActiveState=inactive", 147 }, 148 } 149 } 150 151 func systemctlCallsForServiceRestart(name string) []expectedSystemctl { 152 svc := "snap." + name + ".svc1.service" 153 return []expectedSystemctl{ 154 { 155 expArgs: []string{"show", "--property=Id,ActiveState,UnitFileState,Type", svc}, 156 output: fmt.Sprintf("Id=%s\nActiveState=active\nUnitFileState=enabled\nType=simple\n", svc), 157 }, 158 {expArgs: []string{"stop", svc}}, 159 { 160 expArgs: []string{"show", "--property=ActiveState", svc}, 161 output: "ActiveState=inactive", 162 }, 163 {expArgs: []string{"start", svc}}, 164 } 165 } 166 167 func systemctlCallsForCreateQuota(groupName string, snapNames ...string) []expectedSystemctl { 168 calls := join( 169 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 170 systemctlCallsForSliceStart(groupName), 171 ) 172 for _, snapName := range snapNames { 173 calls = join(calls, systemctlCallsForServiceRestart(snapName)) 174 } 175 176 return calls 177 } 178 179 func systemctlCallsVersion(version int) []expectedSystemctl { 180 return []expectedSystemctl{ 181 { 182 expArgs: []string{"--version"}, 183 output: fmt.Sprintf("systemd %d\n+FOO +BAR\n", version), 184 }, 185 } 186 } 187 188 func join(calls ...[]expectedSystemctl) []expectedSystemctl { 189 fullCall := []expectedSystemctl{} 190 for _, call := range calls { 191 fullCall = append(fullCall, call...) 192 } 193 194 return fullCall 195 } 196 197 func checkQuotaControlTasks(c *C, tasks []*state.Task, expAction *servicestate.QuotaControlAction) { 198 c.Assert(tasks, HasLen, 1) 199 t := tasks[0] 200 201 c.Assert(t.Kind(), Equals, "quota-control") 202 qcs := []*servicestate.QuotaControlAction{} 203 204 err := t.Get("quota-control-actions", &qcs) 205 c.Assert(err, IsNil) 206 c.Assert(qcs, HasLen, 1) 207 208 c.Assert(qcs[0], DeepEquals, expAction) 209 } 210 211 func (s *quotaControlSuite) TestCreateQuotaNotEnabled(c *C) { 212 s.state.Lock() 213 defer s.state.Unlock() 214 tr := config.NewTransaction(s.state) 215 tr.Set("core", "experimental.quota-groups", false) 216 tr.Commit() 217 218 // try to create an empty quota group 219 _, err := servicestate.CreateQuota(s.state, "foo", "", nil, quantity.SizeGiB) 220 c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.quota-groups' to true`) 221 } 222 223 func (s *quotaControlSuite) TestCreateQuotaSystemdTooOld(c *C) { 224 s.state.Lock() 225 defer s.state.Unlock() 226 227 r := s.mockSystemctlCalls(c, systemctlCallsVersion(229)) 228 defer r() 229 230 err := servicestate.CheckSystemdVersion() 231 c.Assert(err, IsNil) 232 233 _, err = servicestate.CreateQuota(s.state, "foo", "", nil, quantity.SizeGiB) 234 c.Assert(err, ErrorMatches, `systemd version too old: snap quotas requires systemd 230 and newer \(currently have 229\)`) 235 } 236 237 func (s *quotaControlSuite) TestCreateQuotaPrecond(c *C) { 238 st := s.state 239 st.Lock() 240 defer st.Unlock() 241 242 err := servicestatetest.MockQuotaInState(st, "foo", "", nil, 2*quantity.SizeGiB) 243 c.Assert(err, IsNil) 244 245 tests := []struct { 246 name string 247 mem quantity.Size 248 snaps []string 249 err string 250 }{ 251 {"foo", 16 * quantity.SizeKiB, nil, `group "foo" already exists`}, 252 {"new", 0, nil, `cannot create quota group with no memory limit set`}, 253 {"new", quantity.SizeKiB, nil, `memory limit for group "new" is too small: size must be larger than 4KB`}, 254 {"new", 16 * quantity.SizeKiB, []string{"baz"}, `cannot use snap "baz" in group "new": snap "baz" is not installed`}, 255 } 256 257 for _, t := range tests { 258 _, err := servicestate.CreateQuota(st, t.name, "", t.snaps, t.mem) 259 c.Check(err, ErrorMatches, t.err) 260 } 261 } 262 263 func (s *quotaControlSuite) TestRemoveQuotaPreseeding(c *C) { 264 r := snapdenv.MockPreseeding(true) 265 defer r() 266 267 st := s.state 268 st.Lock() 269 defer st.Unlock() 270 271 snapstate.Set(s.state, "test-snap", s.testSnapState) 272 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 273 274 // create a quota group 275 ts, err := servicestate.CreateQuota(s.state, "foo", "", []string{"test-snap"}, quantity.SizeGiB) 276 c.Assert(err, IsNil) 277 278 chg := st.NewChange("quota-control", "...") 279 chg.AddAll(ts) 280 281 exp := &servicestate.QuotaControlAction{ 282 Action: "create", 283 QuotaName: "foo", 284 AddSnaps: []string{"test-snap"}, 285 MemoryLimit: quantity.SizeGiB, 286 } 287 288 checkQuotaControlTasks(c, chg.Tasks(), exp) 289 290 // run the change 291 st.Unlock() 292 defer s.se.Stop() 293 err = s.o.Settle(5 * time.Second) 294 st.Lock() 295 c.Assert(err, IsNil) 296 297 // check that the quota groups were created in the state 298 checkQuotaState(c, st, map[string]quotaGroupState{ 299 "foo": { 300 MemoryLimit: quantity.SizeGiB, 301 Snaps: []string{"test-snap"}, 302 }, 303 }) 304 305 // but removing a quota doesn't work, since it just doesn't make sense to be 306 // able to remove a quota group while preseeding, so we purposely fail 307 _, err = servicestate.RemoveQuota(st, "foo") 308 c.Assert(err, ErrorMatches, `removing quota groups not supported while preseeding`) 309 } 310 311 func (s *quotaControlSuite) TestCreateUpdateRemoveQuotaHappy(c *C) { 312 r := s.mockSystemctlCalls(c, join( 313 // CreateQuota for foo - success 314 systemctlCallsForCreateQuota("foo", "test-snap"), 315 316 // UpdateQuota for foo 317 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 318 319 // RemoveQuota for foo 320 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 321 systemctlCallsForSliceStop("foo"), 322 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 323 systemctlCallsForServiceRestart("test-snap"), 324 )) 325 defer r() 326 327 st := s.state 328 st.Lock() 329 defer st.Unlock() 330 331 // setup the snap so it exists 332 snapstate.Set(s.state, "test-snap", s.testSnapState) 333 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 334 335 // create the quota group 336 ts, err := servicestate.CreateQuota(st, "foo", "", []string{"test-snap"}, quantity.SizeGiB) 337 c.Assert(err, IsNil) 338 339 chg := st.NewChange("quota-control", "...") 340 chg.AddAll(ts) 341 342 exp := &servicestate.QuotaControlAction{ 343 Action: "create", 344 QuotaName: "foo", 345 AddSnaps: []string{"test-snap"}, 346 MemoryLimit: quantity.SizeGiB, 347 } 348 349 checkQuotaControlTasks(c, chg.Tasks(), exp) 350 351 // run the change 352 st.Unlock() 353 defer s.se.Stop() 354 err = s.o.Settle(5 * time.Second) 355 st.Lock() 356 c.Assert(err, IsNil) 357 358 // check that the quota groups were created in the state 359 checkQuotaState(c, st, map[string]quotaGroupState{ 360 "foo": { 361 MemoryLimit: quantity.SizeGiB, 362 Snaps: []string{"test-snap"}, 363 }, 364 }) 365 366 // increase the memory limit 367 ts, err = servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{NewMemoryLimit: 2 * quantity.SizeGiB}) 368 c.Assert(err, IsNil) 369 370 chg = st.NewChange("quota-control", "...") 371 chg.AddAll(ts) 372 373 exp2 := &servicestate.QuotaControlAction{ 374 Action: "update", 375 QuotaName: "foo", 376 MemoryLimit: 2 * quantity.SizeGiB, 377 } 378 379 checkQuotaControlTasks(c, chg.Tasks(), exp2) 380 381 // run the change 382 st.Unlock() 383 defer s.se.Stop() 384 err = s.o.Settle(5 * time.Second) 385 st.Lock() 386 c.Assert(err, IsNil) 387 388 checkQuotaState(c, st, map[string]quotaGroupState{ 389 "foo": { 390 MemoryLimit: 2 * quantity.SizeGiB, 391 Snaps: []string{"test-snap"}, 392 }, 393 }) 394 395 // remove the quota 396 ts, err = servicestate.RemoveQuota(st, "foo") 397 c.Assert(err, IsNil) 398 399 chg = st.NewChange("quota-control", "...") 400 chg.AddAll(ts) 401 402 exp3 := &servicestate.QuotaControlAction{ 403 Action: "remove", 404 QuotaName: "foo", 405 } 406 407 checkQuotaControlTasks(c, chg.Tasks(), exp3) 408 409 // run the change 410 st.Unlock() 411 defer s.se.Stop() 412 err = s.o.Settle(5 * time.Second) 413 st.Lock() 414 c.Assert(err, IsNil) 415 416 checkQuotaState(c, st, nil) 417 } 418 419 func (s *quotaControlSuite) TestEnsureSnapAbsentFromQuotaGroup(c *C) { 420 r := s.mockSystemctlCalls(c, join( 421 // CreateQuota for foo 422 systemctlCallsForCreateQuota("foo", "test-snap", "test-snap2"), 423 424 // EnsureSnapAbsentFromQuota with just test-snap restarted since it is 425 // no longer in the group 426 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 427 systemctlCallsForServiceRestart("test-snap"), 428 429 // another identical call to EnsureSnapAbsentFromQuota does nothing 430 // since the function is idempotent 431 432 // EnsureSnapAbsentFromQuota with just test-snap2 restarted since it is no 433 // longer in the group 434 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 435 systemctlCallsForServiceRestart("test-snap2"), 436 )) 437 defer r() 438 439 st := s.state 440 st.Lock() 441 defer st.Unlock() 442 // setup test-snap 443 snapstate.Set(s.state, "test-snap", s.testSnapState) 444 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 445 // and test-snap2 446 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 447 snapst2 := &snapstate.SnapState{ 448 Sequence: []*snap.SideInfo{si2}, 449 Current: si2.Revision, 450 Active: true, 451 SnapType: "app", 452 } 453 snapstate.Set(s.state, "test-snap2", snapst2) 454 snaptest.MockSnapCurrent(c, testYaml2, si2) 455 456 // create a quota group 457 ts, err := servicestate.CreateQuota(s.state, "foo", "", []string{"test-snap", "test-snap2"}, quantity.SizeGiB) 458 c.Assert(err, IsNil) 459 460 chg := st.NewChange("quota-control", "...") 461 chg.AddAll(ts) 462 463 exp := &servicestate.QuotaControlAction{ 464 Action: "create", 465 QuotaName: "foo", 466 AddSnaps: []string{"test-snap", "test-snap2"}, 467 MemoryLimit: quantity.SizeGiB, 468 } 469 470 checkQuotaControlTasks(c, chg.Tasks(), exp) 471 472 // run the change 473 st.Unlock() 474 defer s.se.Stop() 475 err = s.o.Settle(5 * time.Second) 476 st.Lock() 477 c.Assert(err, IsNil) 478 479 checkQuotaState(c, st, map[string]quotaGroupState{ 480 "foo": { 481 MemoryLimit: quantity.SizeGiB, 482 Snaps: []string{"test-snap", "test-snap2"}, 483 }, 484 }) 485 486 // remove test-snap from the group 487 err = servicestate.EnsureSnapAbsentFromQuota(s.state, "test-snap") 488 c.Assert(err, IsNil) 489 490 checkQuotaState(c, st, map[string]quotaGroupState{ 491 "foo": { 492 MemoryLimit: quantity.SizeGiB, 493 Snaps: []string{"test-snap2"}, 494 }, 495 }) 496 497 // removing the same snap twice works as well but does nothing 498 err = servicestate.EnsureSnapAbsentFromQuota(s.state, "test-snap") 499 c.Assert(err, IsNil) 500 501 // now remove test-snap2 too 502 err = servicestate.EnsureSnapAbsentFromQuota(s.state, "test-snap2") 503 c.Assert(err, IsNil) 504 505 // and check that it got updated in the state 506 checkQuotaState(c, st, map[string]quotaGroupState{ 507 "foo": { 508 MemoryLimit: quantity.SizeGiB, 509 }, 510 }) 511 512 // it's not an error to call EnsureSnapAbsentFromQuotaGroup on a snap that 513 // is not in any quota group 514 err = servicestate.EnsureSnapAbsentFromQuota(s.state, "test-snap33333") 515 c.Assert(err, IsNil) 516 } 517 518 func (s *quotaControlSuite) TestUpdateQuotaGroupNotEnabled(c *C) { 519 s.state.Lock() 520 defer s.state.Unlock() 521 tr := config.NewTransaction(s.state) 522 tr.Set("core", "experimental.quota-groups", false) 523 tr.Commit() 524 525 opts := servicestate.QuotaGroupUpdate{} 526 _, err := servicestate.UpdateQuota(s.state, "foo", opts) 527 c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.quota-groups' to true`) 528 } 529 530 func (s *quotaControlSuite) TestUpdateQuotaPrecond(c *C) { 531 st := s.state 532 st.Lock() 533 defer st.Unlock() 534 535 err := servicestatetest.MockQuotaInState(st, "foo", "", nil, 2*quantity.SizeGiB) 536 c.Assert(err, IsNil) 537 538 tests := []struct { 539 name string 540 opts servicestate.QuotaGroupUpdate 541 err string 542 }{ 543 {"what", servicestate.QuotaGroupUpdate{}, `group "what" does not exist`}, 544 {"foo", servicestate.QuotaGroupUpdate{NewMemoryLimit: quantity.SizeGiB}, `cannot decrease memory limit of existing quota-group, remove and re-create it to decrease the limit`}, 545 {"foo", servicestate.QuotaGroupUpdate{AddSnaps: []string{"baz"}}, `cannot use snap "baz" in group "foo": snap "baz" is not installed`}, 546 } 547 548 for _, t := range tests { 549 _, err := servicestate.UpdateQuota(st, t.name, t.opts) 550 c.Check(err, ErrorMatches, t.err) 551 } 552 } 553 554 func (s *quotaControlSuite) TestRemoveQuotaPrecond(c *C) { 555 st := s.state 556 st.Lock() 557 defer st.Unlock() 558 559 err := servicestatetest.MockQuotaInState(st, "foo", "", nil, 2*quantity.SizeGiB) 560 c.Assert(err, IsNil) 561 err = servicestatetest.MockQuotaInState(st, "bar", "foo", nil, quantity.SizeGiB) 562 c.Assert(err, IsNil) 563 564 _, err = servicestate.RemoveQuota(st, "what") 565 c.Check(err, ErrorMatches, `cannot remove non-existent quota group "what"`) 566 567 _, err = servicestate.RemoveQuota(st, "foo") 568 c.Check(err, ErrorMatches, `cannot remove quota group "foo" with sub-groups, remove the sub-groups first`) 569 } 570 571 func (s *quotaControlSuite) createQuota(c *C, name string, limit quantity.Size, snaps ...string) { 572 ts, err := servicestate.CreateQuota(s.state, name, "", snaps, limit) 573 c.Assert(err, IsNil) 574 575 chg := s.state.NewChange("quota-control", "...") 576 chg.AddAll(ts) 577 578 // run the change 579 s.state.Unlock() 580 err = s.o.Settle(5 * time.Second) 581 s.state.Lock() 582 c.Assert(err, IsNil) 583 } 584 585 func (s *quotaControlSuite) TestSnapOpUpdateQuotaConflict(c *C) { 586 r := s.mockSystemctlCalls(c, join( 587 // CreateQuota for foo 588 systemctlCallsForCreateQuota("foo", "test-snap"), 589 )) 590 defer r() 591 592 st := s.state 593 st.Lock() 594 defer st.Unlock() 595 596 // setup test-snap 597 snapstate.Set(s.state, "test-snap", s.testSnapState) 598 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 599 // and test-snap2 600 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 601 snapst2 := &snapstate.SnapState{ 602 Sequence: []*snap.SideInfo{si2}, 603 Current: si2.Revision, 604 Active: true, 605 SnapType: "app", 606 } 607 snapstate.Set(s.state, "test-snap2", snapst2) 608 snaptest.MockSnapCurrent(c, testYaml2, si2) 609 610 // create a quota group 611 defer s.se.Stop() 612 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 613 614 ts, err := snapstate.Disable(st, "test-snap2") 615 c.Assert(err, IsNil) 616 chg1 := s.state.NewChange("disable", "...") 617 chg1.AddAll(ts) 618 619 _, err = servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{AddSnaps: []string{"test-snap2"}}) 620 c.Assert(err, ErrorMatches, `snap "test-snap2" has "disable" change in progress`) 621 } 622 623 func (s *quotaControlSuite) TestSnapOpCreateQuotaConflict(c *C) { 624 st := s.state 625 st.Lock() 626 defer st.Unlock() 627 628 // setup test-snap 629 snapstate.Set(s.state, "test-snap", s.testSnapState) 630 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 631 632 ts, err := snapstate.Disable(st, "test-snap") 633 c.Assert(err, IsNil) 634 chg1 := s.state.NewChange("disable", "...") 635 chg1.AddAll(ts) 636 637 _, err = servicestate.CreateQuota(s.state, "foo", "", []string{"test-snap"}, quantity.SizeGiB) 638 c.Assert(err, ErrorMatches, `snap "test-snap" has "disable" change in progress`) 639 } 640 641 func (s *quotaControlSuite) TestSnapOpRemoveQuotaConflict(c *C) { 642 r := s.mockSystemctlCalls(c, join( 643 // CreateQuota for foo 644 systemctlCallsForCreateQuota("foo", "test-snap"), 645 )) 646 defer r() 647 648 st := s.state 649 st.Lock() 650 defer st.Unlock() 651 652 // setup test-snap 653 snapstate.Set(s.state, "test-snap", s.testSnapState) 654 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 655 656 // create a quota group 657 defer s.se.Stop() 658 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 659 660 ts, err := snapstate.Disable(st, "test-snap") 661 c.Assert(err, IsNil) 662 chg1 := s.state.NewChange("disable", "...") 663 chg1.AddAll(ts) 664 665 _, err = servicestate.RemoveQuota(st, "foo") 666 c.Assert(err, ErrorMatches, `snap "test-snap" has "disable" change in progress`) 667 } 668 669 func (s *quotaControlSuite) TestCreateQuotaSnapOpConflict(c *C) { 670 st := s.state 671 st.Lock() 672 defer st.Unlock() 673 674 // setup test-snap 675 snapstate.Set(s.state, "test-snap", s.testSnapState) 676 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 677 678 ts, err := servicestate.CreateQuota(s.state, "foo", "", []string{"test-snap"}, quantity.SizeGiB) 679 c.Assert(err, IsNil) 680 chg1 := s.state.NewChange("quota-control", "...") 681 chg1.AddAll(ts) 682 683 _, err = snapstate.Disable(st, "test-snap") 684 c.Assert(err, ErrorMatches, `snap "test-snap" has "quota-control" change in progress`) 685 } 686 687 func (s *quotaControlSuite) TestUpdateQuotaSnapOpConflict(c *C) { 688 r := s.mockSystemctlCalls(c, join( 689 // CreateQuota for foo 690 systemctlCallsForCreateQuota("foo", "test-snap"), 691 )) 692 defer r() 693 694 st := s.state 695 st.Lock() 696 defer st.Unlock() 697 698 // setup test-snap 699 snapstate.Set(s.state, "test-snap", s.testSnapState) 700 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 701 // and test-snap2 702 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 703 snapst2 := &snapstate.SnapState{ 704 Sequence: []*snap.SideInfo{si2}, 705 Current: si2.Revision, 706 Active: true, 707 SnapType: "app", 708 } 709 snapstate.Set(s.state, "test-snap2", snapst2) 710 snaptest.MockSnapCurrent(c, testYaml2, si2) 711 712 // create a quota group 713 defer s.se.Stop() 714 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 715 716 ts, err := servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{AddSnaps: []string{"test-snap2"}}) 717 c.Assert(err, IsNil) 718 chg1 := s.state.NewChange("quota-control", "...") 719 chg1.AddAll(ts) 720 721 _, err = snapstate.Disable(st, "test-snap2") 722 c.Assert(err, ErrorMatches, `snap "test-snap2" has "quota-control" change in progress`) 723 } 724 725 func (s *quotaControlSuite) TestRemoveQuotaSnapOpConflict(c *C) { 726 r := s.mockSystemctlCalls(c, join( 727 // CreateQuota for foo 728 systemctlCallsForCreateQuota("foo", "test-snap"), 729 )) 730 defer r() 731 732 st := s.state 733 st.Lock() 734 defer st.Unlock() 735 736 // setup test-snap 737 snapstate.Set(s.state, "test-snap", s.testSnapState) 738 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 739 740 // create a quota group 741 defer s.se.Stop() 742 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 743 744 ts, err := servicestate.RemoveQuota(st, "foo") 745 c.Assert(err, IsNil) 746 chg1 := s.state.NewChange("quota-control", "...") 747 chg1.AddAll(ts) 748 749 _, err = snapstate.Disable(st, "test-snap") 750 c.Assert(err, ErrorMatches, `snap "test-snap" has "quota-control" change in progress`) 751 } 752 753 func (s *quotaControlSuite) TestRemoveQuotaLateSnapOpConflict(c *C) { 754 r := s.mockSystemctlCalls(c, join( 755 // CreateQuota for foo 756 systemctlCallsForCreateQuota("foo", "test-snap"), 757 )) 758 defer r() 759 760 st := s.state 761 st.Lock() 762 defer st.Unlock() 763 764 // setup test-snap 765 snapstate.Set(s.state, "test-snap", s.testSnapState) 766 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 767 768 // create a quota group 769 defer s.se.Stop() 770 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 771 772 ts, err := servicestate.RemoveQuota(st, "foo") 773 c.Assert(err, IsNil) 774 c.Assert(ts.Tasks(), HasLen, 1) 775 chg1 := s.state.NewChange("quota-control", "...") 776 chg1.AddAll(ts) 777 778 // the group is already gone, but the task is not finished 779 s.state.Set("quotas", nil) 780 task := ts.Tasks()[0] 781 task.Set("state-updated", servicestate.QuotaStateUpdated{ 782 BootID: "boot-id", 783 AppsToRestartBySnap: map[string][]string{ 784 "test-snap": {"svc1"}, 785 }, 786 }) 787 788 _, err = snapstate.Disable(st, "test-snap") 789 c.Assert(err, ErrorMatches, `snap "test-snap" has "quota-control" change in progress`) 790 } 791 792 func (s *quotaControlSuite) TestUpdateQuotaUpdateQuotaConflict(c *C) { 793 r := s.mockSystemctlCalls(c, join( 794 // CreateQuota for foo 795 systemctlCallsForCreateQuota("foo", "test-snap"), 796 )) 797 defer r() 798 799 st := s.state 800 st.Lock() 801 defer st.Unlock() 802 803 // setup test-snap 804 snapstate.Set(s.state, "test-snap", s.testSnapState) 805 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 806 // and test-snap2 807 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 808 snapst2 := &snapstate.SnapState{ 809 Sequence: []*snap.SideInfo{si2}, 810 Current: si2.Revision, 811 Active: true, 812 SnapType: "app", 813 } 814 snapstate.Set(s.state, "test-snap2", snapst2) 815 snaptest.MockSnapCurrent(c, testYaml2, si2) 816 817 // create a quota group 818 defer s.se.Stop() 819 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 820 821 ts, err := servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{AddSnaps: []string{"test-snap2"}}) 822 c.Assert(err, IsNil) 823 chg1 := s.state.NewChange("quota-control", "...") 824 chg1.AddAll(ts) 825 826 _, err = servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{NewMemoryLimit: 2 * quantity.SizeGiB}) 827 c.Assert(err, ErrorMatches, `quota group "foo" has "quota-control" change in progress`) 828 } 829 830 func (s *quotaControlSuite) TestUpdateQuotaRemoveQuotaConflict(c *C) { 831 r := s.mockSystemctlCalls(c, join( 832 // CreateQuota for foo 833 systemctlCallsForCreateQuota("foo", "test-snap"), 834 )) 835 defer r() 836 837 st := s.state 838 st.Lock() 839 defer st.Unlock() 840 841 // setup test-snap 842 snapstate.Set(s.state, "test-snap", s.testSnapState) 843 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 844 // and test-snap2 845 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 846 snapst2 := &snapstate.SnapState{ 847 Sequence: []*snap.SideInfo{si2}, 848 Current: si2.Revision, 849 Active: true, 850 SnapType: "app", 851 } 852 snapstate.Set(s.state, "test-snap2", snapst2) 853 snaptest.MockSnapCurrent(c, testYaml2, si2) 854 855 // create a quota group 856 defer s.se.Stop() 857 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 858 859 ts, err := servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{AddSnaps: []string{"test-snap2"}}) 860 c.Assert(err, IsNil) 861 chg1 := s.state.NewChange("quota-control", "...") 862 chg1.AddAll(ts) 863 864 _, err = servicestate.RemoveQuota(st, "foo") 865 c.Assert(err, ErrorMatches, `quota group "foo" has "quota-control" change in progress`) 866 } 867 868 func (s *quotaControlSuite) TestRemoveQuotaUpdateQuotaConflict(c *C) { 869 r := s.mockSystemctlCalls(c, join( 870 // CreateQuota for foo 871 systemctlCallsForCreateQuota("foo", "test-snap"), 872 )) 873 defer r() 874 875 st := s.state 876 st.Lock() 877 defer st.Unlock() 878 879 // setup test-snap 880 snapstate.Set(s.state, "test-snap", s.testSnapState) 881 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 882 // and test-snap2 883 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 884 snapst2 := &snapstate.SnapState{ 885 Sequence: []*snap.SideInfo{si2}, 886 Current: si2.Revision, 887 Active: true, 888 SnapType: "app", 889 } 890 snapstate.Set(s.state, "test-snap2", snapst2) 891 snaptest.MockSnapCurrent(c, testYaml2, si2) 892 893 // create a quota group 894 defer s.se.Stop() 895 s.createQuota(c, "foo", quantity.SizeGiB, "test-snap") 896 897 ts, err := servicestate.RemoveQuota(st, "foo") 898 c.Assert(err, IsNil) 899 chg1 := s.state.NewChange("quota-control", "...") 900 chg1.AddAll(ts) 901 902 _, err = servicestate.UpdateQuota(st, "foo", servicestate.QuotaGroupUpdate{AddSnaps: []string{"test-snap2"}}) 903 c.Assert(err, ErrorMatches, `quota group "foo" has "quota-control" change in progress`) 904 } 905 906 func (s *quotaControlSuite) TestCreateQuotaCreateQuotaConflict(c *C) { 907 st := s.state 908 st.Lock() 909 defer st.Unlock() 910 911 // setup test-snap 912 snapstate.Set(s.state, "test-snap", s.testSnapState) 913 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 914 // and test-snap2 915 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 916 snapst2 := &snapstate.SnapState{ 917 Sequence: []*snap.SideInfo{si2}, 918 Current: si2.Revision, 919 Active: true, 920 SnapType: "app", 921 } 922 snapstate.Set(s.state, "test-snap2", snapst2) 923 snaptest.MockSnapCurrent(c, testYaml2, si2) 924 925 ts, err := servicestate.CreateQuota(st, "foo", "", []string{"test-snap"}, quantity.SizeGiB) 926 c.Assert(err, IsNil) 927 chg1 := s.state.NewChange("quota-control", "...") 928 chg1.AddAll(ts) 929 930 _, err = servicestate.CreateQuota(st, "foo", "", []string{"test-snap2"}, 2*quantity.SizeGiB) 931 c.Assert(err, ErrorMatches, `quota group "foo" has "quota-control" change in progress`) 932 }