gitee.com/mysnapcore/mysnapd@v0.1.0/snap/quota/resources_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  	"reflect"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"gitee.com/mysnapcore/mysnapd/gadget/quantity"
    30  	"gitee.com/mysnapcore/mysnapd/snap/quota"
    31  )
    32  
    33  type resourcesTestSuite struct{}
    34  
    35  var _ = Suite(&resourcesTestSuite{})
    36  
    37  func (s *resourcesTestSuite) TestQuotaValidationFails(c *C) {
    38  	tests := []struct {
    39  		limits quota.Resources
    40  		err    string
    41  	}{
    42  		{quota.NewResourcesBuilder().Build(), `quota group must have at least one resource limit set`},
    43  		{quota.NewResourcesBuilder().WithMemoryLimit(0).Build(), `memory quota must have a limit set`},
    44  		{quota.NewResourcesBuilder().WithCPUPercentage(0).Build(), `invalid cpu quota with a cpu quota of 0`},
    45  		{quota.NewResourcesBuilder().WithCPUSet(nil).Build(), `cpu-set quota must not be empty`},
    46  		{quota.NewResourcesBuilder().WithThreadLimit(0).Build(), `invalid thread quota with a thread count of 0`},
    47  		{quota.NewResourcesBuilder().Build(), `quota group must have at least one resource limit set`},
    48  		{quota.NewResourcesBuilder().WithCPUCount(1).Build(), `invalid cpu quota with count of >0 and percentage of 0`},
    49  		{quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(100).WithCPUSet([]int{0}).Build(), `cpu usage 200% is larger than the maximum allowed for provided set \[0\] of 100%`},
    50  		{quota.NewResourcesBuilder().WithJournalRate(0, 1).Build(), `journal quota must have a period of at least 1 microsecond \(minimum resolution\)`},
    51  		{quota.NewResourcesBuilder().WithJournalRate(1, time.Nanosecond).Build(), `journal quota must have a period of at least 1 microsecond \(minimum resolution\)`},
    52  		{quota.NewResourcesBuilder().WithJournalSize(0).Build(), `journal size quota must have a limit set`},
    53  	}
    54  
    55  	for _, t := range tests {
    56  		err := t.limits.Validate()
    57  		c.Check(err, ErrorMatches, t.err)
    58  	}
    59  }
    60  
    61  func (s *resourcesTestSuite) TestResourceCheckFeatureRequirementsCgroupv1(c *C) {
    62  	r := quota.MockCgroupVer(1)
    63  	defer r()
    64  
    65  	// normal cpu resource is fine with cgroup v1
    66  	good := quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build()
    67  	c.Check(good.Validate(), IsNil)
    68  
    69  	// cpu set with cgroup v1 is not supported
    70  	bad := quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build()
    71  	c.Check(bad.CheckFeatureRequirements(), ErrorMatches, "cannot use CPU set with cgroup version 1")
    72  }
    73  
    74  func (s *resourcesTestSuite) TestResourceCheckFeatureRequirementsCgroupv1Err(c *C) {
    75  	r := quota.MockCgroupVerErr(fmt.Errorf("some cgroup detection error"))
    76  	defer r()
    77  
    78  	// normal cpu resource is fine
    79  	good := quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build()
    80  	c.Check(good.Validate(), IsNil)
    81  
    82  	// cpu set without cgroup detection is not supported
    83  	bad := quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build()
    84  	c.Check(bad.CheckFeatureRequirements(), ErrorMatches, "some cgroup detection error")
    85  }
    86  
    87  func (s *resourcesTestSuite) TestQuotaValidationPasses(c *C) {
    88  	tests := []struct {
    89  		limits quota.Resources
    90  	}{
    91  		{quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build()},
    92  		{quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build()},
    93  		{quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build()},
    94  		{quota.NewResourcesBuilder().WithThreadLimit(16).Build()},
    95  		{quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB).Build()},
    96  		{quota.NewResourcesBuilder().WithJournalRate(1, time.Microsecond).Build()},
    97  		{quota.NewResourcesBuilder().WithJournalNamespace().Build()},
    98  	}
    99  
   100  	for _, t := range tests {
   101  		err := t.limits.Validate()
   102  		c.Check(err, IsNil)
   103  	}
   104  }
   105  
   106  func (s *resourcesTestSuite) TestQuotaChangeValidationFails(c *C) {
   107  	tests := []struct {
   108  		limits       quota.Resources
   109  		updateLimits quota.Resources
   110  		err          string
   111  	}{
   112  		{
   113  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   114  			quota.NewResourcesBuilder().WithMemoryLimit(0).Build(),
   115  			`cannot remove memory limit from quota group`,
   116  		},
   117  		{
   118  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   119  			quota.NewResourcesBuilder().WithMemoryLimit(5 * quantity.SizeKiB).Build(),
   120  			`memory limit 5120 is too small: size must be larger than 640 KiB`,
   121  		},
   122  		{
   123  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   124  			quota.NewResourcesBuilder().WithMemoryLimit(800 * quantity.SizeKiB).Build(),
   125  			`cannot decrease memory limit, remove and re-create it to decrease the limit`,
   126  		},
   127  		{
   128  			quota.NewResourcesBuilder().WithThreadLimit(64).Build(),
   129  			quota.NewResourcesBuilder().WithThreadLimit(0).Build(),
   130  			`cannot remove thread limit from quota group`,
   131  		},
   132  		{
   133  			quota.NewResourcesBuilder().WithThreadLimit(64).Build(),
   134  			quota.NewResourcesBuilder().WithThreadLimit(32).Build(),
   135  			`cannot decrease thread limit, remove and re-create it to decrease the limit`,
   136  		},
   137  		{
   138  			quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(75).Build(),
   139  			quota.NewResourcesBuilder().WithCPUPercentage(0).Build(),
   140  			`cannot remove cpu limit from quota group`,
   141  		},
   142  		{
   143  			quota.NewResourcesBuilder().WithThreadLimit(16).WithCPUSet([]int{0, 1}).Build(),
   144  			quota.NewResourcesBuilder().WithCPUSet([]int{}).Build(),
   145  			`cannot remove all allowed cpus from quota group`,
   146  		},
   147  		// ensure that changes will call "Validate" too
   148  		{
   149  			quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build(),
   150  			quota.NewResourcesBuilder().WithMemoryLimit(1).Build(),
   151  			`memory limit 1 is too small: size must be larger than 640 KiB`,
   152  		},
   153  		{
   154  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   155  			quota.NewResourcesBuilder().WithCPUCount(0).Build(),
   156  			`invalid cpu quota with a cpu quota of 0`,
   157  		},
   158  		{
   159  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   160  			quota.NewResourcesBuilder().WithCPUCount(2).WithCPUPercentage(0).Build(),
   161  			`invalid cpu quota with count of >0 and percentage of 0`,
   162  		},
   163  		{
   164  			quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build(),
   165  			quota.NewResourcesBuilder().WithCPUCount(8).WithCPUPercentage(100).Build(),
   166  			`cpu usage 800% is larger than the maximum allowed for provided set \[0 1\] of 200%`,
   167  		},
   168  		{
   169  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   170  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(100).WithCPUSet([]int{0, 1}).Build(),
   171  			`cpu usage 400% is larger than the maximum allowed for provided set \[0 1\] of 200%`,
   172  		},
   173  		{
   174  			quota.NewResourcesBuilder().WithCPUCount(6).WithCPUPercentage(100).Build(),
   175  			quota.NewResourcesBuilder().WithCPUSet([]int{0, 1}).Build(),
   176  			`cpu usage 600% is larger than the maximum allowed for provided set \[0 1\] of 200%`,
   177  		},
   178  		{
   179  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   180  			quota.NewResourcesBuilder().WithCPUSet([]int{}).Build(),
   181  			`cpu-set quota must not be empty`,
   182  		},
   183  		{
   184  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   185  			quota.NewResourcesBuilder().WithThreadLimit(-1).Build(),
   186  			`invalid thread quota with a thread count of -1`,
   187  		},
   188  		{
   189  			quota.NewResourcesBuilder().WithJournalRate(1, 1).Build(),
   190  			quota.NewResourcesBuilder().WithJournalRate(-2, 0).Build(),
   191  			`journal quota must have a rate count equal to or larger than zero`,
   192  		},
   193  		{
   194  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).Build(),
   195  			quota.NewResourcesBuilder().WithJournalSize(0).Build(),
   196  			`cannot remove journal size limit from quota group`,
   197  		},
   198  		{
   199  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB).Build(),
   200  			quota.NewResourcesBuilder().WithJournalSize(5 * quantity.SizeKiB).Build(),
   201  			`journal size limit 5120 is too small: size must be larger than 64 KiB`,
   202  		},
   203  		{
   204  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB).Build(),
   205  			quota.NewResourcesBuilder().WithJournalSize(5 * quantity.SizeGiB).Build(),
   206  			`journal size quota must be smaller than 4 GiB`,
   207  		},
   208  	}
   209  
   210  	for _, t := range tests {
   211  		err := t.limits.Change(t.updateLimits)
   212  		c.Check(err, ErrorMatches, t.err)
   213  	}
   214  }
   215  
   216  func (s *resourcesTestSuite) TestQuotaChangeValidationPasses(c *C) {
   217  	tests := []struct {
   218  		limits       quota.Resources
   219  		updateLimits quota.Resources
   220  
   221  		// this is not strictly necessary as we only have one limit right now
   222  		// but as we add additional limits, the updateLimits will not
   223  		// equal limits or newLimits as it can contain partial updates.
   224  		newLimits quota.Resources
   225  	}{
   226  		{
   227  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   228  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(),
   229  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).Build(),
   230  		},
   231  		{
   232  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).Build(),
   233  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).Build(),
   234  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(4).WithCPUPercentage(25).Build(),
   235  		},
   236  		{
   237  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(4).WithCPUPercentage(25).Build(),
   238  			quota.NewResourcesBuilder().WithCPUSet([]int{0}).Build(),
   239  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0}).Build(),
   240  		},
   241  		{
   242  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0}).Build(),
   243  			quota.NewResourcesBuilder().WithThreadLimit(128).Build(),
   244  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeMiB).WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0}).WithThreadLimit(128).Build(),
   245  		},
   246  		{
   247  			quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(100).Build(),
   248  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).WithThreadLimit(32).Build(),
   249  			quota.NewResourcesBuilder().WithMemoryLimit(quantity.SizeGiB).WithCPUCount(1).WithCPUPercentage(100).WithThreadLimit(32).Build(),
   250  		},
   251  		{
   252  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).Build(),
   253  			quota.NewResourcesBuilder().WithCPUPercentage(25).Build(),
   254  			quota.NewResourcesBuilder().WithCPUCount(0).WithCPUPercentage(25).Build(),
   255  		},
   256  		{
   257  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0}).Build(),
   258  			quota.NewResourcesBuilder().WithCPUSet([]int{0, 1, 2}).Build(),
   259  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0, 1, 2}).Build(),
   260  		},
   261  		{
   262  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0, 1, 2}).Build(),
   263  			quota.NewResourcesBuilder().WithCPUSet([]int{0}).Build(),
   264  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0}).Build(),
   265  		},
   266  		{
   267  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithCPUSet([]int{0, 1, 2}).Build(),
   268  			quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).Build(),
   269  			quota.NewResourcesBuilder().WithCPUCount(1).WithCPUPercentage(50).WithCPUSet([]int{0, 1, 2}).Build(),
   270  		},
   271  		{
   272  			quota.NewResourcesBuilder().WithJournalRate(15, 5*time.Second).Build(),
   273  			quota.NewResourcesBuilder().WithJournalRate(5, 5*time.Second).Build(),
   274  			quota.NewResourcesBuilder().WithJournalRate(5, 5*time.Second).Build(),
   275  		},
   276  		{
   277  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).Build(),
   278  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB).Build(),
   279  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeMiB).Build(),
   280  		},
   281  		{
   282  			quota.NewResourcesBuilder().WithJournalRate(15, 5*time.Second).Build(),
   283  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).Build(),
   284  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).WithJournalRate(15, 5*time.Second).Build(),
   285  		},
   286  		{
   287  			quota.NewResourcesBuilder().WithJournalRate(0, 0).Build(),
   288  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).Build(),
   289  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).WithJournalRate(0, 0).Build(),
   290  		},
   291  		{
   292  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).Build(),
   293  			quota.NewResourcesBuilder().WithJournalSize(quantity.SizeGiB).Build(),
   294  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithJournalSize(quantity.SizeGiB).Build(),
   295  		},
   296  		{
   297  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).Build(),
   298  			quota.NewResourcesBuilder().WithJournalRate(15, time.Second).Build(),
   299  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithJournalRate(15, time.Second).Build(),
   300  		},
   301  		{
   302  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).Build(),
   303  			quota.NewResourcesBuilder().WithJournalNamespace().Build(),
   304  			quota.NewResourcesBuilder().WithCPUCount(4).WithCPUPercentage(25).WithJournalNamespace().Build(),
   305  		},
   306  	}
   307  
   308  	for _, t := range tests {
   309  		err := t.limits.Change(t.updateLimits)
   310  		c.Check(err, IsNil)
   311  		c.Check(t.limits, DeepEquals, t.newLimits)
   312  	}
   313  }
   314  
   315  func (s *resourcesTestSuite) TestResourceCloneComplete(c *C) {
   316  	r := &quota.Resources{}
   317  	rv := reflect.ValueOf(r).Elem()
   318  	fieldPtrs := make([]uintptr, rv.NumField())
   319  	for i := 0; i < rv.NumField(); i++ {
   320  		fv := rv.Field(i)
   321  		ft := fv.Type()
   322  		nv := reflect.New(ft.Elem())
   323  		fv.Set(nv)
   324  		fieldPtrs[i] = fv.Pointer()
   325  	}
   326  
   327  	// Clone the resource and ensure there are no un-initialized
   328  	// fields in the resource after the clone. Also check that the
   329  	// fields pointers changed too (ensure the sub-struct really
   330  	// got copied not just assigned to the clone)
   331  	r2 := quota.ResourcesClone(r)
   332  	rv = reflect.ValueOf(r2)
   333  	for i := 0; i < rv.NumField(); i++ {
   334  		fv := rv.Field(i)
   335  		c.Check(fv.IsNil(), Equals, false)
   336  		c.Check(fv.Pointer(), Not(Equals), fieldPtrs[i])
   337  	}
   338  }
   339  
   340  func (s *resourcesTestSuite) TestResourceBuilerWithJournalNamespaceOnly(c *C) {
   341  	r := quota.NewResourcesBuilder().WithJournalNamespace().Build()
   342  	c.Assert(r.Journal, NotNil)
   343  	c.Check(r.Journal.Rate, IsNil)
   344  	c.Check(r.Journal.Size, IsNil)
   345  }