gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/snap/quota/quota_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 quota_test 21 22 import ( 23 "fmt" 24 "math" 25 "testing" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/gadget/quantity" 30 "github.com/snapcore/snapd/snap/quota" 31 "github.com/snapcore/snapd/systemd" 32 ) 33 34 // Hook up check.v1 into the "go test" runner 35 func Test(t *testing.T) { TestingT(t) } 36 37 type quotaTestSuite struct{} 38 39 var _ = Suite("aTestSuite{}) 40 41 func (ts *quotaTestSuite) TestNewGroup(c *C) { 42 43 tt := []struct { 44 name string 45 sliceFileName string 46 limit quantity.Size 47 err string 48 comment string 49 }{ 50 { 51 name: "group1", 52 limit: quantity.SizeMiB, 53 comment: "basic happy", 54 }, 55 { 56 name: "biglimit", 57 limit: quantity.Size(math.MaxUint64), 58 comment: "huge limit happy", 59 }, 60 { 61 name: "zero", 62 limit: 0, 63 err: `group memory limit must be non-zero`, 64 comment: "group with zero memory limit", 65 }, 66 { 67 name: "group1-unsupported chars", 68 limit: quantity.SizeMiB, 69 err: `invalid quota group name: contains invalid characters.*`, 70 comment: "unsupported characters in group name", 71 }, 72 { 73 name: "group%%%", 74 limit: quantity.SizeMiB, 75 err: `invalid quota group name: contains invalid characters.*`, 76 comment: "more invalid characters in name", 77 }, 78 { 79 name: "CAPITALIZED", 80 limit: quantity.SizeMiB, 81 err: `invalid quota group name: contains invalid characters.*`, 82 comment: "capitalized letters", 83 }, 84 { 85 name: "g1", 86 limit: quantity.SizeMiB, 87 comment: "small group name", 88 }, 89 { 90 name: "name-with-dashes", 91 sliceFileName: `name\x2dwith\x2ddashes`, 92 limit: quantity.SizeMiB, 93 comment: "name with dashes", 94 }, 95 { 96 name: "", 97 limit: quantity.SizeMiB, 98 err: `invalid quota group name: must not be empty`, 99 comment: "empty group name", 100 }, 101 { 102 name: "g", 103 limit: quantity.SizeMiB, 104 err: `invalid quota group name: must be between 2 and 40 characters long.*`, 105 comment: "too small group name", 106 }, 107 { 108 name: "root", 109 limit: quantity.SizeMiB, 110 err: `group name "root" reserved`, 111 comment: "reserved root name", 112 }, 113 { 114 name: "snapd", 115 limit: quantity.SizeMiB, 116 err: `group name "snapd" reserved`, 117 comment: "reserved snapd name", 118 }, 119 { 120 name: "system", 121 limit: quantity.SizeMiB, 122 err: `group name "system" reserved`, 123 comment: "reserved system name", 124 }, 125 { 126 name: "user", 127 limit: quantity.SizeMiB, 128 err: `group name "user" reserved`, 129 comment: "reserved user name", 130 }, 131 } 132 133 for _, t := range tt { 134 comment := Commentf(t.comment) 135 grp, err := quota.NewGroup(t.name, t.limit) 136 if t.err != "" { 137 c.Assert(err, ErrorMatches, t.err, comment) 138 continue 139 } 140 c.Assert(err, IsNil, comment) 141 142 if t.sliceFileName != "" { 143 c.Assert(grp.SliceFileName(), Equals, "snap."+t.sliceFileName+".slice", comment) 144 } else { 145 c.Assert(grp.SliceFileName(), Equals, "snap."+t.name+".slice", comment) 146 } 147 } 148 } 149 150 func (ts *quotaTestSuite) TestSimpleSubGroupVerification(c *C) { 151 tt := []struct { 152 rootname string 153 rootlimit quantity.Size 154 subname string 155 sliceFileName string 156 sublimit quantity.Size 157 err string 158 comment string 159 }{ 160 { 161 rootlimit: quantity.SizeMiB, 162 subname: "sub", 163 sublimit: quantity.SizeMiB, 164 comment: "basic sub group with same quota as parent happy", 165 }, 166 { 167 rootlimit: quantity.SizeMiB, 168 subname: "sub", 169 sublimit: quantity.SizeMiB / 2, 170 comment: "basic sub group with smaller quota than parent happy", 171 }, 172 { 173 rootlimit: quantity.SizeMiB, 174 subname: "sub-with-dashes", 175 sliceFileName: `myroot-sub\x2dwith\x2ddashes`, 176 sublimit: quantity.SizeMiB / 2, 177 comment: "basic sub group with dashes in the name", 178 }, 179 { 180 rootname: "my-root", 181 rootlimit: quantity.SizeMiB, 182 subname: "sub-with-dashes", 183 sliceFileName: `my\x2droot-sub\x2dwith\x2ddashes`, 184 sublimit: quantity.SizeMiB / 2, 185 comment: "parent and sub group have dashes in name", 186 }, 187 { 188 rootlimit: quantity.SizeMiB, 189 subname: "sub", 190 sublimit: quantity.SizeMiB * 2, 191 err: "sub-group memory limit of 2 MiB is too large to fit inside remaining quota space 1 MiB for parent group myroot", 192 comment: "sub group with larger quota than parent unhappy", 193 }, 194 { 195 rootlimit: quantity.SizeMiB, 196 subname: "sub invalid chars", 197 sublimit: quantity.SizeMiB, 198 err: `invalid quota group name: contains invalid characters.*`, 199 comment: "sub group with invalid name", 200 }, 201 { 202 rootlimit: quantity.SizeMiB, 203 subname: "myroot", 204 sublimit: quantity.SizeMiB, 205 err: `cannot use same name "myroot" for sub group as parent group`, 206 comment: "sub group with same name as parent group", 207 }, 208 { 209 rootlimit: quantity.SizeMiB, 210 subname: "snapd", 211 sublimit: quantity.SizeMiB, 212 err: `group name "snapd" reserved`, 213 comment: "sub group with reserved name", 214 }, 215 { 216 rootlimit: quantity.SizeMiB, 217 subname: "zero", 218 sublimit: 0, 219 err: `group memory limit must be non-zero`, 220 comment: "sub group with zero memory limit", 221 }, 222 } 223 224 for _, t := range tt { 225 comment := Commentf(t.comment) 226 // make a root group 227 rootname := t.rootname 228 if rootname == "" { 229 rootname = "myroot" 230 } 231 rootGrp, err := quota.NewGroup(rootname, t.rootlimit) 232 c.Assert(err, IsNil, comment) 233 234 // make a sub-group under the root group 235 subGrp, err := rootGrp.NewSubGroup(t.subname, t.sublimit) 236 if t.err != "" { 237 c.Assert(err, ErrorMatches, t.err, comment) 238 continue 239 } 240 c.Assert(err, IsNil, comment) 241 242 if t.sliceFileName != "" { 243 c.Assert(subGrp.SliceFileName(), Equals, "snap."+t.sliceFileName+".slice") 244 } else { 245 c.Assert(subGrp.SliceFileName(), Equals, "snap.myroot-"+t.subname+".slice") 246 } 247 } 248 } 249 250 func (ts *quotaTestSuite) TestComplexSubGroups(c *C) { 251 rootGrp, err := quota.NewGroup("myroot", quantity.SizeMiB) 252 c.Assert(err, IsNil) 253 254 // try adding 2 sub-groups with total quota split exactly equally 255 sub1, err := rootGrp.NewSubGroup("sub1", quantity.SizeMiB/2) 256 c.Assert(err, IsNil) 257 c.Assert(sub1.SliceFileName(), Equals, "snap.myroot-sub1.slice") 258 259 sub2, err := rootGrp.NewSubGroup("sub2", quantity.SizeMiB/2) 260 c.Assert(err, IsNil) 261 c.Assert(sub2.SliceFileName(), Equals, "snap.myroot-sub2.slice") 262 263 // adding another sub-group to this group fails 264 _, err = rootGrp.NewSubGroup("sub3", 1) 265 c.Assert(err, ErrorMatches, "sub-group memory limit of 1 B is too large to fit inside remaining quota space 0 B for parent group myroot") 266 267 // we can however add a sub-group to one of the sub-groups with the exact 268 // size of the parent sub-group 269 subsub1, err := sub1.NewSubGroup("subsub1", quantity.SizeMiB/2) 270 c.Assert(err, IsNil) 271 c.Assert(subsub1.SliceFileName(), Equals, "snap.myroot-sub1-subsub1.slice") 272 273 // and we can even add a smaller sub-sub-sub-group to the sub-group 274 subsubsub1, err := subsub1.NewSubGroup("subsubsub1", quantity.SizeMiB/4) 275 c.Assert(err, IsNil) 276 c.Assert(subsubsub1.SliceFileName(), Equals, "snap.myroot-sub1-subsub1-subsubsub1.slice") 277 } 278 279 func (ts *quotaTestSuite) TestResolveCrossReferences(c *C) { 280 tt := []struct { 281 grps map[string]*quota.Group 282 err string 283 comment string 284 }{ 285 { 286 grps: map[string]*quota.Group{ 287 "foogroup": { 288 Name: "foogroup", 289 MemoryLimit: quantity.SizeMiB, 290 }, 291 }, 292 comment: "single group", 293 }, 294 { 295 grps: map[string]*quota.Group{ 296 "foogroup": { 297 Name: "foogroup", 298 MemoryLimit: quantity.SizeMiB, 299 ParentGroup: "foogroup", 300 }, 301 }, 302 err: `group "foogroup" is invalid: group has circular parent reference to itself`, 303 comment: "parent group self-reference group", 304 }, 305 { 306 grps: map[string]*quota.Group{ 307 "foogroup": { 308 Name: "foogroup", 309 MemoryLimit: quantity.SizeMiB, 310 SubGroups: []string{"foogroup"}, 311 }, 312 }, 313 err: `group "foogroup" is invalid: group has circular sub-group reference to itself`, 314 comment: "parent group self-reference group", 315 }, 316 { 317 grps: map[string]*quota.Group{ 318 "foogroup": { 319 Name: "foogroup", 320 MemoryLimit: 0, 321 }, 322 }, 323 err: `group "foogroup" is invalid: group memory limit must be non-zero`, 324 comment: "invalid group", 325 }, 326 { 327 grps: map[string]*quota.Group{ 328 "foogroup": { 329 Name: "foogroup", 330 MemoryLimit: quantity.SizeMiB, 331 }, 332 "foogroup2": { 333 Name: "foogroup2", 334 MemoryLimit: quantity.SizeMiB, 335 }, 336 }, 337 comment: "multiple root groups", 338 }, 339 { 340 grps: map[string]*quota.Group{ 341 "foogroup": { 342 Name: "foogroup", 343 MemoryLimit: quantity.SizeMiB, 344 }, 345 "subgroup": { 346 Name: "subgroup", 347 MemoryLimit: quantity.SizeMiB, 348 ParentGroup: "foogroup", 349 }, 350 }, 351 err: `group "foogroup" does not reference necessary child group "subgroup"`, 352 comment: "incomplete references in parent group to child group", 353 }, 354 { 355 grps: map[string]*quota.Group{ 356 "foogroup": { 357 Name: "foogroup", 358 MemoryLimit: quantity.SizeMiB, 359 SubGroups: []string{"subgroup"}, 360 }, 361 "subgroup": { 362 Name: "subgroup", 363 MemoryLimit: quantity.SizeMiB, 364 }, 365 }, 366 err: `group "subgroup" does not reference necessary parent group "foogroup"`, 367 comment: "incomplete references in sub-group to parent group", 368 }, 369 { 370 grps: map[string]*quota.Group{ 371 "foogroup": { 372 Name: "foogroup", 373 MemoryLimit: quantity.SizeMiB, 374 SubGroups: []string{"subgroup"}, 375 }, 376 "subgroup": { 377 Name: "subgroup", 378 MemoryLimit: quantity.SizeMiB, 379 ParentGroup: "foogroup", 380 }, 381 }, 382 comment: "valid fully specified sub-group", 383 }, 384 { 385 grps: map[string]*quota.Group{ 386 "foogroup": { 387 Name: "foogroup", 388 MemoryLimit: quantity.SizeMiB, 389 SubGroups: []string{"subgroup1", "subgroup2"}, 390 }, 391 "subgroup1": { 392 Name: "subgroup1", 393 MemoryLimit: quantity.SizeMiB / 2, 394 ParentGroup: "foogroup", 395 }, 396 "subgroup2": { 397 Name: "subgroup2", 398 MemoryLimit: quantity.SizeMiB / 2, 399 ParentGroup: "foogroup", 400 }, 401 }, 402 comment: "multiple valid fully specified sub-groups", 403 }, 404 { 405 grps: map[string]*quota.Group{ 406 "foogroup": { 407 Name: "foogroup", 408 MemoryLimit: quantity.SizeMiB, 409 SubGroups: []string{"subgroup1"}, 410 }, 411 "subgroup1": { 412 Name: "subgroup1", 413 MemoryLimit: quantity.SizeMiB, 414 ParentGroup: "foogroup", 415 SubGroups: []string{"subgroup2"}, 416 }, 417 "subgroup2": { 418 Name: "subgroup2", 419 MemoryLimit: quantity.SizeMiB, 420 ParentGroup: "subgroup1", 421 }, 422 }, 423 comment: "deeply nested valid fully specified sub-groups", 424 }, 425 { 426 grps: map[string]*quota.Group{ 427 "foogroup": { 428 Name: "foogroup", 429 MemoryLimit: quantity.SizeMiB, 430 SubGroups: []string{"subgroup1"}, 431 }, 432 "subgroup1": { 433 Name: "subgroup1", 434 MemoryLimit: quantity.SizeMiB, 435 ParentGroup: "foogroup", 436 SubGroups: []string{"subgroup2"}, 437 }, 438 "subgroup2": { 439 Name: "subgroup2", 440 MemoryLimit: quantity.SizeMiB, 441 // missing parent reference 442 }, 443 }, 444 err: `group "subgroup2" does not reference necessary parent group "subgroup1"`, 445 comment: "deeply nested invalid fully specified sub-groups", 446 }, 447 { 448 grps: map[string]*quota.Group{ 449 "not-foogroup": { 450 Name: "foogroup", 451 MemoryLimit: quantity.SizeMiB, 452 }, 453 }, 454 err: `group has name "foogroup", but is referenced as "not-foogroup"`, 455 comment: "group misname", 456 }, 457 { 458 grps: map[string]*quota.Group{ 459 "foogroup": { 460 Name: "foogroup", 461 MemoryLimit: quantity.SizeMiB, 462 SubGroups: []string{"other-missing"}, 463 }, 464 }, 465 err: `missing group "other-missing" referenced as the sub-group of group "foogroup"`, 466 comment: "missing sub-group name", 467 }, 468 { 469 grps: map[string]*quota.Group{ 470 "foogroup": { 471 Name: "foogroup", 472 MemoryLimit: quantity.SizeMiB, 473 ParentGroup: "other-missing", 474 }, 475 }, 476 err: `missing group "other-missing" referenced as the parent of group "foogroup"`, 477 comment: "missing sub-group name", 478 }, 479 } 480 481 for _, t := range tt { 482 comment := Commentf(t.comment) 483 err := quota.ResolveCrossReferences(t.grps) 484 if t.err != "" { 485 c.Assert(err, ErrorMatches, t.err, comment) 486 } else { 487 c.Assert(err, IsNil, comment) 488 } 489 } 490 } 491 492 func (ts *quotaTestSuite) TestAddAllNecessaryGroupsAvoidsInfiniteRecursion(c *C) { 493 grp, err := quota.NewGroup("infinite-group", quantity.SizeGiB) 494 c.Assert(err, IsNil) 495 496 grp2, err := grp.NewSubGroup("infinite-group2", quantity.SizeGiB) 497 c.Assert(err, IsNil) 498 499 // create a cycle artificially to the same group 500 grp2.SetInternalSubGroups([]*quota.Group{grp2}) 501 502 // now we fail to add this to a quota set 503 qs := "a.QuotaGroupSet{} 504 err = qs.AddAllNecessaryGroups(grp) 505 c.Assert(err, ErrorMatches, "internal error: circular reference found") 506 507 // create a more difficult to detect cycle going from the child to the 508 // parent 509 grp2.SetInternalSubGroups([]*quota.Group{grp}) 510 err = qs.AddAllNecessaryGroups(grp) 511 c.Assert(err, ErrorMatches, "internal error: circular reference found") 512 513 // make a real sub-group and try one more level of indirection going back 514 // to the parent 515 grp2.SetInternalSubGroups(nil) 516 grp3, err := grp2.NewSubGroup("infinite-group3", quantity.SizeGiB) 517 c.Assert(err, IsNil) 518 grp3.SetInternalSubGroups([]*quota.Group{grp}) 519 520 err = qs.AddAllNecessaryGroups(grp) 521 c.Assert(err, ErrorMatches, "internal error: circular reference found") 522 } 523 524 func (ts *quotaTestSuite) TestAddAllNecessaryGroups(c *C) { 525 qs := "a.QuotaGroupSet{} 526 527 // it should initially be empty 528 c.Assert(qs.AllQuotaGroups(), HasLen, 0) 529 530 grp1, err := quota.NewGroup("myroot", quantity.SizeGiB) 531 c.Assert(err, IsNil) 532 533 // add the group and make sure it is in the set 534 err = qs.AddAllNecessaryGroups(grp1) 535 c.Assert(err, IsNil) 536 c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1}) 537 538 // adding multiple times doesn't change the set 539 err = qs.AddAllNecessaryGroups(grp1) 540 c.Assert(err, IsNil) 541 err = qs.AddAllNecessaryGroups(grp1) 542 c.Assert(err, IsNil) 543 c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1}) 544 545 // add a new group and make sure it is in the set now 546 grp2, err := quota.NewGroup("myroot2", quantity.SizeGiB) 547 c.Assert(err, IsNil) 548 err = qs.AddAllNecessaryGroups(grp2) 549 c.Assert(err, IsNil) 550 c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2}) 551 552 // start again 553 qs = "a.QuotaGroupSet{} 554 555 // make a sub-group and add the root group - it will automatically add 556 // the sub-group without us needing to explicitly add the sub-group 557 subgrp1, err := grp1.NewSubGroup("mysub1", quantity.SizeGiB) 558 c.Assert(err, IsNil) 559 // add grp2 as well 560 err = qs.AddAllNecessaryGroups(grp2) 561 c.Assert(err, IsNil) 562 563 err = qs.AddAllNecessaryGroups(grp1) 564 c.Assert(err, IsNil) 565 c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2, subgrp1}) 566 567 // we can explicitly add the sub-group and still have the same set too 568 err = qs.AddAllNecessaryGroups(subgrp1) 569 c.Assert(err, IsNil) 570 c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2, subgrp1}) 571 572 // create a new set of group and sub-groups to add the deepest child group 573 // and add that, and notice that the root groups are also added 574 grp3, err := quota.NewGroup("myroot3", quantity.SizeGiB) 575 c.Assert(err, IsNil) 576 577 subgrp3, err := grp3.NewSubGroup("mysub3", quantity.SizeGiB) 578 c.Assert(err, IsNil) 579 580 subsubgrp3, err := subgrp3.NewSubGroup("mysubsub3", quantity.SizeGiB) 581 c.Assert(err, IsNil) 582 583 err = qs.AddAllNecessaryGroups(subsubgrp3) 584 c.Assert(err, IsNil) 585 c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2, grp3, subgrp1, subgrp3, subsubgrp3}) 586 587 // finally create a tree with multiple branches and ensure that adding just 588 // a single deepest child will add all the other deepest children from other 589 // branches 590 grp4, err := quota.NewGroup("myroot4", quantity.SizeGiB) 591 c.Assert(err, IsNil) 592 593 subgrp4, err := grp4.NewSubGroup("mysub4", quantity.SizeGiB/2) 594 c.Assert(err, IsNil) 595 596 subgrp5, err := grp4.NewSubGroup("mysub5", quantity.SizeGiB/2) 597 c.Assert(err, IsNil) 598 599 // adding just subgrp5 to a quota set will automatically add the other sub 600 // group, subgrp4 601 qs2 := "a.QuotaGroupSet{} 602 err = qs2.AddAllNecessaryGroups(subgrp4) 603 c.Assert(err, IsNil) 604 c.Assert(qs2.AllQuotaGroups(), DeepEquals, []*quota.Group{grp4, subgrp4, subgrp5}) 605 } 606 607 func (ts *quotaTestSuite) TestResolveCrossReferencesLimitCheckSkipsSelf(c *C) { 608 grp1, err := quota.NewGroup("myroot", quantity.SizeGiB) 609 c.Assert(err, IsNil) 610 611 subgrp1, err := grp1.NewSubGroup("mysub1", quantity.SizeGiB) 612 c.Assert(err, IsNil) 613 614 subgrp2, err := subgrp1.NewSubGroup("mysub2", quantity.SizeGiB) 615 c.Assert(err, IsNil) 616 617 all := map[string]*quota.Group{ 618 "myroot": grp1, 619 "mysub1": subgrp1, 620 "mysub2": subgrp2, 621 } 622 err = quota.ResolveCrossReferences(all) 623 c.Assert(err, IsNil) 624 } 625 626 func (ts *quotaTestSuite) TestResolveCrossReferencesCircular(c *C) { 627 grp1, err := quota.NewGroup("myroot", quantity.SizeGiB) 628 c.Assert(err, IsNil) 629 630 subgrp1, err := grp1.NewSubGroup("mysub1", quantity.SizeGiB) 631 c.Assert(err, IsNil) 632 633 subgrp2, err := subgrp1.NewSubGroup("mysub2", quantity.SizeGiB) 634 c.Assert(err, IsNil) 635 636 all := map[string]*quota.Group{ 637 "myroot": grp1, 638 "mysub1": subgrp1, 639 "mysub2": subgrp2, 640 } 641 // try to set up circular ref 642 subgrp2.SubGroups = append(subgrp2.SubGroups, "mysub1") 643 err = quota.ResolveCrossReferences(all) 644 c.Assert(err, ErrorMatches, `.*reference necessary parent.*`) 645 } 646 647 type systemctlInactiveServiceError struct{} 648 649 func (s systemctlInactiveServiceError) Msg() []byte { return []byte("inactive") } 650 func (s systemctlInactiveServiceError) ExitCode() int { return 0 } 651 func (s systemctlInactiveServiceError) Error() string { return "inactive" } 652 653 func (ts *quotaTestSuite) TestCurrentMemoryUsage(c *C) { 654 systemctlCalls := 0 655 r := systemd.MockSystemctl(func(args ...string) ([]byte, error) { 656 systemctlCalls++ 657 switch systemctlCalls { 658 659 // inactive case, memory is 0 660 case 1: 661 // first time pretend the service is inactive 662 c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"}) 663 return []byte("inactive"), systemctlInactiveServiceError{} 664 665 // active but no tasks, but we still return the memory usage because it 666 // can be valid on some systems to have non-zero memory usage for a 667 // group without any tasks in it, such as on hirsute, arch, fedora 33+, 668 // and debian sid 669 case 2: 670 // now pretend it is active 671 c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"}) 672 return []byte("active"), nil 673 case 3: 674 // and the memory count can be non-zero like 675 c.Assert(args, DeepEquals, []string{"show", "--property", "MemoryCurrent", "snap.group.slice"}) 676 return []byte("MemoryCurrent=4096"), nil 677 678 case 4: 679 // now pretend it is active 680 c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"}) 681 return []byte("active"), nil 682 case 5: 683 // and the memory count can be zero too 684 c.Assert(args, DeepEquals, []string{"show", "--property", "MemoryCurrent", "snap.group.slice"}) 685 return []byte("MemoryCurrent=0"), nil 686 687 // bug case where 16 exb is erroneous - this is left in for posterity, 688 // but we don't handle this differently, previously we had a workaround 689 // for this sort of case, but it ended up not being tenable but still 690 // test that a huge value just gets reported as-is 691 case 6: 692 // the cgroup is active, has no tasks and has 16 exb usage 693 c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"}) 694 return []byte("active"), nil 695 case 7: 696 // since it is active, we will query the current memory usage, 697 // this time return an obviously wrong number 698 c.Assert(args, DeepEquals, []string{"show", "--property", "MemoryCurrent", "snap.group.slice"}) 699 return []byte("MemoryCurrent=18446744073709551615"), nil 700 701 default: 702 c.Errorf("too many systemctl calls (%d) (current call is %+v)", systemctlCalls, args) 703 return []byte("broken test"), fmt.Errorf("broken test") 704 } 705 }) 706 defer r() 707 708 grp1, err := quota.NewGroup("group", quantity.SizeGiB) 709 c.Assert(err, IsNil) 710 711 // group initially is inactive, so it has no current memory usage 712 currentMem, err := grp1.CurrentMemoryUsage() 713 c.Assert(err, IsNil) 714 c.Assert(currentMem, Equals, quantity.Size(0)) 715 716 // now with the slice mocked as active it has real usage 717 currentMem, err = grp1.CurrentMemoryUsage() 718 c.Assert(err, IsNil) 719 c.Assert(currentMem, Equals, 4*quantity.SizeKiB) 720 721 // but it can also have 0 usage 722 currentMem, err = grp1.CurrentMemoryUsage() 723 c.Assert(err, IsNil) 724 c.Assert(currentMem, Equals, quantity.Size(0)) 725 726 // and it can also be an incredibly huge value too 727 currentMem, err = grp1.CurrentMemoryUsage() 728 c.Assert(err, IsNil) 729 const sixteenExb = quantity.Size(1<<64 - 1) 730 c.Assert(currentMem, Equals, sixteenExb) 731 }