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 := "a.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 }