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