gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/servicestate/internal/quotas_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 internal_test 21 22 import ( 23 "testing" 24 25 . "gopkg.in/check.v1" 26 27 "github.com/snapcore/snapd/gadget/quantity" 28 "github.com/snapcore/snapd/overlord/servicestate/internal" 29 "github.com/snapcore/snapd/overlord/state" 30 "github.com/snapcore/snapd/snap/quota" 31 ) 32 33 func TestInternal(t *testing.T) { TestingT(t) } 34 35 type servicestateQuotasSuite struct { 36 state *state.State 37 } 38 39 var _ = Suite(&servicestateQuotasSuite{}) 40 41 func (s *servicestateQuotasSuite) SetUpTest(c *C) { 42 s.state = state.New(nil) 43 } 44 45 func (s *servicestateQuotasSuite) TestQuotas(c *C) { 46 st := s.state 47 st.Lock() 48 defer st.Unlock() 49 50 // with nothing in state we don't get any quotas 51 quotaMap, err := internal.AllQuotas(st) 52 c.Assert(err, IsNil) 53 c.Assert(quotaMap, HasLen, 0) 54 55 // we can add some basic quotas to state 56 grp := "a.Group{ 57 Name: "foogroup", 58 MemoryLimit: quantity.SizeGiB, 59 } 60 newGrps, err := internal.PatchQuotas(st, grp) 61 c.Assert(err, IsNil) 62 c.Assert(newGrps, DeepEquals, map[string]*quota.Group{ 63 "foogroup": grp, 64 }) 65 66 // now we get back the same quota 67 quotaMap, err = internal.AllQuotas(st) 68 c.Assert(err, IsNil) 69 c.Assert(quotaMap, DeepEquals, map[string]*quota.Group{ 70 "foogroup": grp, 71 }) 72 73 // adding a sub-group quota only works when we update the parent group to 74 // reference the sub-group at the same time 75 grp2 := "a.Group{ 76 Name: "group-2", 77 MemoryLimit: quantity.SizeGiB, 78 ParentGroup: "foogroup", 79 } 80 _, err = internal.PatchQuotas(st, grp2) 81 c.Assert(err, ErrorMatches, `cannot update quota "group-2": group "foogroup" does not reference necessary child group "group-2"`) 82 83 // we also can't add a sub-group to the parent without adding the sub-group 84 // itself 85 grp.SubGroups = append(grp.SubGroups, "group-2") 86 _, err = internal.PatchQuotas(st, grp) 87 c.Assert(err, ErrorMatches, `cannot update quota "foogroup": missing group "group-2" referenced as the sub-group of group "foogroup"`) 88 89 // foogroup didn't get updated in the state to mention the sub-group 90 quotaMap, err = internal.AllQuotas(st) 91 c.Assert(err, IsNil) 92 foogrp, ok := quotaMap["foogroup"] 93 c.Assert(ok, Equals, true) 94 c.Assert(foogrp.SubGroups, HasLen, 0) 95 96 // but if we update them both at the same time we succeed 97 newGrps, err = internal.PatchQuotas(st, grp, grp2) 98 c.Assert(err, IsNil) 99 c.Assert(newGrps, DeepEquals, map[string]*quota.Group{ 100 "foogroup": grp, 101 "group-2": grp2, 102 }) 103 104 // and now we see both in the state 105 quotaMap, err = internal.AllQuotas(st) 106 c.Assert(err, IsNil) 107 c.Assert(quotaMap, DeepEquals, map[string]*quota.Group{ 108 "foogroup": grp, 109 "group-2": grp2, 110 }) 111 112 // adding multiple quotas that are invalid produces a nice error message 113 otherGrp := "a.Group{ 114 Name: "other-group", 115 // invalid memory limit 116 } 117 118 otherGrp2 := "a.Group{ 119 Name: "other-group2", 120 // invalid memory limit 121 } 122 123 _, err = internal.PatchQuotas(st, otherGrp2, otherGrp) 124 // either group can get checked first 125 c.Assert(err, ErrorMatches, `cannot update quotas "other-group", "other-group2": group "other-group2?" is invalid: group memory limit must be non-zero`) 126 } 127 128 func (s *servicestateQuotasSuite) TestCreateQuotaInState(c *C) { 129 st := s.state 130 st.Lock() 131 defer st.Unlock() 132 133 // we can create a basic quota in state 134 grp := "a.Group{ 135 Name: "foogroup", 136 MemoryLimit: quantity.SizeGiB, 137 } 138 grp1, newGrps, err := internal.CreateQuotaInState(st, "foogroup", nil, nil, quantity.SizeGiB, nil) 139 c.Assert(err, IsNil) 140 c.Check(grp1, DeepEquals, grp) 141 c.Check(newGrps, DeepEquals, map[string]*quota.Group{ 142 "foogroup": grp, 143 }) 144 145 // now quota is really in state 146 quotaMap, err := internal.AllQuotas(st) 147 c.Assert(err, IsNil) 148 c.Assert(quotaMap, DeepEquals, map[string]*quota.Group{ 149 "foogroup": grp, 150 }) 151 152 // create a sub-group quota 153 grp2 := "a.Group{ 154 Name: "group-2", 155 MemoryLimit: quantity.SizeGiB, 156 ParentGroup: "foogroup", 157 Snaps: []string{"snap1", "snap2"}, 158 } 159 grp3, newGrps, err := internal.CreateQuotaInState(st, "group-2", grp1, []string{"snap1", "snap2"}, quantity.SizeGiB, nil) 160 c.Assert(err, IsNil) 161 c.Check(grp3.Name, Equals, grp2.Name) 162 c.Check(grp3.MemoryLimit, Equals, grp2.MemoryLimit) 163 c.Check(grp3.ParentGroup, Equals, grp2.ParentGroup) 164 c.Check(grp3.Snaps, DeepEquals, grp2.Snaps) 165 c.Check(newGrps, HasLen, 2) 166 c.Check(newGrps["foogroup"].SubGroups, DeepEquals, []string{"group-2"}) 167 168 // and now we see both in the state 169 quotaMap, err = internal.AllQuotas(st) 170 c.Assert(err, IsNil) 171 c.Assert(quotaMap, DeepEquals, map[string]*quota.Group{ 172 "foogroup": newGrps["foogroup"], 173 "group-2": grp3, 174 }) 175 }