github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/overlord/configstate/configcore/vitality_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2020 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 configcore_test 21 22 import ( 23 "path/filepath" 24 "strconv" 25 "strings" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/asserts/assertstest" 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/gadget/quantity" 33 "github.com/snapcore/snapd/overlord/configstate/config" 34 "github.com/snapcore/snapd/overlord/configstate/configcore" 35 "github.com/snapcore/snapd/overlord/servicestate" 36 "github.com/snapcore/snapd/overlord/snapstate" 37 "github.com/snapcore/snapd/overlord/snapstate/snapstatetest" 38 "github.com/snapcore/snapd/snap" 39 "github.com/snapcore/snapd/snap/snaptest" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 type vitalitySuite struct { 44 configcoreSuite 45 } 46 47 var _ = Suite(&vitalitySuite{}) 48 49 func (s *vitalitySuite) SetUpTest(c *C) { 50 s.configcoreSuite.SetUpTest(c) 51 52 uc18model := assertstest.FakeAssertion(map[string]interface{}{ 53 "type": "model", 54 "authority-id": "canonical", 55 "series": "16", 56 "brand-id": "canonical", 57 "model": "pc", 58 "gadget": "pc", 59 "kernel": "kernel", 60 "architecture": "amd64", 61 "base": "core18", 62 }).(*asserts.Model) 63 64 s.AddCleanup(snapstatetest.MockDeviceModel(uc18model)) 65 } 66 67 func (s *vitalitySuite) TestConfigureVitalityUnhappyName(c *C) { 68 err := configcore.Run(&mockConf{ 69 state: s.state, 70 changes: map[string]interface{}{ 71 "resilience.vitality-hint": "-invalid-snap-name!yf", 72 }, 73 }) 74 c.Assert(err, ErrorMatches, `cannot set "resilience.vitality-hint": invalid snap name: ".*"`) 75 } 76 77 func (s *vitalitySuite) TestConfigureVitalityNoSnapd(c *C) { 78 err := configcore.Run(&mockConf{ 79 state: s.state, 80 changes: map[string]interface{}{ 81 "resilience.vitality-hint": "snapd", 82 }, 83 }) 84 c.Assert(err, ErrorMatches, `cannot set "resilience.vitality-hint": snapd snap vitality cannot be changed`) 85 } 86 87 func (s *vitalitySuite) TestConfigureVitalityhappyName(c *C) { 88 err := configcore.Run(&mockConf{ 89 state: s.state, 90 changes: map[string]interface{}{ 91 "resilience.vitality-hint": "valid-snapname", 92 }, 93 }) 94 c.Assert(err, IsNil) 95 // no snap named "valid-snapname" is installed, so no systemd action 96 c.Check(s.systemctlArgs, HasLen, 0) 97 } 98 99 var mockSnapWithService = `name: test-snap 100 version: 1.0 101 apps: 102 foo: 103 daemon: simple 104 ` 105 106 func (s *vitalitySuite) TestConfigureVitalityWithValidSnapUC16(c *C) { 107 uc16model := assertstest.FakeAssertion(map[string]interface{}{ 108 "type": "model", 109 "authority-id": "canonical", 110 "series": "16", 111 "brand-id": "canonical", 112 "model": "pc", 113 "gadget": "pc", 114 "kernel": "kernel", 115 "architecture": "amd64", 116 }).(*asserts.Model) 117 118 defer snapstatetest.MockDeviceModel(uc16model)() 119 120 s.testConfigureVitalityWithValidSnap(c, false) 121 } 122 123 func (s *vitalitySuite) TestConfigureVitalityWithValidSnapUC18(c *C) { 124 s.testConfigureVitalityWithValidSnap(c, true) 125 } 126 127 func (s *vitalitySuite) testConfigureVitalityWithValidSnap(c *C, uc18 bool) { 128 si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)} 129 snaptest.MockSnap(c, mockSnapWithService, si) 130 s.state.Lock() 131 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 132 Sequence: []*snap.SideInfo{si}, 133 Current: snap.R(1), 134 Active: true, 135 SnapType: "app", 136 }) 137 s.state.Unlock() 138 139 err := configcore.Run(&mockConf{ 140 state: s.state, 141 changes: map[string]interface{}{ 142 "resilience.vitality-hint": "unrelated,test-snap", 143 }, 144 }) 145 c.Assert(err, IsNil) 146 svcName := "snap.test-snap.foo.service" 147 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 148 {"daemon-reload"}, 149 {"is-enabled", "snap.test-snap.foo.service"}, 150 {"enable", "snap.test-snap.foo.service"}, 151 {"start", "snap.test-snap.foo.service"}, 152 }) 153 svcPath := filepath.Join(dirs.SnapServicesDir, svcName) 154 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n") 155 if uc18 { 156 c.Check(svcPath, testutil.FileContains, "\nWants=usr-lib-snapd.mount\n") 157 } 158 } 159 160 func (s *vitalitySuite) TestConfigureVitalityWithQuotaGroup(c *C) { 161 r := servicestate.MockSystemdVersion(248) 162 defer r() 163 164 si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)} 165 snaptest.MockSnap(c, mockSnapWithService, si) 166 s.state.Lock() 167 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 168 Sequence: []*snap.SideInfo{si}, 169 Current: snap.R(1), 170 Active: true, 171 SnapType: "app", 172 }) 173 174 tr := config.NewTransaction(s.state) 175 tr.Set("core", "experimental.quota-groups", true) 176 tr.Commit() 177 178 // make a new quota group with this snap in it 179 err := servicestate.CreateQuota(s.state, "foogroup", "", []string{"test-snap"}, quantity.SizeMiB) 180 c.Assert(err, IsNil) 181 182 // CreateQuota uses systemctl, but we don't care about that here 183 s.systemctlArgs = nil 184 185 s.state.Unlock() 186 187 err = configcore.Run(&mockConf{ 188 state: s.state, 189 changes: map[string]interface{}{ 190 "resilience.vitality-hint": "unrelated,test-snap", 191 }, 192 }) 193 c.Assert(err, IsNil) 194 svcName := "snap.test-snap.foo.service" 195 c.Check(s.systemctlArgs, DeepEquals, [][]string{ 196 {"daemon-reload"}, 197 {"is-enabled", "snap.test-snap.foo.service"}, 198 {"enable", "snap.test-snap.foo.service"}, 199 {"start", "snap.test-snap.foo.service"}, 200 }) 201 svcPath := filepath.Join(dirs.SnapServicesDir, svcName) 202 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n") 203 c.Check(svcPath, testutil.FileContains, "\nSlice=snap.foogroup.slice\n") 204 } 205 206 func (s *vitalitySuite) TestConfigureVitalityHintTooMany(c *C) { 207 l := make([]string, 101) 208 for i := range l { 209 l[i] = strconv.Itoa(i) 210 } 211 manyStr := strings.Join(l, ",") 212 err := configcore.Run(&mockConf{ 213 state: s.state, 214 changes: map[string]interface{}{ 215 "resilience.vitality-hint": manyStr, 216 }, 217 }) 218 c.Assert(err, ErrorMatches, `cannot set more than 100 snaps in "resilience.vitality-hint": got 101`) 219 } 220 221 func (s *vitalitySuite) TestConfigureVitalityManySnaps(c *C) { 222 for _, snapName := range []string{"snap1", "snap2", "snap3"} { 223 si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)} 224 snaptest.MockSnap(c, mockSnapWithService, si) 225 s.state.Lock() 226 snapstate.Set(s.state, snapName, &snapstate.SnapState{ 227 Sequence: []*snap.SideInfo{si}, 228 Current: snap.R(1), 229 Active: true, 230 SnapType: "app", 231 }) 232 s.state.Unlock() 233 } 234 235 // snap1,snap2,snap3 236 err := configcore.Run(&mockConf{ 237 state: s.state, 238 changes: map[string]interface{}{ 239 "resilience.vitality-hint": "snap1,snap2,snap3", 240 }, 241 }) 242 c.Assert(err, IsNil) 243 // test 244 svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service") 245 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899\n") 246 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service") 247 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n") 248 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service") 249 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-897\n") 250 } 251 252 func (s *vitalitySuite) TestConfigureVitalityManySnapsDelta(c *C) { 253 for _, snapName := range []string{"snap1", "snap2", "snap3"} { 254 si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)} 255 snaptest.MockSnap(c, mockSnapWithService, si) 256 s.state.Lock() 257 snapstate.Set(s.state, snapName, &snapstate.SnapState{ 258 Sequence: []*snap.SideInfo{si}, 259 Current: snap.R(1), 260 Active: true, 261 SnapType: "app", 262 }) 263 s.state.Unlock() 264 } 265 266 // snap1,snap2,snap3 switch to snap3,snap1 267 err := configcore.Run(&mockConf{ 268 state: s.state, 269 conf: map[string]interface{}{ 270 "resilience.vitality-hint": "snap1,snap2,snap3", 271 }, 272 changes: map[string]interface{}{ 273 "resilience.vitality-hint": "snap3,snap1", 274 }, 275 }) 276 c.Assert(err, IsNil) 277 // test that snap1,snap3 got the new rank 278 svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service") 279 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898") 280 // and that snap2 no longer has a OOMScoreAdjust setting 281 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service") 282 c.Check(svcPath, Not(testutil.FileContains), "\nOOMScoreAdjust=") 283 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service") 284 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899\n") 285 } 286 287 func (s *vitalitySuite) TestConfigureVitalityManySnapsOneRemovedOneUnchanged(c *C) { 288 for _, snapName := range []string{"snap1", "snap2", "snap3"} { 289 si := &snap.SideInfo{RealName: snapName, Revision: snap.R(1)} 290 snaptest.MockSnap(c, mockSnapWithService, si) 291 s.state.Lock() 292 snapstate.Set(s.state, snapName, &snapstate.SnapState{ 293 Sequence: []*snap.SideInfo{si}, 294 Current: snap.R(1), 295 Active: true, 296 SnapType: "app", 297 }) 298 s.state.Unlock() 299 } 300 301 // first run generates the snap1,snap2 configs 302 err := configcore.Run(&mockConf{ 303 state: s.state, 304 changes: map[string]interface{}{ 305 "resilience.vitality-hint": "snap1,snap2", 306 }, 307 }) 308 c.Assert(err, IsNil) 309 svcPath := filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service") 310 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899") 311 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service") 312 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n") 313 c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap1.foo.service"}) 314 c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap2.foo.service"}) 315 s.systemctlArgs = nil 316 317 // now we change the configuration and set snap1,snap3 318 err = configcore.Run(&mockConf{ 319 state: s.state, 320 conf: map[string]interface{}{ 321 "resilience.vitality-hint": "snap1,snap2", 322 }, 323 changes: map[string]interface{}{ 324 "resilience.vitality-hint": "snap1,snap3", 325 }, 326 }) 327 c.Assert(err, IsNil) 328 // test that snap1 is unchanged 329 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap1.foo.service") 330 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-899") 331 // and that snap2 no longer has a OOMScoreAdjust setting 332 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap2.foo.service") 333 c.Check(svcPath, Not(testutil.FileContains), "\nOOMScoreAdjust=") 334 // snap3 got added 335 svcPath = filepath.Join(dirs.SnapServicesDir, "snap.snap3.foo.service") 336 c.Check(svcPath, testutil.FileContains, "\nOOMScoreAdjust=-898\n") 337 338 // ensure that snap1 did not get started again (it is unchanged) 339 c.Check(s.systemctlArgs, Not(testutil.DeepContains), []string{"start", "snap.snap1.foo.service"}) 340 // snap2 changed (no OOMScoreAdjust anymore) so needs restart 341 c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap2.foo.service"}) 342 // snap3 changed so needs restart 343 c.Check(s.systemctlArgs, testutil.DeepContains, []string{"start", "snap.snap3.foo.service"}) 344 } 345 346 func (s *vitalitySuite) TestConfigureVitalityNotActiveSnap(c *C) { 347 si := &snap.SideInfo{RealName: "test-snap", Revision: snap.R(1)} 348 snaptest.MockSnap(c, mockSnapWithService, si) 349 s.state.Lock() 350 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 351 Sequence: []*snap.SideInfo{si}, 352 Current: snap.R(1), 353 Active: false, 354 SnapType: "app", 355 }) 356 s.state.Unlock() 357 358 err := configcore.Run(&mockConf{ 359 state: s.state, 360 changes: map[string]interface{}{ 361 "resilience.vitality-hint": "unrelated,test-snap", 362 }, 363 }) 364 c.Assert(err, IsNil) 365 c.Check(s.systemctlArgs, HasLen, 0) 366 }