github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/overlord/servicestate/quota_handlers_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 . "gopkg.in/check.v1" 24 25 "github.com/snapcore/snapd/gadget/quantity" 26 "github.com/snapcore/snapd/overlord/configstate/config" 27 "github.com/snapcore/snapd/overlord/servicestate" 28 "github.com/snapcore/snapd/overlord/snapstate" 29 "github.com/snapcore/snapd/overlord/state" 30 "github.com/snapcore/snapd/snap" 31 "github.com/snapcore/snapd/snap/quota" 32 "github.com/snapcore/snapd/snap/snaptest" 33 "github.com/snapcore/snapd/snapdenv" 34 "github.com/snapcore/snapd/systemd" 35 ) 36 37 type quotaHandlersSuite struct { 38 baseServiceMgrTestSuite 39 } 40 41 var _ = Suite("aHandlersSuite{}) 42 43 func allGrps(c *C, st *state.State) map[string]*quota.Group { 44 allGrps, err := servicestate.AllQuotas(st) 45 c.Assert(err, IsNil) 46 47 return allGrps 48 } 49 50 func (s *quotaHandlersSuite) SetUpTest(c *C) { 51 s.baseServiceMgrTestSuite.SetUpTest(c) 52 53 // we don't need the EnsureSnapServices ensure loop to run by default 54 servicestate.MockEnsuredSnapServices(s.mgr, true) 55 56 // we enable quota-groups by default 57 s.state.Lock() 58 defer s.state.Unlock() 59 tr := config.NewTransaction(s.state) 60 tr.Set("core", "experimental.quota-groups", true) 61 tr.Commit() 62 63 // mock that we have a new enough version of systemd by default 64 r := servicestate.MockSystemdVersion(248) 65 s.AddCleanup(r) 66 } 67 68 func (s *quotaHandlersSuite) TestDoQuotaControlCreate(c *C) { 69 r := s.mockSystemctlCalls(c, join( 70 // doQuotaControl handler to create the group 71 systemctlCallsForCreateQuota("foo-group", "test-snap"), 72 )) 73 defer r() 74 75 st := s.state 76 st.Lock() 77 defer st.Unlock() 78 79 // setup the snap so it exists 80 snapstate.Set(s.state, "test-snap", s.testSnapState) 81 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 82 83 // make a fake task 84 t := st.NewTask("create-quota", "...") 85 86 qcs := []servicestate.QuotaControlAction{ 87 { 88 Action: "create", 89 QuotaName: "foo-group", 90 MemoryLimit: quantity.SizeGiB, 91 AddSnaps: []string{"test-snap"}, 92 }, 93 } 94 95 t.Set("quota-control-actions", &qcs) 96 97 st.Unlock() 98 err := s.o.ServiceManager().DoQuotaControl(t, nil) 99 st.Lock() 100 101 c.Assert(err, IsNil) 102 c.Assert(t.Status(), Equals, state.DoneStatus) 103 104 checkQuotaState(c, st, map[string]quotaGroupState{ 105 "foo-group": { 106 MemoryLimit: quantity.SizeGiB, 107 Snaps: []string{"test-snap"}, 108 }, 109 }) 110 } 111 112 func (s *quotaHandlersSuite) TestDoQuotaControlUpdate(c *C) { 113 r := s.mockSystemctlCalls(c, join( 114 // CreateQuota for foo-group 115 systemctlCallsForCreateQuota("foo-group", "test-snap"), 116 117 // doQuotaControl handler which updates the group 118 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 119 )) 120 defer r() 121 122 st := s.state 123 st.Lock() 124 defer st.Unlock() 125 126 // setup the snap so it exists 127 snapstate.Set(s.state, "test-snap", s.testSnapState) 128 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 129 130 // create a quota group 131 err := servicestate.CreateQuota(st, "foo-group", "", []string{"test-snap"}, quantity.SizeGiB) 132 c.Assert(err, IsNil) 133 134 // create a task for updating the quota group 135 t := st.NewTask("update-quota", "...") 136 137 // update the memory limit to be double 138 qcs := []servicestate.QuotaControlAction{ 139 { 140 Action: "update", 141 QuotaName: "foo-group", 142 MemoryLimit: 2 * quantity.SizeGiB, 143 }, 144 } 145 146 t.Set("quota-control-actions", &qcs) 147 148 st.Unlock() 149 err = s.o.ServiceManager().DoQuotaControl(t, nil) 150 st.Lock() 151 152 c.Assert(err, IsNil) 153 c.Assert(t.Status(), Equals, state.DoneStatus) 154 155 checkQuotaState(c, st, map[string]quotaGroupState{ 156 "foo-group": { 157 MemoryLimit: 2 * quantity.SizeGiB, 158 Snaps: []string{"test-snap"}, 159 }, 160 }) 161 } 162 163 func (s *quotaHandlersSuite) TestDoQuotaControlRemove(c *C) { 164 r := s.mockSystemctlCalls(c, join( 165 // CreateQuota for foo-group 166 systemctlCallsForCreateQuota("foo-group", "test-snap"), 167 168 // doQuotaControl handler which removes the group 169 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 170 systemctlCallsForSliceStop("foo-group"), 171 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 172 systemctlCallsForServiceRestart("test-snap"), 173 )) 174 defer r() 175 176 st := s.state 177 st.Lock() 178 defer st.Unlock() 179 180 // setup the snap so it exists 181 snapstate.Set(s.state, "test-snap", s.testSnapState) 182 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 183 184 // create a quota group 185 err := servicestate.CreateQuota(st, "foo-group", "", []string{"test-snap"}, quantity.SizeGiB) 186 c.Assert(err, IsNil) 187 188 // create a task for removing the quota group 189 t := st.NewTask("remove-quota", "...") 190 191 // update the memory limit to be double 192 qcs := []servicestate.QuotaControlAction{ 193 { 194 Action: "remove", 195 QuotaName: "foo-group", 196 }, 197 } 198 199 t.Set("quota-control-actions", &qcs) 200 201 st.Unlock() 202 err = s.o.ServiceManager().DoQuotaControl(t, nil) 203 st.Lock() 204 205 c.Assert(err, IsNil) 206 c.Assert(t.Status(), Equals, state.DoneStatus) 207 208 checkQuotaState(c, st, nil) 209 } 210 211 func (s *quotaHandlersSuite) TestQuotaCreatePreseeding(c *C) { 212 // should be no systemctl calls since we are preseeding 213 r := snapdenv.MockPreseeding(true) 214 defer r() 215 216 st := s.state 217 st.Lock() 218 defer st.Unlock() 219 220 // setup the snap so it exists 221 snapstate.Set(s.state, "test-snap", s.testSnapState) 222 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 223 224 // now we can create the quota group 225 qc := servicestate.QuotaControlAction{ 226 Action: "create", 227 QuotaName: "foo", 228 MemoryLimit: quantity.SizeGiB, 229 AddSnaps: []string{"test-snap"}, 230 } 231 232 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 233 c.Assert(err, IsNil) 234 235 // check that the quota groups were created in the state 236 checkQuotaState(c, st, map[string]quotaGroupState{ 237 "foo": { 238 MemoryLimit: quantity.SizeGiB, 239 Snaps: []string{"test-snap"}, 240 }, 241 }) 242 } 243 244 func (s *quotaHandlersSuite) TestQuotaCreate(c *C) { 245 r := s.mockSystemctlCalls(c, join( 246 // CreateQuota for non-installed snap - fails 247 248 // CreateQuota for foo - success 249 systemctlCallsForCreateQuota("foo", "test-snap"), 250 251 // CreateQuota for foo2 with overlapping snap already in foo 252 253 // CreateQuota for foo again - fails 254 )) 255 defer r() 256 257 st := s.state 258 st.Lock() 259 defer st.Unlock() 260 261 // trying to create a quota with a snap that doesn't exist fails 262 qc := servicestate.QuotaControlAction{ 263 Action: "create", 264 QuotaName: "foo", 265 MemoryLimit: quantity.SizeGiB, 266 AddSnaps: []string{"test-snap"}, 267 } 268 269 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 270 c.Assert(err, ErrorMatches, `cannot use snap "test-snap" in group "foo": snap "test-snap" is not installed`) 271 272 // setup the snap so it exists 273 snapstate.Set(s.state, "test-snap", s.testSnapState) 274 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 275 276 // now we can create the quota group 277 err = servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 278 c.Assert(err, IsNil) 279 280 // creating the same group again will fail 281 err = servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 282 c.Assert(err, ErrorMatches, `group "foo" already exists`) 283 284 // we can't add the same snap to a different group 285 qc2 := servicestate.QuotaControlAction{ 286 Action: "create", 287 QuotaName: "foo2", 288 MemoryLimit: quantity.SizeGiB, 289 AddSnaps: []string{"test-snap"}, 290 } 291 292 err = servicestate.QuotaCreate(st, nil, qc2, allGrps(c, st), nil, nil) 293 c.Assert(err, ErrorMatches, `cannot add snap "test-snap" to group "foo2": snap already in quota group "foo"`) 294 295 // check that the quota groups were created in the state 296 checkQuotaState(c, st, map[string]quotaGroupState{ 297 "foo": { 298 MemoryLimit: quantity.SizeGiB, 299 Snaps: []string{"test-snap"}, 300 }, 301 }) 302 } 303 304 func (s *quotaHandlersSuite) TestDoCreateSubGroupQuota(c *C) { 305 r := s.mockSystemctlCalls(c, join( 306 // CreateQuota for foo - no systemctl calls since no snaps in it 307 308 // CreateQuota for foo2 - fails thus no systemctl calls 309 310 // CreateQuota for foo2 - we don't write anything for the first quota 311 // since there are no snaps in the quota to track 312 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 313 systemctlCallsForSliceStart("foo-group"), 314 systemctlCallsForSliceStart("foo-group/foo2"), 315 systemctlCallsForServiceRestart("test-snap"), 316 )) 317 defer r() 318 319 st := s.state 320 st.Lock() 321 defer st.Unlock() 322 323 // setup the snap so it exists 324 snapstate.Set(s.state, "test-snap", s.testSnapState) 325 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 326 327 // create a quota group with no snaps to be the parent 328 qc := servicestate.QuotaControlAction{ 329 Action: "create", 330 QuotaName: "foo-group", 331 MemoryLimit: quantity.SizeGiB, 332 } 333 334 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 335 c.Assert(err, IsNil) 336 337 // trying to create a quota group with a non-existent parent group fails 338 qc2 := servicestate.QuotaControlAction{ 339 Action: "create", 340 QuotaName: "foo2", 341 MemoryLimit: quantity.SizeGiB, 342 ParentName: "foo-non-real", 343 AddSnaps: []string{"test-snap"}, 344 } 345 346 err = servicestate.QuotaCreate(st, nil, qc2, allGrps(c, st), nil, nil) 347 c.Assert(err, ErrorMatches, `cannot create group under non-existent parent group "foo-non-real"`) 348 349 // trying to create a quota group with too big of a limit to fit inside the 350 // parent fails 351 qc3 := servicestate.QuotaControlAction{ 352 Action: "create", 353 QuotaName: "foo2", 354 MemoryLimit: 2 * quantity.SizeGiB, 355 ParentName: "foo-group", 356 AddSnaps: []string{"test-snap"}, 357 } 358 359 err = servicestate.QuotaCreate(st, nil, qc3, allGrps(c, st), nil, nil) 360 c.Assert(err, ErrorMatches, `sub-group memory limit of 2 GiB is too large to fit inside remaining quota space 1 GiB for parent group foo-group`) 361 362 // now we can create a sub-quota 363 qc4 := servicestate.QuotaControlAction{ 364 Action: "create", 365 QuotaName: "foo2", 366 MemoryLimit: quantity.SizeGiB, 367 ParentName: "foo-group", 368 AddSnaps: []string{"test-snap"}, 369 } 370 371 err = servicestate.QuotaCreate(st, nil, qc4, allGrps(c, st), nil, nil) 372 c.Assert(err, IsNil) 373 374 // check that the quota groups were created in the state 375 checkQuotaState(c, st, map[string]quotaGroupState{ 376 "foo-group": { 377 MemoryLimit: quantity.SizeGiB, 378 SubGroups: []string{"foo2"}, 379 }, 380 "foo2": { 381 MemoryLimit: quantity.SizeGiB, 382 Snaps: []string{"test-snap"}, 383 ParentGroup: "foo-group", 384 }, 385 }) 386 387 // foo-group exists as a slice too, but has no snap services in the slice 388 checkSliceState(c, systemd.EscapeUnitNamePath("foo-group"), quantity.SizeGiB) 389 } 390 391 func (s *quotaHandlersSuite) TestQuotaRemove(c *C) { 392 r := s.mockSystemctlCalls(c, join( 393 // CreateQuota for foo 394 systemctlCallsForCreateQuota("foo", "test-snap"), 395 396 // for CreateQuota foo2 - no systemctl calls since there are no snaps 397 398 // for CreateQuota foo3 - no systemctl calls since there are no snaps 399 400 // RemoveQuota for foo2 - no daemon reload initially because 401 // we didn't modify anything, as there are no snaps in foo2 so we don't 402 // create that group on disk 403 // TODO: is this bit correct in practice? we are in effect calling 404 // systemctl stop <non-existing-slice> ? 405 systemctlCallsForSliceStop("foo/foo3"), 406 407 systemctlCallsForSliceStop("foo/foo2"), 408 409 // RemoveQuota for foo 410 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 411 systemctlCallsForSliceStop("foo"), 412 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 413 systemctlCallsForServiceRestart("test-snap"), 414 )) 415 defer r() 416 417 st := s.state 418 st.Lock() 419 defer st.Unlock() 420 421 // setup the snap so it exists 422 snapstate.Set(s.state, "test-snap", s.testSnapState) 423 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 424 425 // trying to remove a group that does not exist fails 426 qc := servicestate.QuotaControlAction{ 427 Action: "remove", 428 QuotaName: "not-exists", 429 } 430 431 err := servicestate.QuotaRemove(st, nil, qc, allGrps(c, st), nil, nil) 432 c.Assert(err, ErrorMatches, `cannot remove non-existent quota group "not-exists"`) 433 434 qc2 := servicestate.QuotaControlAction{ 435 Action: "create", 436 QuotaName: "foo", 437 MemoryLimit: quantity.SizeGiB, 438 AddSnaps: []string{"test-snap"}, 439 } 440 441 err = servicestate.QuotaCreate(st, nil, qc2, allGrps(c, st), nil, nil) 442 c.Assert(err, IsNil) 443 444 // create 2 quota sub-groups too 445 qc3 := servicestate.QuotaControlAction{ 446 Action: "create", 447 QuotaName: "foo2", 448 MemoryLimit: quantity.SizeGiB / 2, 449 ParentName: "foo", 450 } 451 452 err = servicestate.QuotaCreate(st, nil, qc3, allGrps(c, st), nil, nil) 453 c.Assert(err, IsNil) 454 455 qc4 := servicestate.QuotaControlAction{ 456 Action: "create", 457 QuotaName: "foo3", 458 MemoryLimit: quantity.SizeGiB / 2, 459 ParentName: "foo", 460 } 461 462 err = servicestate.QuotaCreate(st, nil, qc4, allGrps(c, st), nil, nil) 463 c.Assert(err, IsNil) 464 465 // check that the quota groups was created in the state 466 checkQuotaState(c, st, map[string]quotaGroupState{ 467 "foo": { 468 MemoryLimit: quantity.SizeGiB, 469 Snaps: []string{"test-snap"}, 470 SubGroups: []string{"foo2", "foo3"}, 471 }, 472 "foo2": { 473 MemoryLimit: quantity.SizeGiB / 2, 474 ParentGroup: "foo", 475 }, 476 "foo3": { 477 MemoryLimit: quantity.SizeGiB / 2, 478 ParentGroup: "foo", 479 }, 480 }) 481 482 // try removing the parent and it fails since it still has a sub-group 483 // under it 484 qc5 := servicestate.QuotaControlAction{ 485 Action: "remove", 486 QuotaName: "foo", 487 } 488 489 err = servicestate.QuotaRemove(st, nil, qc5, allGrps(c, st), nil, nil) 490 c.Assert(err, ErrorMatches, "cannot remove quota group with sub-groups, remove the sub-groups first") 491 492 // but we can remove the sub-group successfully first 493 qc6 := servicestate.QuotaControlAction{ 494 Action: "remove", 495 QuotaName: "foo3", 496 } 497 498 err = servicestate.QuotaRemove(st, nil, qc6, allGrps(c, st), nil, nil) 499 c.Assert(err, IsNil) 500 501 checkQuotaState(c, st, map[string]quotaGroupState{ 502 "foo": { 503 MemoryLimit: quantity.SizeGiB, 504 Snaps: []string{"test-snap"}, 505 SubGroups: []string{"foo2"}, 506 }, 507 "foo2": { 508 MemoryLimit: quantity.SizeGiB / 2, 509 ParentGroup: "foo", 510 }, 511 }) 512 513 // and we can remove the other sub-group 514 qc7 := servicestate.QuotaControlAction{ 515 Action: "remove", 516 QuotaName: "foo2", 517 } 518 519 err = servicestate.QuotaRemove(st, nil, qc7, allGrps(c, st), nil, nil) 520 c.Assert(err, IsNil) 521 522 checkQuotaState(c, st, map[string]quotaGroupState{ 523 "foo": { 524 MemoryLimit: quantity.SizeGiB, 525 Snaps: []string{"test-snap"}, 526 }, 527 }) 528 529 // now we can remove the quota from the state 530 qc8 := servicestate.QuotaControlAction{ 531 Action: "remove", 532 QuotaName: "foo", 533 } 534 535 err = servicestate.QuotaRemove(st, nil, qc8, allGrps(c, st), nil, nil) 536 c.Assert(err, IsNil) 537 538 checkQuotaState(c, st, nil) 539 540 // foo is not mentioned in the service and doesn't exist 541 checkSvcAndSliceState(c, "test-snap.svc1", "foo", 0) 542 } 543 544 func (s *quotaHandlersSuite) TestQuotaUpdateGroupNotExist(c *C) { 545 st := s.state 546 st.Lock() 547 defer st.Unlock() 548 549 // non-existent quota group 550 qc := servicestate.QuotaControlAction{ 551 Action: "update", 552 QuotaName: "non-existing", 553 } 554 555 err := servicestate.QuotaUpdate(st, nil, qc, allGrps(c, st), nil, nil) 556 c.Check(err, ErrorMatches, `group "non-existing" does not exist`) 557 } 558 559 func (s *quotaHandlersSuite) TestQuotaUpdateSubGroupTooBig(c *C) { 560 r := s.mockSystemctlCalls(c, join( 561 // CreateQuota for foo 562 systemctlCallsForCreateQuota("foo", "test-snap"), 563 564 // CreateQuota for foo2 565 systemctlCallsForCreateQuota("foo/foo2", "test-snap2"), 566 567 // UpdateQuota for foo2 - just the slice changes 568 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 569 570 // UpdateQuota for foo2 which fails - no systemctl calls 571 )) 572 defer r() 573 574 st := s.state 575 st.Lock() 576 defer st.Unlock() 577 578 // setup the snap so it exists 579 snapstate.Set(s.state, "test-snap", s.testSnapState) 580 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 581 // and test-snap2 582 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 583 snapst2 := &snapstate.SnapState{ 584 Sequence: []*snap.SideInfo{si2}, 585 Current: si2.Revision, 586 Active: true, 587 SnapType: "app", 588 } 589 snapstate.Set(s.state, "test-snap2", snapst2) 590 snaptest.MockSnapCurrent(c, testYaml2, si2) 591 592 // create a quota group 593 qc := servicestate.QuotaControlAction{ 594 Action: "create", 595 QuotaName: "foo", 596 MemoryLimit: quantity.SizeGiB, 597 AddSnaps: []string{"test-snap"}, 598 } 599 600 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 601 c.Assert(err, IsNil) 602 603 // ensure mem-limit is 1 GB 604 expFooGroupState := quotaGroupState{ 605 MemoryLimit: quantity.SizeGiB, 606 Snaps: []string{"test-snap"}, 607 } 608 checkQuotaState(c, st, map[string]quotaGroupState{ 609 "foo": expFooGroupState, 610 }) 611 612 // create a sub-group with 0.5 GiB 613 qc2 := servicestate.QuotaControlAction{ 614 Action: "create", 615 QuotaName: "foo2", 616 MemoryLimit: quantity.SizeGiB / 2, 617 AddSnaps: []string{"test-snap2"}, 618 ParentName: "foo", 619 } 620 621 err = servicestate.QuotaCreate(st, nil, qc2, allGrps(c, st), nil, nil) 622 c.Assert(err, IsNil) 623 624 expFooGroupState.SubGroups = []string{"foo2"} 625 626 expFoo2GroupState := quotaGroupState{ 627 MemoryLimit: quantity.SizeGiB / 2, 628 Snaps: []string{"test-snap2"}, 629 ParentGroup: "foo", 630 } 631 632 // verify it was set in state 633 checkQuotaState(c, st, map[string]quotaGroupState{ 634 "foo": expFooGroupState, 635 "foo2": expFoo2GroupState, 636 }) 637 638 // now try to increase it to the max size 639 qc3 := servicestate.QuotaControlAction{ 640 Action: "update", 641 QuotaName: "foo2", 642 MemoryLimit: quantity.SizeGiB, 643 } 644 645 err = servicestate.QuotaUpdate(st, nil, qc3, allGrps(c, st), nil, nil) 646 c.Assert(err, IsNil) 647 648 expFoo2GroupState.MemoryLimit = quantity.SizeGiB 649 // and check that it got updated in the state 650 checkQuotaState(c, st, map[string]quotaGroupState{ 651 "foo": expFooGroupState, 652 "foo2": expFoo2GroupState, 653 }) 654 655 // now try to increase it above the parent limit 656 qc4 := servicestate.QuotaControlAction{ 657 Action: "update", 658 QuotaName: "foo2", 659 MemoryLimit: 2 * quantity.SizeGiB, 660 } 661 662 err = servicestate.QuotaUpdate(st, nil, qc4, allGrps(c, st), nil, nil) 663 c.Assert(err, ErrorMatches, `cannot update quota "foo2": group "foo2" is invalid: sub-group memory limit of 2 GiB is too large to fit inside remaining quota space 1 GiB for parent group foo`) 664 665 // and make sure that the existing memory limit is still in place 666 checkQuotaState(c, st, map[string]quotaGroupState{ 667 "foo": expFooGroupState, 668 "foo2": expFoo2GroupState, 669 }) 670 } 671 672 func (s *quotaHandlersSuite) TestUpdateQuotaGroupNotEnabled(c *C) { 673 s.state.Lock() 674 defer s.state.Unlock() 675 tr := config.NewTransaction(s.state) 676 tr.Set("core", "experimental.quota-groups", false) 677 tr.Commit() 678 679 opts := servicestate.QuotaGroupUpdate{} 680 err := servicestate.UpdateQuota(s.state, "foo", opts) 681 c.Assert(err, ErrorMatches, `experimental feature disabled - test it by setting 'experimental.quota-groups' to true`) 682 } 683 684 func (s *quotaHandlersSuite) TestQuotaUpdateChangeMemLimit(c *C) { 685 r := s.mockSystemctlCalls(c, join( 686 // CreateQuota for foo 687 systemctlCallsForCreateQuota("foo", "test-snap"), 688 689 // UpdateQuota for foo - an existing slice was changed, so all we need 690 // to is daemon-reload 691 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 692 )) 693 defer r() 694 695 st := s.state 696 st.Lock() 697 defer st.Unlock() 698 699 // setup the snap so it exists 700 snapstate.Set(s.state, "test-snap", s.testSnapState) 701 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 702 703 // create a quota group 704 qc := servicestate.QuotaControlAction{ 705 Action: "create", 706 QuotaName: "foo", 707 MemoryLimit: quantity.SizeGiB, 708 AddSnaps: []string{"test-snap"}, 709 } 710 711 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 712 c.Assert(err, IsNil) 713 714 // ensure mem-limit is 1 GB 715 checkQuotaState(c, st, map[string]quotaGroupState{ 716 "foo": { 717 MemoryLimit: quantity.SizeGiB, 718 Snaps: []string{"test-snap"}, 719 }, 720 }) 721 722 // modify to 2 GB 723 qc2 := servicestate.QuotaControlAction{ 724 Action: "update", 725 QuotaName: "foo", 726 MemoryLimit: 2 * quantity.SizeGiB, 727 } 728 err = servicestate.QuotaUpdate(st, nil, qc2, allGrps(c, st), nil, nil) 729 c.Assert(err, IsNil) 730 731 // and check that it got updated in the state 732 checkQuotaState(c, st, map[string]quotaGroupState{ 733 "foo": { 734 MemoryLimit: 2 * quantity.SizeGiB, 735 Snaps: []string{"test-snap"}, 736 }, 737 }) 738 739 // trying to decrease the memory limit is not yet supported 740 qc3 := servicestate.QuotaControlAction{ 741 Action: "update", 742 QuotaName: "foo", 743 MemoryLimit: quantity.SizeGiB, 744 } 745 err = servicestate.QuotaUpdate(st, nil, qc3, allGrps(c, st), nil, nil) 746 c.Assert(err, ErrorMatches, "cannot decrease memory limit of existing quota-group, remove and re-create it to decrease the limit") 747 } 748 749 func (s *quotaHandlersSuite) TestQuotaUpdateAddSnap(c *C) { 750 r := s.mockSystemctlCalls(c, join( 751 // CreateQuota for foo 752 systemctlCallsForCreateQuota("foo", "test-snap"), 753 754 // UpdateQuota with just test-snap2 restarted since the group already 755 // exists 756 []expectedSystemctl{{expArgs: []string{"daemon-reload"}}}, 757 systemctlCallsForServiceRestart("test-snap2"), 758 )) 759 defer r() 760 761 st := s.state 762 st.Lock() 763 defer st.Unlock() 764 765 // setup test-snap 766 snapstate.Set(s.state, "test-snap", s.testSnapState) 767 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 768 // and test-snap2 769 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 770 snapst2 := &snapstate.SnapState{ 771 Sequence: []*snap.SideInfo{si2}, 772 Current: si2.Revision, 773 Active: true, 774 SnapType: "app", 775 } 776 snapstate.Set(s.state, "test-snap2", snapst2) 777 snaptest.MockSnapCurrent(c, testYaml2, si2) 778 779 // create a quota group 780 qc := servicestate.QuotaControlAction{ 781 Action: "create", 782 QuotaName: "foo", 783 MemoryLimit: quantity.SizeGiB, 784 AddSnaps: []string{"test-snap"}, 785 } 786 787 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 788 c.Assert(err, IsNil) 789 790 checkQuotaState(c, st, map[string]quotaGroupState{ 791 "foo": { 792 MemoryLimit: quantity.SizeGiB, 793 Snaps: []string{"test-snap"}, 794 }, 795 }) 796 797 // add a snap 798 qc2 := servicestate.QuotaControlAction{ 799 Action: "update", 800 QuotaName: "foo", 801 AddSnaps: []string{"test-snap2"}, 802 } 803 err = servicestate.QuotaUpdate(st, nil, qc2, allGrps(c, st), nil, nil) 804 c.Assert(err, IsNil) 805 806 // and check that it got updated in the state 807 checkQuotaState(c, st, map[string]quotaGroupState{ 808 "foo": { 809 MemoryLimit: quantity.SizeGiB, 810 Snaps: []string{"test-snap", "test-snap2"}, 811 }, 812 }) 813 } 814 815 func (s *quotaHandlersSuite) TestQuotaUpdateAddSnapAlreadyInOtherGroup(c *C) { 816 r := s.mockSystemctlCalls(c, join( 817 // CreateQuota for foo 818 systemctlCallsForCreateQuota("foo", "test-snap"), 819 820 // CreateQuota for foo2 821 systemctlCallsForCreateQuota("foo2", "test-snap2"), 822 823 // UpdateQuota for foo which fails - no systemctl calls 824 )) 825 defer r() 826 827 st := s.state 828 st.Lock() 829 defer st.Unlock() 830 831 // setup test-snap 832 snapstate.Set(s.state, "test-snap", s.testSnapState) 833 snaptest.MockSnapCurrent(c, testYaml, s.testSnapSideInfo) 834 // and test-snap2 835 si2 := &snap.SideInfo{RealName: "test-snap2", Revision: snap.R(42)} 836 snapst2 := &snapstate.SnapState{ 837 Sequence: []*snap.SideInfo{si2}, 838 Current: si2.Revision, 839 Active: true, 840 SnapType: "app", 841 } 842 snapstate.Set(s.state, "test-snap2", snapst2) 843 snaptest.MockSnapCurrent(c, testYaml2, si2) 844 845 // create a quota group 846 qc := servicestate.QuotaControlAction{ 847 Action: "create", 848 QuotaName: "foo", 849 MemoryLimit: quantity.SizeGiB, 850 AddSnaps: []string{"test-snap"}, 851 } 852 853 err := servicestate.QuotaCreate(st, nil, qc, allGrps(c, st), nil, nil) 854 c.Assert(err, IsNil) 855 856 checkQuotaState(c, st, map[string]quotaGroupState{ 857 "foo": { 858 MemoryLimit: quantity.SizeGiB, 859 Snaps: []string{"test-snap"}, 860 }, 861 }) 862 863 // create another quota group with the second snap 864 qc2 := servicestate.QuotaControlAction{ 865 Action: "create", 866 QuotaName: "foo2", 867 MemoryLimit: quantity.SizeGiB, 868 AddSnaps: []string{"test-snap2"}, 869 } 870 871 err = servicestate.QuotaCreate(st, nil, qc2, allGrps(c, st), nil, nil) 872 c.Assert(err, IsNil) 873 874 // verify state 875 checkQuotaState(c, st, map[string]quotaGroupState{ 876 "foo": { 877 MemoryLimit: quantity.SizeGiB, 878 Snaps: []string{"test-snap"}, 879 }, 880 "foo2": { 881 MemoryLimit: quantity.SizeGiB, 882 Snaps: []string{"test-snap2"}, 883 }, 884 }) 885 886 // try to add test-snap2 to foo 887 qc3 := servicestate.QuotaControlAction{ 888 Action: "update", 889 QuotaName: "foo", 890 AddSnaps: []string{"test-snap2"}, 891 } 892 893 err = servicestate.QuotaUpdate(st, nil, qc3, allGrps(c, st), nil, nil) 894 c.Assert(err, ErrorMatches, `cannot add snap "test-snap2" to group "foo": snap already in quota group "foo2"`) 895 896 // nothing changed in the state 897 checkQuotaState(c, st, map[string]quotaGroupState{ 898 "foo": { 899 MemoryLimit: quantity.SizeGiB, 900 Snaps: []string{"test-snap"}, 901 }, 902 "foo2": { 903 MemoryLimit: quantity.SizeGiB, 904 Snaps: []string{"test-snap2"}, 905 }, 906 }) 907 }