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