gitee.com/mysnapcore/mysnapd@v0.1.0/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  	"time"
    27  
    28  	. "gopkg.in/check.v1"
    29  
    30  	"gitee.com/mysnapcore/mysnapd/gadget/quantity"
    31  	"gitee.com/mysnapcore/mysnapd/snap/quota"
    32  	"gitee.com/mysnapcore/mysnapd/systemd"
    33  )
    34  
    35  // Hook up check.v1 into the "go test" runner
    36  func Test(t *testing.T) { TestingT(t) }
    37  
    38  type quotaTestSuite struct{}
    39  
    40  var _ = Suite(&quotaTestSuite{})
    41  
    42  func (ts *quotaTestSuite) TestNewGroup(c *C) {
    43  
    44  	tt := []struct {
    45  		name          string
    46  		sliceFileName string
    47  		limits        quota.Resources
    48  		err           string
    49  		comment       string
    50  	}{
    51  		{
    52  			name:    "group1",
    53  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
    54  			comment: "basic happy",
    55  		},
    56  		{
    57  			name:    "biglimit",
    58  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.Size(math.MaxUint64)).Build(),
    59  			comment: "huge limit happy",
    60  		},
    61  		{
    62  			name:    "zero",
    63  			limits:  quota.NewResourcesBuilder().Build(),
    64  			err:     `quota group must have at least one resource limit set`,
    65  			comment: "group with no limits",
    66  		},
    67  		{
    68  			name:    "group1-unsupported chars",
    69  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
    70  			err:     `invalid quota group name: contains invalid characters.*`,
    71  			comment: "unsupported characters in group name",
    72  		},
    73  		{
    74  			name:    "group%%%",
    75  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
    76  			err:     `invalid quota group name: contains invalid characters.*`,
    77  			comment: "more invalid characters in name",
    78  		},
    79  		{
    80  			name:    "CAPITALIZED",
    81  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
    82  			err:     `invalid quota group name: contains invalid characters.*`,
    83  			comment: "capitalized letters",
    84  		},
    85  		{
    86  			name:    "g1",
    87  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
    88  			comment: "small group name",
    89  		},
    90  		{
    91  			name:          "name-with-dashes",
    92  			sliceFileName: `name\x2dwith\x2ddashes`,
    93  			limits:        quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
    94  			comment:       "name with dashes",
    95  		},
    96  		{
    97  			name:    "",
    98  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
    99  			err:     `invalid quota group name: must not be empty`,
   100  			comment: "empty group name",
   101  		},
   102  		{
   103  			name:    "g",
   104  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   105  			err:     `invalid quota group name: must be between 2 and 40 characters long.*`,
   106  			comment: "too small group name",
   107  		},
   108  		{
   109  			name:    "root",
   110  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   111  			err:     `group name "root" reserved`,
   112  			comment: "reserved root name",
   113  		},
   114  		{
   115  			name:    "snapd",
   116  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   117  			err:     `group name "snapd" reserved`,
   118  			comment: "reserved snapd name",
   119  		},
   120  		{
   121  			name:    "system",
   122  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   123  			err:     `group name "system" reserved`,
   124  			comment: "reserved system name",
   125  		},
   126  		{
   127  			name:    "user",
   128  			limits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   129  			err:     `group name "user" reserved`,
   130  			comment: "reserved user name",
   131  		},
   132  	}
   133  
   134  	for _, t := range tt {
   135  		comment := Commentf(t.comment)
   136  		grp, err := quota.NewGroup(t.name, t.limits)
   137  		if t.err != "" {
   138  			c.Assert(err, ErrorMatches, t.err, comment)
   139  			continue
   140  		}
   141  		c.Assert(err, IsNil, comment)
   142  
   143  		if t.sliceFileName != "" {
   144  			c.Assert(grp.SliceFileName(), Equals, "snap."+t.sliceFileName+".slice", comment)
   145  		} else {
   146  			c.Assert(grp.SliceFileName(), Equals, "snap."+t.name+".slice", comment)
   147  		}
   148  	}
   149  }
   150  
   151  func (ts *quotaTestSuite) TestSimpleSubGroupVerification(c *C) {
   152  	tt := []struct {
   153  		rootname      string
   154  		rootlimits    quota.Resources
   155  		subname       string
   156  		sliceFileName string
   157  		sublimits     quota.Resources
   158  		err           string
   159  		comment       string
   160  	}{
   161  		{
   162  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   163  			subname:    "sub",
   164  			sublimits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   165  			comment:    "basic sub group with same quota as parent happy",
   166  		},
   167  		{
   168  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(2 * quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   169  			subname:    "sub",
   170  			sublimits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(50).WithCPUSet([]int{0}).WithThreadLimit(16).Build(),
   171  			comment:    "basic sub group with smaller quota than parent happy",
   172  		},
   173  		{
   174  			rootlimits:    quota.NewResourcesBuilder().WithMemoryLimit(2 * quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   175  			subname:       "sub-with-dashes",
   176  			sliceFileName: `myroot-sub\x2dwith\x2ddashes`,
   177  			sublimits:     quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(50).WithCPUSet([]int{0}).WithThreadLimit(16).Build(),
   178  			comment:       "basic sub group with dashes in the name",
   179  		},
   180  		{
   181  			rootname:      "my-root",
   182  			rootlimits:    quota.NewResourcesBuilder().WithMemoryLimit(2 * quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   183  			subname:       "sub-with-dashes",
   184  			sliceFileName: `my\x2droot-sub\x2dwith\x2ddashes`,
   185  			sublimits:     quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(50).WithCPUSet([]int{0}).WithThreadLimit(16).Build(),
   186  			comment:       "parent and sub group have dashes in name",
   187  		},
   188  		{
   189  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   190  			subname:    "sub",
   191  			sublimits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB * 2).Build(),
   192  			err:        "sub-group memory limit of 2 MiB is too large to fit inside group \"myroot\" remaining quota space 1 MiB",
   193  			comment:    "sub group with larger memory quota than parent unhappy",
   194  		},
   195  		{
   196  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   197  			subname:    "sub",
   198  			sublimits:  quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(100).Build(),
   199  			err:        "sub-group cpu limit of 200% is too large to fit inside group \"myroot\" remaining quota space 100%",
   200  			comment:    "sub group with larger cpu count quota than parent unhappy",
   201  		},
   202  		{
   203  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   204  			subname:    "sub",
   205  			sublimits:  quota.NewResourcesBuilder().WithCPUSet([]int{1}).Build(),
   206  			err:        "sub-group cpu-set \\[1\\] is not a subset of group \"myroot\" cpu-set \\[0\\]",
   207  			comment:    "sub group with different cpu allowance quota than parent unhappy",
   208  		},
   209  		{
   210  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   211  			subname:    "sub",
   212  			sublimits:  quota.NewResourcesBuilder().WithThreadLimit(64).Build(),
   213  			err:        "sub-group thread limit of 64 is too large to fit inside group \"myroot\" remaining quota space 32",
   214  			comment:    "sub group with larger task allowance quota than parent unhappy",
   215  		},
   216  		{
   217  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   218  			subname:    "sub invalid chars",
   219  			sublimits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   220  			err:        `invalid quota group name: contains invalid characters.*`,
   221  			comment:    "sub group with invalid name",
   222  		},
   223  		{
   224  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   225  			subname:    "myroot",
   226  			sublimits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   227  			err:        `cannot use same name "myroot" for sub group as parent group`,
   228  			comment:    "sub group with same name as parent group",
   229  		},
   230  		{
   231  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   232  			subname:    "snapd",
   233  			sublimits:  quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   234  			err:        `group name "snapd" reserved`,
   235  			comment:    "sub group with reserved name",
   236  		},
   237  		{
   238  			rootlimits: quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(1).WithCPUPercentage(100).WithCPUSet([]int{0}).WithThreadLimit(32).Build(),
   239  			subname:    "zero",
   240  			sublimits:  quota.NewResourcesBuilder().Build(),
   241  			err:        `quota group must have at least one resource limit set`,
   242  			comment:    "sub group with no limits",
   243  		},
   244  	}
   245  
   246  	for _, t := range tt {
   247  		comment := Commentf(t.comment)
   248  		// make a root group
   249  		rootname := t.rootname
   250  		if rootname == "" {
   251  			rootname = "myroot"
   252  		}
   253  		rootGrp, err := quota.NewGroup(rootname, t.rootlimits)
   254  		c.Assert(err, IsNil, comment)
   255  
   256  		// make a sub-group under the root group
   257  		subGrp, err := rootGrp.NewSubGroup(t.subname, t.sublimits)
   258  		if t.err != "" {
   259  			c.Assert(err, ErrorMatches, t.err, comment)
   260  			continue
   261  		}
   262  		c.Assert(err, IsNil, comment)
   263  
   264  		if t.sliceFileName != "" {
   265  			c.Assert(subGrp.SliceFileName(), Equals, "snap."+t.sliceFileName+".slice")
   266  		} else {
   267  			c.Assert(subGrp.SliceFileName(), Equals, "snap.myroot-"+t.subname+".slice")
   268  		}
   269  	}
   270  }
   271  
   272  func (ts *quotaTestSuite) TestComplexSubGroups(c *C) {
   273  	rootGrp, err := quota.NewGroup("myroot", quota.NewResourcesBuilder().WithMemoryLimit(2*quantity.SizeMiB).Build())
   274  	c.Assert(err, IsNil)
   275  
   276  	// try adding 2 sub-groups with total quota split exactly equally
   277  	sub1, err := rootGrp.NewSubGroup("sub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   278  	c.Assert(err, IsNil)
   279  	c.Assert(sub1.SliceFileName(), Equals, "snap.myroot-sub1.slice")
   280  
   281  	sub2, err := rootGrp.NewSubGroup("sub2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   282  	c.Assert(err, IsNil)
   283  	c.Assert(sub2.SliceFileName(), Equals, "snap.myroot-sub2.slice")
   284  
   285  	// adding another sub-group to this group fails
   286  	_, err = rootGrp.NewSubGroup("sub3", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   287  	c.Assert(err, ErrorMatches, "sub-group memory limit of 1 MiB is too large to fit inside group \"myroot\" remaining quota space 0 B")
   288  
   289  	// we can however add a sub-group to one of the sub-groups with the exact
   290  	// size of the parent sub-group
   291  	subsub1, err := sub1.NewSubGroup("subsub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   292  	c.Assert(err, IsNil)
   293  	c.Assert(subsub1.SliceFileName(), Equals, "snap.myroot-sub1-subsub1.slice")
   294  
   295  	// and we can even add a sub-sub-sub-group to the sub-group
   296  	subsubsub1, err := subsub1.NewSubGroup("subsubsub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   297  	c.Assert(err, IsNil)
   298  	c.Assert(subsubsub1.SliceFileName(), Equals, "snap.myroot-sub1-subsub1-subsubsub1.slice")
   299  }
   300  
   301  func (ts *quotaTestSuite) TestGroupUnmixableSnapsSubgroups(c *C) {
   302  	parent, err := quota.NewGroup("parent", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   303  	c.Assert(err, IsNil)
   304  
   305  	// now we add a snap to the parent group
   306  	parent.Snaps = []string{"test-snap"}
   307  
   308  	// add a subgroup to the parent group, this should fail as the group now has snaps
   309  	_, err = parent.NewSubGroup("sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   310  	c.Assert(err, ErrorMatches, "cannot mix sub groups with snaps in the same group")
   311  }
   312  
   313  func (ts *quotaTestSuite) TestJournalNamespaceName(c *C) {
   314  	grp, err := quota.NewGroup("foo", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   315  	c.Assert(err, IsNil)
   316  	c.Check(grp.JournalNamespaceName(), Equals, "snap-foo")
   317  }
   318  
   319  func (ts *quotaTestSuite) TestJournalConfFileName(c *C) {
   320  	grp, err := quota.NewGroup("foo", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   321  	c.Assert(err, IsNil)
   322  	c.Check(grp.JournalConfFileName(), Equals, "journald@snap-foo.conf")
   323  }
   324  
   325  func (ts *quotaTestSuite) TestJournalServiceName(c *C) {
   326  	grp, err := quota.NewGroup("foo", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   327  	c.Assert(err, IsNil)
   328  	c.Check(grp.JournalServiceName(), Equals, "systemd-journald@snap-foo.service")
   329  }
   330  
   331  func (ts *quotaTestSuite) TestJournalServiceDropInDir(c *C) {
   332  	grp, err := quota.NewGroup("foo", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   333  	c.Assert(err, IsNil)
   334  	c.Check(grp.JournalServiceDropInDir(), Equals, "/etc/systemd/system/systemd-journald@snap-foo.service.d")
   335  }
   336  
   337  func (ts *quotaTestSuite) TestJournalServiceDropInFile(c *C) {
   338  	grp, err := quota.NewGroup("foo", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build())
   339  	c.Assert(err, IsNil)
   340  	c.Check(grp.JournalServiceDropInFile(), Equals, "/etc/systemd/system/systemd-journald@snap-foo.service.d/00-snap.conf")
   341  }
   342  
   343  func (ts *quotaTestSuite) TestResolveCrossReferences(c *C) {
   344  	tt := []struct {
   345  		grps    map[string]*quota.Group
   346  		err     string
   347  		comment string
   348  	}{
   349  		{
   350  			grps: map[string]*quota.Group{
   351  				"foogroup": {
   352  					Name:        "foogroup",
   353  					MemoryLimit: quantity.SizeMiB,
   354  				},
   355  			},
   356  			comment: "single group",
   357  		},
   358  		{
   359  			grps: map[string]*quota.Group{
   360  				"foogroup": {
   361  					Name:        "foogroup",
   362  					MemoryLimit: quantity.SizeMiB,
   363  					ParentGroup: "foogroup",
   364  				},
   365  			},
   366  			err:     `group "foogroup" is invalid: group has circular parent reference to itself`,
   367  			comment: "parent group self-reference group",
   368  		},
   369  		{
   370  			grps: map[string]*quota.Group{
   371  				"foogroup": {
   372  					Name:        "foogroup",
   373  					MemoryLimit: quantity.SizeMiB,
   374  					SubGroups:   []string{"foogroup"},
   375  				},
   376  			},
   377  			err:     `group "foogroup" is invalid: group has circular sub-group reference to itself`,
   378  			comment: "parent group self-reference group",
   379  		},
   380  		{
   381  			grps: map[string]*quota.Group{
   382  				"foogroup": {
   383  					Name:        "foogroup",
   384  					MemoryLimit: 0,
   385  				},
   386  			},
   387  			err:     `group "foogroup" is invalid: quota group must have at least one resource limit set`,
   388  			comment: "invalid group",
   389  		},
   390  		{
   391  			grps: map[string]*quota.Group{
   392  				"foogroup": {
   393  					Name:        "foogroup",
   394  					MemoryLimit: quantity.SizeMiB,
   395  				},
   396  				"foogroup2": {
   397  					Name:        "foogroup2",
   398  					MemoryLimit: quantity.SizeMiB,
   399  				},
   400  			},
   401  			comment: "multiple root groups",
   402  		},
   403  		{
   404  			grps: map[string]*quota.Group{
   405  				"foogroup": {
   406  					Name:        "foogroup",
   407  					MemoryLimit: quantity.SizeMiB,
   408  				},
   409  				"subgroup": {
   410  					Name:        "subgroup",
   411  					MemoryLimit: quantity.SizeMiB,
   412  					ParentGroup: "foogroup",
   413  				},
   414  			},
   415  			err:     `group "foogroup" does not reference necessary child group "subgroup"`,
   416  			comment: "incomplete references in parent group to child group",
   417  		},
   418  		{
   419  			grps: map[string]*quota.Group{
   420  				"foogroup": {
   421  					Name:        "foogroup",
   422  					MemoryLimit: quantity.SizeMiB,
   423  					SubGroups:   []string{"subgroup"},
   424  				},
   425  				"subgroup": {
   426  					Name:        "subgroup",
   427  					MemoryLimit: quantity.SizeMiB,
   428  				},
   429  			},
   430  			err:     `group "subgroup" does not reference necessary parent group "foogroup"`,
   431  			comment: "incomplete references in sub-group to parent group",
   432  		},
   433  		{
   434  			grps: map[string]*quota.Group{
   435  				"foogroup": {
   436  					Name:        "foogroup",
   437  					MemoryLimit: quantity.SizeMiB,
   438  					SubGroups:   []string{"subgroup"},
   439  				},
   440  				"subgroup": {
   441  					Name:        "subgroup",
   442  					MemoryLimit: quantity.SizeMiB,
   443  					ParentGroup: "foogroup",
   444  				},
   445  			},
   446  			comment: "valid fully specified sub-group",
   447  		},
   448  		{
   449  			grps: map[string]*quota.Group{
   450  				"foogroup": {
   451  					Name:        "foogroup",
   452  					MemoryLimit: 2 * quantity.SizeMiB,
   453  					SubGroups:   []string{"subgroup1", "subgroup2"},
   454  				},
   455  				"subgroup1": {
   456  					Name:        "subgroup1",
   457  					MemoryLimit: quantity.SizeMiB,
   458  					ParentGroup: "foogroup",
   459  				},
   460  				"subgroup2": {
   461  					Name:        "subgroup2",
   462  					MemoryLimit: quantity.SizeMiB,
   463  					ParentGroup: "foogroup",
   464  				},
   465  			},
   466  			comment: "multiple valid fully specified sub-groups",
   467  		},
   468  		{
   469  			grps: map[string]*quota.Group{
   470  				"foogroup": {
   471  					Name:        "foogroup",
   472  					MemoryLimit: quantity.SizeMiB,
   473  					SubGroups:   []string{"subgroup1"},
   474  				},
   475  				"subgroup1": {
   476  					Name:        "subgroup1",
   477  					MemoryLimit: quantity.SizeMiB,
   478  					ParentGroup: "foogroup",
   479  					SubGroups:   []string{"subgroup2"},
   480  				},
   481  				"subgroup2": {
   482  					Name:        "subgroup2",
   483  					MemoryLimit: quantity.SizeMiB,
   484  					ParentGroup: "subgroup1",
   485  				},
   486  			},
   487  			comment: "deeply nested valid fully specified sub-groups",
   488  		},
   489  		{
   490  			grps: map[string]*quota.Group{
   491  				"foogroup": {
   492  					Name:        "foogroup",
   493  					MemoryLimit: quantity.SizeMiB,
   494  					SubGroups:   []string{"subgroup1"},
   495  				},
   496  				"subgroup1": {
   497  					Name:        "subgroup1",
   498  					MemoryLimit: quantity.SizeMiB,
   499  					ParentGroup: "foogroup",
   500  					SubGroups:   []string{"subgroup2"},
   501  				},
   502  				"subgroup2": {
   503  					Name:        "subgroup2",
   504  					MemoryLimit: quantity.SizeMiB,
   505  					// missing parent reference
   506  				},
   507  			},
   508  			err:     `group "subgroup2" does not reference necessary parent group "subgroup1"`,
   509  			comment: "deeply nested invalid fully specified sub-groups",
   510  		},
   511  		{
   512  			grps: map[string]*quota.Group{
   513  				"not-foogroup": {
   514  					Name:        "foogroup",
   515  					MemoryLimit: quantity.SizeMiB,
   516  				},
   517  			},
   518  			err:     `group has name "foogroup", but is referenced as "not-foogroup"`,
   519  			comment: "group misname",
   520  		},
   521  		{
   522  			grps: map[string]*quota.Group{
   523  				"foogroup": {
   524  					Name:        "foogroup",
   525  					MemoryLimit: quantity.SizeMiB,
   526  					SubGroups:   []string{"other-missing"},
   527  				},
   528  			},
   529  			err:     `missing group "other-missing" referenced as the sub-group of group "foogroup"`,
   530  			comment: "missing sub-group name",
   531  		},
   532  		{
   533  			grps: map[string]*quota.Group{
   534  				"foogroup": {
   535  					Name:        "foogroup",
   536  					MemoryLimit: quantity.SizeMiB,
   537  					ParentGroup: "other-missing",
   538  				},
   539  			},
   540  			err:     `missing group "other-missing" referenced as the parent of group "foogroup"`,
   541  			comment: "missing sub-group name",
   542  		},
   543  	}
   544  
   545  	for _, t := range tt {
   546  		comment := Commentf(t.comment)
   547  		err := quota.ResolveCrossReferences(t.grps)
   548  		if t.err != "" {
   549  			c.Assert(err, ErrorMatches, t.err, comment)
   550  		} else {
   551  			c.Assert(err, IsNil, comment)
   552  		}
   553  	}
   554  }
   555  
   556  func (ts *quotaTestSuite) TestChangingRequirementsDoesNotBreakExistingGroups(c *C) {
   557  	tt := []struct {
   558  		grp     *quota.Group
   559  		err     string
   560  		comment string
   561  	}{
   562  		// Test that an existing group with lower than 640kB limit
   563  		// does not break .validate(), since the requirement was increased
   564  		{
   565  			grp: &quota.Group{
   566  				Name:        "foogroup",
   567  				MemoryLimit: quantity.SizeKiB * 12,
   568  			},
   569  			comment: "group with a lower memory limit than 640kB",
   570  		},
   571  	}
   572  
   573  	for _, t := range tt {
   574  		comment := Commentf(t.comment)
   575  		err := t.grp.ValidateGroup()
   576  		if t.err != "" {
   577  			c.Assert(err, ErrorMatches, t.err, comment)
   578  		} else {
   579  			c.Assert(err, IsNil, comment)
   580  		}
   581  	}
   582  }
   583  
   584  func (ts *quotaTestSuite) TestAddAllNecessaryGroupsAvoidsInfiniteRecursion(c *C) {
   585  	grp, err := quota.NewGroup("infinite-group", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   586  	c.Assert(err, IsNil)
   587  
   588  	grp2, err := grp.NewSubGroup("infinite-group2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   589  	c.Assert(err, IsNil)
   590  
   591  	// create a cycle artificially to the same group
   592  	grp2.SetInternalSubGroups([]*quota.Group{grp2})
   593  
   594  	// now we fail to add this to a quota set
   595  	qs := &quota.QuotaGroupSet{}
   596  	err = qs.AddAllNecessaryGroups(grp)
   597  	c.Assert(err, ErrorMatches, "internal error: circular reference found")
   598  
   599  	// create a more difficult to detect cycle going from the child to the
   600  	// parent
   601  	grp2.SetInternalSubGroups([]*quota.Group{grp})
   602  	err = qs.AddAllNecessaryGroups(grp)
   603  	c.Assert(err, ErrorMatches, "internal error: circular reference found")
   604  
   605  	// make a real sub-group and try one more level of indirection going back
   606  	// to the parent
   607  	grp2.SetInternalSubGroups(nil)
   608  	grp3, err := grp2.NewSubGroup("infinite-group3", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   609  	c.Assert(err, IsNil)
   610  	grp3.SetInternalSubGroups([]*quota.Group{grp})
   611  
   612  	err = qs.AddAllNecessaryGroups(grp)
   613  	c.Assert(err, ErrorMatches, "internal error: circular reference found")
   614  }
   615  
   616  func (ts *quotaTestSuite) TestAddAllNecessaryGroups(c *C) {
   617  	qs := &quota.QuotaGroupSet{}
   618  
   619  	// it should initially be empty
   620  	c.Assert(qs.AllQuotaGroups(), HasLen, 0)
   621  
   622  	grp1, err := quota.NewGroup("myroot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   623  	c.Assert(err, IsNil)
   624  
   625  	// add the group and make sure it is in the set
   626  	err = qs.AddAllNecessaryGroups(grp1)
   627  	c.Assert(err, IsNil)
   628  	c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1})
   629  
   630  	// adding multiple times doesn't change the set
   631  	err = qs.AddAllNecessaryGroups(grp1)
   632  	c.Assert(err, IsNil)
   633  	err = qs.AddAllNecessaryGroups(grp1)
   634  	c.Assert(err, IsNil)
   635  	c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1})
   636  
   637  	// add a new group and make sure it is in the set now
   638  	grp2, err := quota.NewGroup("myroot2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   639  	c.Assert(err, IsNil)
   640  	err = qs.AddAllNecessaryGroups(grp2)
   641  	c.Assert(err, IsNil)
   642  	c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2})
   643  
   644  	// start again
   645  	qs = &quota.QuotaGroupSet{}
   646  
   647  	// make a sub-group and add the root group - it will automatically add
   648  	// the sub-group without us needing to explicitly add the sub-group
   649  	subgrp1, err := grp1.NewSubGroup("mysub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   650  	c.Assert(err, IsNil)
   651  	// add grp2 as well
   652  	err = qs.AddAllNecessaryGroups(grp2)
   653  	c.Assert(err, IsNil)
   654  
   655  	err = qs.AddAllNecessaryGroups(grp1)
   656  	c.Assert(err, IsNil)
   657  	c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2, subgrp1})
   658  
   659  	// we can explicitly add the sub-group and still have the same set too
   660  	err = qs.AddAllNecessaryGroups(subgrp1)
   661  	c.Assert(err, IsNil)
   662  	c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2, subgrp1})
   663  
   664  	// create a new set of group and sub-groups to add the deepest child group
   665  	// and add that, and notice that the root groups are also added
   666  	grp3, err := quota.NewGroup("myroot3", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   667  	c.Assert(err, IsNil)
   668  
   669  	subgrp3, err := grp3.NewSubGroup("mysub3", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   670  	c.Assert(err, IsNil)
   671  
   672  	subsubgrp3, err := subgrp3.NewSubGroup("mysubsub3", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   673  	c.Assert(err, IsNil)
   674  
   675  	err = qs.AddAllNecessaryGroups(subsubgrp3)
   676  	c.Assert(err, IsNil)
   677  	c.Assert(qs.AllQuotaGroups(), DeepEquals, []*quota.Group{grp1, grp2, grp3, subgrp1, subgrp3, subsubgrp3})
   678  
   679  	// finally create a tree with multiple branches and ensure that adding just
   680  	// a single deepest child will add all the other deepest children from other
   681  	// branches
   682  	grp4, err := quota.NewGroup("myroot4", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   683  	c.Assert(err, IsNil)
   684  
   685  	subgrp4, err := grp4.NewSubGroup("mysub4", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB/2).Build())
   686  	c.Assert(err, IsNil)
   687  
   688  	subgrp5, err := grp4.NewSubGroup("mysub5", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB/2).Build())
   689  	c.Assert(err, IsNil)
   690  
   691  	// adding just subgrp5 to a quota set will automatically add the other sub
   692  	// group, subgrp4
   693  	qs2 := &quota.QuotaGroupSet{}
   694  	err = qs2.AddAllNecessaryGroups(subgrp4)
   695  	c.Assert(err, IsNil)
   696  	c.Assert(qs2.AllQuotaGroups(), DeepEquals, []*quota.Group{grp4, subgrp4, subgrp5})
   697  }
   698  
   699  func (ts *quotaTestSuite) TestResolveCrossReferencesLimitCheckSkipsSelf(c *C) {
   700  	grp1, err := quota.NewGroup("myroot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   701  	c.Assert(err, IsNil)
   702  
   703  	subgrp1, err := grp1.NewSubGroup("mysub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   704  	c.Assert(err, IsNil)
   705  
   706  	subgrp2, err := subgrp1.NewSubGroup("mysub2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   707  	c.Assert(err, IsNil)
   708  
   709  	all := map[string]*quota.Group{
   710  		"myroot": grp1,
   711  		"mysub1": subgrp1,
   712  		"mysub2": subgrp2,
   713  	}
   714  	err = quota.ResolveCrossReferences(all)
   715  	c.Assert(err, IsNil)
   716  }
   717  
   718  func (ts *quotaTestSuite) TestResolveCrossReferencesCircular(c *C) {
   719  	grp1, err := quota.NewGroup("myroot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   720  	c.Assert(err, IsNil)
   721  
   722  	subgrp1, err := grp1.NewSubGroup("mysub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   723  	c.Assert(err, IsNil)
   724  
   725  	subgrp2, err := subgrp1.NewSubGroup("mysub2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   726  	c.Assert(err, IsNil)
   727  
   728  	all := map[string]*quota.Group{
   729  		"myroot": grp1,
   730  		"mysub1": subgrp1,
   731  		"mysub2": subgrp2,
   732  	}
   733  	// try to set up circular ref
   734  	subgrp2.SubGroups = append(subgrp2.SubGroups, "mysub1")
   735  	err = quota.ResolveCrossReferences(all)
   736  	c.Assert(err, ErrorMatches, `.*reference necessary parent.*`)
   737  }
   738  
   739  type systemctlInactiveServiceError struct{}
   740  
   741  func (s systemctlInactiveServiceError) Msg() []byte   { return []byte("inactive") }
   742  func (s systemctlInactiveServiceError) ExitCode() int { return 0 }
   743  func (s systemctlInactiveServiceError) Error() string { return "inactive" }
   744  
   745  func (ts *quotaTestSuite) TestCurrentMemoryUsage(c *C) {
   746  	systemctlCalls := 0
   747  	r := systemd.MockSystemctl(func(args ...string) ([]byte, error) {
   748  		systemctlCalls++
   749  		switch systemctlCalls {
   750  
   751  		// inactive case, memory is 0
   752  		case 1:
   753  			// first time pretend the service is inactive
   754  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   755  			return []byte("inactive"), systemctlInactiveServiceError{}
   756  
   757  		// active but no tasks, but we still return the memory usage because it
   758  		// can be valid on some systems to have non-zero memory usage for a
   759  		// group without any tasks in it, such as on hirsute, arch, fedora 33+,
   760  		// and debian sid
   761  		case 2:
   762  			// now pretend it is active
   763  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   764  			return []byte("active"), nil
   765  		case 3:
   766  			// and the memory count can be non-zero like
   767  			c.Assert(args, DeepEquals, []string{"show", "--property", "MemoryCurrent", "snap.group.slice"})
   768  			return []byte("MemoryCurrent=4096"), nil
   769  
   770  		case 4:
   771  			// now pretend it is active
   772  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   773  			return []byte("active"), nil
   774  		case 5:
   775  			// and the memory count can be zero too
   776  			c.Assert(args, DeepEquals, []string{"show", "--property", "MemoryCurrent", "snap.group.slice"})
   777  			return []byte("MemoryCurrent=0"), nil
   778  
   779  		// bug case where 16 exb is erroneous - this is left in for posterity,
   780  		// but we don't handle this differently, previously we had a workaround
   781  		// for this sort of case, but it ended up not being tenable but still
   782  		// test that a huge value just gets reported as-is
   783  		case 6:
   784  			// the cgroup is active, has no tasks and has 16 exb usage
   785  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   786  			return []byte("active"), nil
   787  		case 7:
   788  			// since it is active, we will query the current memory usage,
   789  			// this time return an obviously wrong number
   790  			c.Assert(args, DeepEquals, []string{"show", "--property", "MemoryCurrent", "snap.group.slice"})
   791  			return []byte("MemoryCurrent=18446744073709551615"), nil
   792  
   793  		default:
   794  			c.Errorf("too many systemctl calls (%d) (current call is %+v)", systemctlCalls, args)
   795  			return []byte("broken test"), fmt.Errorf("broken test")
   796  		}
   797  	})
   798  	defer r()
   799  
   800  	grp1, err := quota.NewGroup("group", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   801  	c.Assert(err, IsNil)
   802  
   803  	// group initially is inactive, so it has no current memory usage
   804  	currentMem, err := grp1.CurrentMemoryUsage()
   805  	c.Assert(err, IsNil)
   806  	c.Assert(currentMem, Equals, quantity.Size(0))
   807  
   808  	// now with the slice mocked as active it has real usage
   809  	currentMem, err = grp1.CurrentMemoryUsage()
   810  	c.Assert(err, IsNil)
   811  	c.Assert(currentMem, Equals, 4*quantity.SizeKiB)
   812  
   813  	// but it can also have 0 usage
   814  	currentMem, err = grp1.CurrentMemoryUsage()
   815  	c.Assert(err, IsNil)
   816  	c.Assert(currentMem, Equals, quantity.Size(0))
   817  
   818  	// and it can also be an incredibly huge value too
   819  	currentMem, err = grp1.CurrentMemoryUsage()
   820  	c.Assert(err, IsNil)
   821  	const sixteenExb = quantity.Size(1<<64 - 1)
   822  	c.Assert(currentMem, Equals, sixteenExb)
   823  }
   824  
   825  func (ts *quotaTestSuite) TestCurrentTaskUsage(c *C) {
   826  	systemctlCalls := 0
   827  	r := systemd.MockSystemctl(func(args ...string) ([]byte, error) {
   828  		systemctlCalls++
   829  		switch systemctlCalls {
   830  
   831  		// inactive case, number of tasks must be 0
   832  		case 1:
   833  			// first time pretend the service is inactive
   834  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   835  			return []byte("inactive"), systemctlInactiveServiceError{}
   836  
   837  		// active cases
   838  		case 2:
   839  			// now pretend it is active
   840  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   841  			return []byte("active"), nil
   842  		case 3:
   843  			// and the task count can be non-zero like
   844  			c.Assert(args, DeepEquals, []string{"show", "--property", "TasksCurrent", "snap.group.slice"})
   845  			return []byte("TasksCurrent=32"), nil
   846  
   847  		case 4:
   848  			// now pretend it is active
   849  			c.Assert(args, DeepEquals, []string{"is-active", "snap.group.slice"})
   850  			return []byte("active"), nil
   851  		case 5:
   852  			// and no tasks are active
   853  			c.Assert(args, DeepEquals, []string{"show", "--property", "TasksCurrent", "snap.group.slice"})
   854  			return []byte("TasksCurrent=0"), nil
   855  
   856  		default:
   857  			c.Errorf("unexpected number of systemctl calls (%d) (current call is %+v)", systemctlCalls, args)
   858  			return []byte("broken test"), fmt.Errorf("broken test")
   859  		}
   860  	})
   861  	defer r()
   862  
   863  	grp1, err := quota.NewGroup("group", quota.NewResourcesBuilder().WithThreadLimit(32).Build())
   864  	c.Assert(err, IsNil)
   865  
   866  	// group initially is inactive, so it has no current task usage
   867  	currentTasks, err := grp1.CurrentTaskUsage()
   868  	c.Check(err, IsNil)
   869  	c.Check(currentTasks, Equals, 0)
   870  	c.Check(systemctlCalls, Equals, 1)
   871  
   872  	// now with the slice mocked as active it has real usage
   873  	currentTasks, err = grp1.CurrentTaskUsage()
   874  	c.Check(err, IsNil)
   875  	c.Check(currentTasks, Equals, 32)
   876  	c.Check(systemctlCalls, Equals, 3)
   877  
   878  	// but it can also have 0 usage
   879  	currentTasks, err = grp1.CurrentTaskUsage()
   880  	c.Check(err, IsNil)
   881  	c.Check(currentTasks, Equals, 0)
   882  	c.Check(systemctlCalls, Equals, 5)
   883  }
   884  
   885  func (ts *quotaTestSuite) TestGetGroupQuotaAllocations(c *C) {
   886  	// Verify we get the correct allocations for a group with a more complex tree-structure
   887  	// and different quotas split out into different sub-groups.
   888  	// The tree we will be verifying will be like this
   889  	//                   <groot>                     (root group, 1GB Memory)
   890  	// 				  /    |      \
   891  	// 	     <cpu-q0>      |       \                 (subgroup, 2x50% Cpu Quota)
   892  	// 		 /        <thread-q0>   \                (subgroup, 32 threads)
   893  	//      /	           |     <cpus-q0>           (subgroup, cpu-set quota with cpus 0,1)
   894  	// <mem-q1>        <mem-q2>       \              (2 subgroups, 256MB Memory each)
   895  	//    |                |       <cpus-q1>         (subgroup, cpu-set quota with cpus 0)
   896  	// <cpu-q1>        <thread-q1>                   (subgroups, cpu quota of 50%, thread quota of 16)
   897  	//                     |
   898  	//                 <mem-q3>                      (subgroup, 128MB Memory)
   899  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
   900  	c.Assert(err, IsNil)
   901  
   902  	cpuq0, err := grp1.NewSubGroup("cpu-q0", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
   903  	c.Assert(err, IsNil)
   904  
   905  	thrq0, err := grp1.NewSubGroup("thread-q0", quota.NewResourcesBuilder().WithThreadLimit(32).Build())
   906  	c.Assert(err, IsNil)
   907  
   908  	cpusq0, err := grp1.NewSubGroup("cpus-q0", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
   909  	c.Assert(err, IsNil)
   910  
   911  	memq1, err := cpuq0.NewSubGroup("mem-q1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB*256).Build())
   912  	c.Assert(err, IsNil)
   913  
   914  	memq2, err := thrq0.NewSubGroup("mem-q2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB*256).Build())
   915  	c.Assert(err, IsNil)
   916  
   917  	_, err = cpusq0.NewSubGroup("cpus-q1", quota.NewResourcesBuilder().WithCPUSet([]int{0}).Build())
   918  	c.Check(err, IsNil)
   919  
   920  	_, err = memq1.NewSubGroup("cpu-q1", quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build())
   921  	c.Check(err, IsNil)
   922  
   923  	thrq1, err := memq2.NewSubGroup("thread-q1", quota.NewResourcesBuilder().WithThreadLimit(16).Build())
   924  	c.Assert(err, IsNil)
   925  
   926  	_, err = thrq1.NewSubGroup("mem-q3", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB*128).Build())
   927  	c.Check(err, IsNil)
   928  
   929  	// Now we verify that the reservations made for the relevant groups are correct. The upper parent group will
   930  	// contained a combined overview of reserveations made.
   931  	allReservations := grp1.InspectInternalQuotaAllocations()
   932  
   933  	// Verify the root group
   934  	c.Check(allReservations["groot"], DeepEquals, &quota.GroupQuotaAllocations{
   935  		MemoryLimit:               quantity.SizeGiB,
   936  		MemoryReservedByChildren:  quantity.SizeMiB * 512,
   937  		CPUReservedByChildren:     100,
   938  		ThreadsReservedByChildren: 32,
   939  		CPUSetLimit:               []int{},
   940  		CPUSetReservedByChildren:  []int{0, 1},
   941  	})
   942  
   943  	// Verify the subgroup cpu-q0
   944  	c.Check(allReservations["cpu-q0"], DeepEquals, &quota.GroupQuotaAllocations{
   945  		CPULimit:                 100,
   946  		CPUReservedByChildren:    50,
   947  		MemoryReservedByChildren: quantity.SizeMiB * 256,
   948  		CPUSetLimit:              []int{},
   949  	})
   950  
   951  	// Verify the subgroup thread-q0
   952  	c.Check(allReservations["thread-q0"], DeepEquals, &quota.GroupQuotaAllocations{
   953  		MemoryReservedByChildren:  quantity.SizeMiB * 256,
   954  		ThreadsLimit:              32,
   955  		ThreadsReservedByChildren: 16,
   956  		CPUSetLimit:               []int{},
   957  	})
   958  
   959  	// Verify the subgroup cpus-q0
   960  	c.Check(allReservations["cpus-q0"], DeepEquals, &quota.GroupQuotaAllocations{
   961  		CPUSetLimit:              []int{0, 1},
   962  		CPUSetReservedByChildren: []int{0},
   963  	})
   964  
   965  	// Verify the subgroup cpus-q1
   966  	c.Check(allReservations["cpus-q1"], DeepEquals, &quota.GroupQuotaAllocations{
   967  		CPUSetLimit: []int{0},
   968  	})
   969  
   970  	// Verify the subgroup mem-q1
   971  	c.Check(allReservations["mem-q1"], DeepEquals, &quota.GroupQuotaAllocations{
   972  		MemoryLimit:           quantity.SizeMiB * 256,
   973  		CPUReservedByChildren: 50,
   974  		CPUSetLimit:           []int{},
   975  	})
   976  
   977  	// Verify the subgroup mem-q2
   978  	c.Check(allReservations["mem-q2"], DeepEquals, &quota.GroupQuotaAllocations{
   979  		MemoryLimit:               quantity.SizeMiB * 256,
   980  		MemoryReservedByChildren:  quantity.SizeMiB * 128,
   981  		ThreadsReservedByChildren: 16,
   982  		CPUSetLimit:               []int{},
   983  	})
   984  
   985  	// Verify the subgroup cpu-q1
   986  	c.Check(allReservations["cpu-q1"], DeepEquals, &quota.GroupQuotaAllocations{
   987  		CPULimit:    50,
   988  		CPUSetLimit: []int{},
   989  	})
   990  
   991  	// Verify the subgroup thread-q1
   992  	c.Check(allReservations["thread-q1"], DeepEquals, &quota.GroupQuotaAllocations{
   993  		MemoryReservedByChildren: quantity.SizeMiB * 128,
   994  		ThreadsLimit:             16,
   995  		CPUSetLimit:              []int{},
   996  	})
   997  
   998  	// Verify the subgroup mem-q3
   999  	c.Check(allReservations["mem-q3"], DeepEquals, &quota.GroupQuotaAllocations{
  1000  		MemoryLimit: quantity.SizeMiB * 128,
  1001  		CPUSetLimit: []int{},
  1002  	})
  1003  }
  1004  
  1005  func (ts *quotaTestSuite) TestNestingOfLimitsWithExceedingParent(c *C) {
  1006  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1007  	c.Assert(err, IsNil)
  1008  
  1009  	subgrp1, err := grp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1010  	c.Assert(err, IsNil)
  1011  
  1012  	_, err = grp1.NewSubGroup("thread-sub", quota.NewResourcesBuilder().WithThreadLimit(32).Build())
  1013  	c.Check(err, IsNil)
  1014  
  1015  	_, err = grp1.NewSubGroup("cpus-sub", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1016  	c.Check(err, IsNil)
  1017  
  1018  	// Now we have the root with a memory limit, and three subgroups with
  1019  	// each with one of the remaining limits. The point of this test is to make
  1020  	// sure nested cases of limits that don't fit are caught and reported. So in a
  1021  	// sub-sub group we create a limit higher than the upper parent
  1022  	_, err = subgrp1.NewSubGroup("mem-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB*2).Build())
  1023  	c.Check(err, ErrorMatches, `sub-group memory limit of 2 GiB is too large to fit inside group \"groot\" remaining quota space 1 GiB`)
  1024  }
  1025  
  1026  func (ts *quotaTestSuite) TestNestingOfLimitsWithExceedingSiblings(c *C) {
  1027  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1028  	c.Assert(err, IsNil)
  1029  
  1030  	subgrp1, err := grp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1031  	c.Assert(err, IsNil)
  1032  
  1033  	_, err = grp1.NewSubGroup("thread-sub", quota.NewResourcesBuilder().WithThreadLimit(32).Build())
  1034  	c.Check(err, IsNil)
  1035  
  1036  	subgrp2, err := grp1.NewSubGroup("cpus-sub", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1037  	c.Check(err, IsNil)
  1038  
  1039  	// The point here is to catch if we, in a nested, scenario, together with our siblings
  1040  	// exceed one of the parent's limits.
  1041  	subgrp3, err := subgrp1.NewSubGroup("mem-sub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1042  	c.Assert(err, IsNil)
  1043  
  1044  	_, err = subgrp3.NewSubGroup("mem-sub-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1045  	c.Check(err, IsNil)
  1046  
  1047  	// now we have consumed the entire memory quota set by the parent, so this should fail
  1048  	_, err = subgrp2.NewSubGroup("mem-sub2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1049  	c.Check(err, ErrorMatches, `sub-group memory limit of 1 GiB is too large to fit inside group \"groot\" remaining quota space 0 B`)
  1050  }
  1051  
  1052  func (ts *quotaTestSuite) TestChangingSubgroupLimits(c *C) {
  1053  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1054  	c.Assert(err, IsNil)
  1055  
  1056  	subgrp1, err := grp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1057  	c.Assert(err, IsNil)
  1058  
  1059  	// Create a nested subgroup with a memory limit of only half, then we try to adjust the value to another
  1060  	// larger value. This must succeed.
  1061  	memgrp, err := subgrp1.NewSubGroup("mem-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB/2).Build())
  1062  	c.Assert(err, IsNil)
  1063  
  1064  	// Now we change it to fill the entire quota of our upper parent
  1065  	err = memgrp.UpdateQuotaLimits(quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1066  	c.Check(err, IsNil)
  1067  
  1068  	// Now we try to change the limits of the subgroup to a value that is too large to fit inside the parent,
  1069  	// the error message should also correctly report that the remaining space is 1GiB, as it should not consider
  1070  	// the current memory quota of the subgroup.
  1071  	err = memgrp.UpdateQuotaLimits(quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build())
  1072  	c.Check(err, ErrorMatches, `sub-group memory limit of 2 GiB is too large to fit inside group \"groot\" remaining quota space 1 GiB`)
  1073  }
  1074  
  1075  func (ts *quotaTestSuite) TestChangingParentMemoryLimits(c *C) {
  1076  	// The purpose here is to make sure we can't change the limits of the parent group
  1077  	// that would otherwise conflict with the current usage of limits by children of the
  1078  	// parent.
  1079  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1080  	c.Assert(err, IsNil)
  1081  
  1082  	subgrp1, err := grp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1083  	c.Assert(err, IsNil)
  1084  
  1085  	// Create a nested subgroup with a memory limit that takes up the entire quota of the parent
  1086  	_, err = subgrp1.NewSubGroup("mem-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1087  	c.Assert(err, IsNil)
  1088  
  1089  	// Now the test is to change the upper most parent limit so that it would be less
  1090  	// than the current usage, which we should not be able to do
  1091  	err = grp1.QuotaUpdateCheck(quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB / 2).Build())
  1092  	c.Check(err, ErrorMatches, `group memory limit of 512 MiB is too small to fit current subgroup usage of 1 GiB`)
  1093  }
  1094  
  1095  func (ts *quotaTestSuite) TestChangingParentCpuPercentageLimits(c *C) {
  1096  	// The purpose here is to make sure we can't change the limits of the parent group
  1097  	// that would otherwise conflict with the current usage of limits by children of the
  1098  	// parent.
  1099  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1100  	c.Assert(err, IsNil)
  1101  
  1102  	subgrp1, err := grp1.NewSubGroup("mem-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1103  	c.Assert(err, IsNil)
  1104  
  1105  	// Create a nested subgroup with a cpu limit that takes up the entire quota of the parent
  1106  	_, err = subgrp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1107  	c.Assert(err, IsNil)
  1108  
  1109  	// Now the test is to change the upper most parent limit so that it would be less
  1110  	// than the current usage, which we should not be able to do
  1111  	err = grp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build())
  1112  	c.Check(err, ErrorMatches, `group cpu limit of 50% is less than current subgroup usage of 100%`)
  1113  }
  1114  
  1115  func (ts *quotaTestSuite) TestChangingParentCpuSetLimits(c *C) {
  1116  	// The purpose here is to make sure we can't change the limits of the parent group
  1117  	// that would otherwise conflict with the current usage of limits by children of the
  1118  	// parent.
  1119  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1120  	c.Assert(err, IsNil)
  1121  
  1122  	subgrp1, err := grp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1123  	c.Assert(err, IsNil)
  1124  
  1125  	// Create a nested subgroup with a cpu limit that uses both of allowed cpus
  1126  	_, err = subgrp1.NewSubGroup("cpuset-sub", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1127  	c.Assert(err, IsNil)
  1128  
  1129  	// Now the test is to change the upper most parent limit so that it would be more
  1130  	// restrictive then the previous limit
  1131  	err = grp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUSet([]int{0}).Build())
  1132  	c.Check(err, ErrorMatches, `group cpu-set \[0\] is not a superset of current subgroup usage of \[0 1\]`)
  1133  }
  1134  
  1135  func (ts *quotaTestSuite) TestChangingParentThreadLimits(c *C) {
  1136  	// The purpose here is to make sure we can't change the limits of the parent group
  1137  	// that would otherwise conflict with the current usage of limits by children of the
  1138  	// parent.
  1139  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithThreadLimit(32).Build())
  1140  	c.Assert(err, IsNil)
  1141  
  1142  	subgrp1, err := grp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1143  	c.Assert(err, IsNil)
  1144  
  1145  	// Create a nested subgroup with a thread limit that takes up the entire quota of the parent
  1146  	_, err = subgrp1.NewSubGroup("thread-sub", quota.NewResourcesBuilder().WithThreadLimit(32).Build())
  1147  	c.Assert(err, IsNil)
  1148  
  1149  	// Now the test is to change the upper most parent limit so that it would be less
  1150  	// than the current usage, which we should not be able to do
  1151  	err = grp1.QuotaUpdateCheck(quota.NewResourcesBuilder().WithThreadLimit(16).Build())
  1152  	c.Check(err, ErrorMatches, `group thread limit of 16 is too small to fit current subgroup usage of 32`)
  1153  }
  1154  
  1155  func (ts *quotaTestSuite) TestChangingMiddleParentLimits(c *C) {
  1156  	// Catch any algorithmic mistakes made in regards to not catching parents
  1157  	// that are also children of other parents.
  1158  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1159  	c.Assert(err, IsNil)
  1160  
  1161  	subgrp1, err := grp1.NewSubGroup("cpu-sub1", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1162  	c.Assert(err, IsNil)
  1163  
  1164  	// Create a nested subgroup with a memory limit that takes up the entire quota of the upper parent
  1165  	subgrp2, err := subgrp1.NewSubGroup("mem-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1166  	c.Assert(err, IsNil)
  1167  
  1168  	// Create a nested subgroup with a cpu limit that takes up the entire quota of the middle parent
  1169  	_, err = subgrp2.NewSubGroup("cpu-sub2", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1170  	c.Assert(err, IsNil)
  1171  
  1172  	// Now the test is to change the middle parent limit so that it would be less
  1173  	// than the current usage, which we should not be able to do
  1174  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build())
  1175  	c.Check(err, ErrorMatches, `group cpu limit of 50% is less than current subgroup usage of 100%`)
  1176  }
  1177  
  1178  func (ts *quotaTestSuite) TestAddingNewMiddleParentMemoryLimits(c *C) {
  1179  	// The purpose here is to make sure we catch any new limits inserted into
  1180  	// the tree, which would conflict with the current usage.
  1181  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB*2).Build())
  1182  	c.Assert(err, IsNil)
  1183  
  1184  	subgrp1, err := grp1.NewSubGroup("cpu-sub1", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1185  	c.Assert(err, IsNil)
  1186  
  1187  	// Create a nested subgroup with a memory limit that takes half of the quota of the upper parent
  1188  	subgrp2, err := subgrp1.NewSubGroup("mem-sub", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1189  	c.Assert(err, IsNil)
  1190  
  1191  	// Create a nested subgroup with a cpu limit that takes up the entire quota of the middle parent
  1192  	_, err = subgrp2.NewSubGroup("cpu-sub2", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1193  	c.Assert(err, IsNil)
  1194  
  1195  	// Now lets inject a memory quota that is less than currently used by children
  1196  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB * 512).Build())
  1197  	c.Check(err, ErrorMatches, `group memory limit of 512 MiB is too small to fit current subgroup usage of 1 GiB`)
  1198  
  1199  	// Now lets inject one that is larger, that should be possible
  1200  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB * 2).Build())
  1201  	c.Check(err, IsNil)
  1202  }
  1203  
  1204  func (ts *quotaTestSuite) TestAddingNewMiddleParentCpuLimits(c *C) {
  1205  	// The purpose here is to make sure we catch any new limits inserted into
  1206  	// the tree, which would conflict with the current usage.
  1207  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1208  	c.Assert(err, IsNil)
  1209  
  1210  	subgrp1, err := grp1.NewSubGroup("mem-sub1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1211  	c.Assert(err, IsNil)
  1212  
  1213  	// Create a nested subgroup with a cpu limit that takes half of the quota of the upper parent
  1214  	subgrp2, err := subgrp1.NewSubGroup("cpu-sub", quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build())
  1215  	c.Assert(err, IsNil)
  1216  
  1217  	// Create a nested subgroup with a memory limit that takes up the entire quota of the middle parent
  1218  	_, err = subgrp2.NewSubGroup("mem-sub2", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1219  	c.Assert(err, IsNil)
  1220  
  1221  	// Now lets inject a cpu quota that is less than currently used by children
  1222  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(25).Build())
  1223  	c.Check(err, ErrorMatches, `group cpu limit of 25% is less than current subgroup usage of 50%`)
  1224  
  1225  	// Now lets inject one that is larger, that should be possible
  1226  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1227  	c.Check(err, IsNil)
  1228  }
  1229  
  1230  func (ts *quotaTestSuite) TestAddingNewMiddleParentCpuSetLimits(c *C) {
  1231  	// The purpose here is to make sure we catch any new limits inserted into
  1232  	// the tree, which would conflict with the current usage.
  1233  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1, 2, 3}).Build())
  1234  	c.Assert(err, IsNil)
  1235  
  1236  	subgrp1, err := grp1.NewSubGroup("cpu-sub1", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1237  	c.Assert(err, IsNil)
  1238  
  1239  	// Create a nested subgroup with a more restrictive cpu-set of the upper parent
  1240  	subgrp2, err := subgrp1.NewSubGroup("cpuset-sub", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1241  	c.Assert(err, IsNil)
  1242  
  1243  	// Create a nested subgroup with a cpu limit that takes up the entire quota of the middle parent
  1244  	_, err = subgrp2.NewSubGroup("cpu-sub2", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1245  	c.Assert(err, IsNil)
  1246  
  1247  	// Now lets inject a cpu-set that does not match whats currently used by children
  1248  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUSet([]int{2, 3}).Build())
  1249  	c.Check(err, ErrorMatches, `group cpu-set \[2 3\] is not a superset of current subgroup usage of \[0 1\]`)
  1250  
  1251  	// Now lets inject one that is larger, that should be possible
  1252  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithCPUSet([]int{0, 1, 2}).Build())
  1253  	c.Check(err, IsNil)
  1254  }
  1255  
  1256  func (ts *quotaTestSuite) TestAddingNewMiddleParentThreadLimits(c *C) {
  1257  	// The purpose here is to make sure we catch any new limits inserted into
  1258  	// the tree, which would conflict with the current usage.
  1259  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithThreadLimit(1024).Build())
  1260  	c.Assert(err, IsNil)
  1261  
  1262  	subgrp1, err := grp1.NewSubGroup("cpu-sub1", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1263  	c.Assert(err, IsNil)
  1264  
  1265  	// Create a nested subgroup with a thread limit that takes half of the quota of the upper parent
  1266  	subgrp2, err := subgrp1.NewSubGroup("thread-sub", quota.NewResourcesBuilder().WithThreadLimit(512).Build())
  1267  	c.Assert(err, IsNil)
  1268  
  1269  	// Create a nested subgroup with a cpu limit that takes up the entire quota of the middle parent
  1270  	_, err = subgrp2.NewSubGroup("cpu-sub2", quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(50).Build())
  1271  	c.Assert(err, IsNil)
  1272  
  1273  	// Now lets inject a thread quota that is less than currently used by children
  1274  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithThreadLimit(256).Build())
  1275  	c.Check(err, ErrorMatches, `group thread limit of 256 is too small to fit current subgroup usage of 512`)
  1276  
  1277  	// Now lets inject one that is larger, that should be possible
  1278  	err = subgrp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithThreadLimit(1024).Build())
  1279  	c.Check(err, IsNil)
  1280  }
  1281  
  1282  func (ts *quotaTestSuite) TestCombinedCpuPercentageWithCpuSetLimits(c *C) {
  1283  	// mock the CPU count to be above 2
  1284  	restore := quota.MockRuntimeNumCPU(func() int { return 4 })
  1285  	defer restore()
  1286  
  1287  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1288  	c.Assert(err, IsNil)
  1289  
  1290  	// Create a subgroup of the CPU set of 0,1 with 50% allowed CPU usage. This should result in a combined
  1291  	// allowance of 100%
  1292  	subgrp1, err := grp1.NewSubGroup("cpu-sub1", quota.NewResourcesBuilder().WithCPUPercentage(50).Build())
  1293  	c.Assert(err, IsNil)
  1294  	c.Check(subgrp1.GetCPUQuotaPercentage(), Equals, 100)
  1295  
  1296  	_, err = grp1.NewSubGroup("cpu-sub2", quota.NewResourcesBuilder().WithCPUCount(8).WithCPUPercentage(50).Build())
  1297  	c.Assert(err, ErrorMatches, `sub-group cpu limit of 400% is too large to fit inside group "groot" with allowed CPU set \[0 1\]`)
  1298  }
  1299  
  1300  func (ts *quotaTestSuite) TestCombinedCpuPercentageWithLowCoreCount(c *C) {
  1301  	// mock the CPU count to be above 1
  1302  	restore := quota.MockRuntimeNumCPU(func() int { return 1 })
  1303  	defer restore()
  1304  
  1305  	grp1, err := quota.NewGroup("groot", quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build())
  1306  	c.Assert(err, IsNil)
  1307  
  1308  	subgrp1, err := grp1.NewSubGroup("cpu-sub1", quota.NewResourcesBuilder().WithCPUPercentage(50).Build())
  1309  	c.Assert(err, IsNil)
  1310  
  1311  	// Even though the CPU set is set to cores 0+1, which technically means that a CPUPercentage of 50 would
  1312  	// be half of this, the CPU percentage is capped at at total of 100% because the number of cores on the system
  1313  	// is 1.
  1314  	c.Check(subgrp1.GetCPUQuotaPercentage(), Equals, 50)
  1315  
  1316  	subgrp2, err := grp1.NewSubGroup("cpu-sub2", quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(50).Build())
  1317  	c.Assert(err, IsNil)
  1318  
  1319  	// Verify that the number of cpus are now correctly reported as the one explicitly set
  1320  	// by the quota
  1321  	c.Check(subgrp2.GetCPUQuotaPercentage(), Equals, 200)
  1322  }
  1323  
  1324  func (ts *quotaTestSuite) TestJournalQuotasSetCorrectly(c *C) {
  1325  	grp1, err := quota.NewGroup("groot1", quota.NewResourcesBuilder().WithJournalNamespace().Build())
  1326  	c.Assert(err, IsNil)
  1327  	c.Assert(grp1.JournalLimit, NotNil)
  1328  
  1329  	grp2, err := quota.NewGroup("groot2", quota.NewResourcesBuilder().WithJournalRate(15, time.Second).Build())
  1330  	c.Assert(err, IsNil)
  1331  	c.Assert(grp2.JournalLimit, NotNil)
  1332  	c.Check(grp2.JournalLimit.RateCount, Equals, 15)
  1333  	c.Check(grp2.JournalLimit.RatePeriod, Equals, time.Second)
  1334  
  1335  	grp3, err := quota.NewGroup("groot3", quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB).Build())
  1336  	c.Assert(err, IsNil)
  1337  	c.Assert(grp3.JournalLimit, NotNil)
  1338  	c.Check(grp3.JournalLimit.Size, Equals, quantity.SizeMiB)
  1339  }
  1340  
  1341  func (ts *quotaTestSuite) TestJournalQuotasUpdatesCorrectly(c *C) {
  1342  	grp1, err := quota.NewGroup("groot1", quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build())
  1343  	c.Assert(err, IsNil)
  1344  	c.Assert(grp1.JournalLimit, IsNil)
  1345  
  1346  	grp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithJournalNamespace().Build())
  1347  	c.Assert(grp1.JournalLimit, NotNil)
  1348  	c.Check(grp1.JournalLimit.Size, Equals, quantity.Size(0))
  1349  	c.Check(grp1.JournalLimit.RateCount, Equals, 0)
  1350  	c.Check(grp1.JournalLimit.RatePeriod, Equals, time.Duration(0))
  1351  
  1352  	grp1.UpdateQuotaLimits(quota.NewResourcesBuilder().WithJournalRate(15, time.Microsecond*5).WithJournalSize(quantity.SizeMiB).Build())
  1353  	c.Assert(grp1.JournalLimit, NotNil)
  1354  	c.Check(grp1.JournalLimit.Size, Equals, quantity.SizeMiB)
  1355  	c.Check(grp1.JournalLimit.RateCount, Equals, 15)
  1356  	c.Check(grp1.JournalLimit.RatePeriod, Equals, time.Microsecond*5)
  1357  }