gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/servicestate/servicestate_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-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 servicestate_test 21 22 import ( 23 "bytes" 24 "fmt" 25 "os" 26 "path/filepath" 27 "strings" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/client" 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/gadget/quantity" 34 "github.com/snapcore/snapd/overlord/configstate/config" 35 "github.com/snapcore/snapd/overlord/servicestate" 36 "github.com/snapcore/snapd/overlord/servicestate/servicestatetest" 37 "github.com/snapcore/snapd/overlord/state" 38 "github.com/snapcore/snapd/snap" 39 "github.com/snapcore/snapd/snap/quota" 40 "github.com/snapcore/snapd/systemd" 41 "github.com/snapcore/snapd/testutil" 42 "github.com/snapcore/snapd/wrappers" 43 ) 44 45 type statusDecoratorSuite struct{} 46 47 var _ = Suite(&statusDecoratorSuite{}) 48 49 func (s *statusDecoratorSuite) TestDecorateWithStatus(c *C) { 50 dirs.SetRootDir(c.MkDir()) 51 defer dirs.SetRootDir("") 52 snp := &snap.Info{ 53 SideInfo: snap.SideInfo{ 54 RealName: "foo", 55 Revision: snap.R(1), 56 }, 57 } 58 err := os.MkdirAll(snp.MountDir(), 0755) 59 c.Assert(err, IsNil) 60 err = os.Symlink(snp.Revision.String(), filepath.Join(filepath.Dir(snp.MountDir()), "current")) 61 c.Assert(err, IsNil) 62 63 disabled := false 64 r := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) { 65 switch args[0] { 66 case "show": 67 c.Assert(args[0], Equals, "show") 68 unit := args[2] 69 activeState, unitState := "active", "enabled" 70 if disabled { 71 activeState = "inactive" 72 unitState = "disabled" 73 } 74 if strings.HasSuffix(unit, ".timer") || strings.HasSuffix(unit, ".socket") { 75 return []byte(fmt.Sprintf(`Id=%s 76 ActiveState=%s 77 UnitFileState=%s 78 `, args[2], activeState, unitState)), nil 79 } else { 80 return []byte(fmt.Sprintf(`Id=%s 81 Type=simple 82 ActiveState=%s 83 UnitFileState=%s 84 `, args[2], activeState, unitState)), nil 85 } 86 case "--user": 87 c.Assert(args[1], Equals, "--global") 88 c.Assert(args[2], Equals, "is-enabled") 89 unitState := "enabled\n" 90 if disabled { 91 unitState = "disabled\n" 92 } 93 return bytes.Repeat([]byte(unitState), len(args)-3), nil 94 default: 95 c.Errorf("unexpected systemctl command: %v", args) 96 return nil, fmt.Errorf("should not be reached") 97 } 98 }) 99 defer r() 100 101 sd := servicestate.NewStatusDecorator(nil) 102 103 // not a service 104 app := &client.AppInfo{ 105 Snap: "foo", 106 Name: "app", 107 } 108 snapApp := &snap.AppInfo{Snap: snp, Name: "app"} 109 110 err = sd.DecorateWithStatus(app, snapApp) 111 c.Assert(err, IsNil) 112 113 for _, enabled := range []bool{true, false} { 114 disabled = !enabled 115 116 // service only 117 app = &client.AppInfo{ 118 Snap: snp.InstanceName(), 119 Name: "svc", 120 Daemon: "simple", 121 } 122 snapApp = &snap.AppInfo{ 123 Snap: snp, 124 Name: "svc", 125 Daemon: "simple", 126 DaemonScope: snap.SystemDaemon, 127 } 128 129 err = sd.DecorateWithStatus(app, snapApp) 130 c.Assert(err, IsNil) 131 c.Check(app.Active, Equals, enabled) 132 c.Check(app.Enabled, Equals, enabled) 133 134 // service + timer 135 app = &client.AppInfo{ 136 Snap: snp.InstanceName(), 137 Name: "svc", 138 Daemon: "simple", 139 } 140 snapApp = &snap.AppInfo{ 141 Snap: snp, 142 Name: "svc", 143 Daemon: "simple", 144 DaemonScope: snap.SystemDaemon, 145 } 146 snapApp.Timer = &snap.TimerInfo{ 147 App: snapApp, 148 Timer: "10:00", 149 } 150 151 err = sd.DecorateWithStatus(app, snapApp) 152 c.Assert(err, IsNil) 153 c.Check(app.Active, Equals, enabled) 154 c.Check(app.Enabled, Equals, enabled) 155 c.Check(app.Activators, DeepEquals, []client.AppActivator{ 156 {Name: "svc", Type: "timer", Active: enabled, Enabled: enabled}, 157 }) 158 159 // service with socket 160 app = &client.AppInfo{ 161 Snap: snp.InstanceName(), 162 Name: "svc", 163 Daemon: "simple", 164 } 165 snapApp = &snap.AppInfo{ 166 Snap: snp, 167 Name: "svc", 168 Daemon: "simple", 169 DaemonScope: snap.SystemDaemon, 170 } 171 snapApp.Sockets = map[string]*snap.SocketInfo{ 172 "socket1": { 173 App: snapApp, 174 Name: "socket1", 175 ListenStream: "a.socket", 176 }, 177 } 178 179 err = sd.DecorateWithStatus(app, snapApp) 180 c.Assert(err, IsNil) 181 c.Check(app.Active, Equals, enabled) 182 c.Check(app.Enabled, Equals, enabled) 183 c.Check(app.Activators, DeepEquals, []client.AppActivator{ 184 {Name: "socket1", Type: "socket", Active: enabled, Enabled: enabled}, 185 }) 186 187 // service with D-Bus activation 188 app = &client.AppInfo{ 189 Snap: snp.InstanceName(), 190 Name: "svc", 191 Daemon: "simple", 192 } 193 snapApp = &snap.AppInfo{ 194 Snap: snp, 195 Name: "svc", 196 Daemon: "simple", 197 DaemonScope: snap.SystemDaemon, 198 } 199 snapApp.ActivatesOn = []*snap.SlotInfo{ 200 { 201 Snap: snp, 202 Name: "dbus-slot", 203 Interface: "dbus", 204 Attrs: map[string]interface{}{ 205 "bus": "system", 206 "name": "org.example.Svc", 207 }, 208 }, 209 } 210 211 err = sd.DecorateWithStatus(app, snapApp) 212 c.Assert(err, IsNil) 213 c.Check(app.Active, Equals, enabled) 214 c.Check(app.Enabled, Equals, enabled) 215 c.Check(app.Activators, DeepEquals, []client.AppActivator{ 216 {Name: "org.example.Svc", Type: "dbus", Active: true, Enabled: true}, 217 }) 218 219 // No state is currently extracted for user daemons 220 app = &client.AppInfo{ 221 Snap: snp.InstanceName(), 222 Name: "svc", 223 Daemon: "simple", 224 } 225 snapApp = &snap.AppInfo{ 226 Snap: snp, 227 Name: "svc", 228 Daemon: "simple", 229 DaemonScope: snap.UserDaemon, 230 } 231 snapApp.Sockets = map[string]*snap.SocketInfo{ 232 "socket1": { 233 App: snapApp, 234 Name: "socket1", 235 ListenStream: "a.socket", 236 }, 237 } 238 snapApp.Timer = &snap.TimerInfo{ 239 App: snapApp, 240 Timer: "10:00", 241 } 242 snapApp.ActivatesOn = []*snap.SlotInfo{ 243 { 244 Snap: snp, 245 Name: "dbus-slot", 246 Interface: "dbus", 247 Attrs: map[string]interface{}{ 248 "bus": "session", 249 "name": "org.example.Svc", 250 }, 251 }, 252 } 253 254 err = sd.DecorateWithStatus(app, snapApp) 255 c.Assert(err, IsNil) 256 c.Check(app.Active, Equals, false) 257 c.Check(app.Enabled, Equals, enabled) 258 c.Check(app.Activators, DeepEquals, []client.AppActivator{ 259 {Name: "socket1", Type: "socket", Active: false, Enabled: enabled}, 260 {Name: "svc", Type: "timer", Active: false, Enabled: enabled}, 261 {Name: "org.example.Svc", Type: "dbus", Active: true, Enabled: true}, 262 }) 263 } 264 } 265 266 type snapServiceOptionsSuite struct { 267 testutil.BaseTest 268 state *state.State 269 } 270 271 var _ = Suite(&snapServiceOptionsSuite{}) 272 273 func (s *snapServiceOptionsSuite) SetUpTest(c *C) { 274 s.BaseTest.SetUpTest(c) 275 s.state = state.New(nil) 276 } 277 278 func (s *snapServiceOptionsSuite) TestSnapServiceOptionsVitalityRank(c *C) { 279 st := s.state 280 st.Lock() 281 defer st.Unlock() 282 t := config.NewTransaction(st) 283 err := t.Set("core", "resilience.vitality-hint", "bar,foo") 284 c.Assert(err, IsNil) 285 t.Commit() 286 287 opts, err := servicestate.SnapServiceOptions(st, "foo", nil) 288 c.Assert(err, IsNil) 289 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 290 VitalityRank: 2, 291 }) 292 opts, err = servicestate.SnapServiceOptions(st, "bar", nil) 293 c.Assert(err, IsNil) 294 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 295 VitalityRank: 1, 296 }) 297 opts, err = servicestate.SnapServiceOptions(st, "unknown", nil) 298 c.Assert(err, IsNil) 299 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 300 VitalityRank: 0, 301 }) 302 } 303 304 func (s *snapServiceOptionsSuite) TestSnapServiceOptionsQuotaGroups(c *C) { 305 st := s.state 306 st.Lock() 307 defer st.Unlock() 308 309 // make a quota group 310 grp, err := quota.NewGroup("foogroup", quantity.SizeGiB) 311 c.Assert(err, IsNil) 312 313 grp.Snaps = []string{"foosnap"} 314 315 // add it into the state 316 newGrps, err := servicestatetest.PatchQuotas(st, grp) 317 c.Assert(err, IsNil) 318 c.Assert(newGrps, DeepEquals, map[string]*quota.Group{ 319 "foogroup": grp, 320 }) 321 322 opts, err := servicestate.SnapServiceOptions(st, "foosnap", nil) 323 c.Assert(err, IsNil) 324 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 325 QuotaGroup: grp, 326 }) 327 328 // save the current state of the quota group before modifying it to prove 329 // that the group caching works 330 grps, err := servicestate.AllQuotas(st) 331 c.Assert(err, IsNil) 332 333 // modify state to use an instance name instead now 334 grp.Snaps = []string{"foosnap_instance"} 335 newGrps, err = servicestatetest.PatchQuotas(st, grp) 336 c.Assert(err, IsNil) 337 c.Assert(newGrps, DeepEquals, map[string]*quota.Group{ 338 "foogroup": grp, 339 }) 340 341 // we can still get the quota group using the local map we got before 342 // modifying state 343 opts, err = servicestate.SnapServiceOptions(st, "foosnap", grps) 344 c.Assert(err, IsNil) 345 grp.Snaps = []string{"foosnap"} 346 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 347 QuotaGroup: grp, 348 }) 349 350 // but using state produces nothing for the non-instance name snap 351 opts, err = servicestate.SnapServiceOptions(st, "foosnap", nil) 352 c.Assert(err, IsNil) 353 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{}) 354 355 // but it does work with instance names 356 grp.Snaps = []string{"foosnap_instance"} 357 opts, err = servicestate.SnapServiceOptions(st, "foosnap_instance", nil) 358 c.Assert(err, IsNil) 359 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 360 QuotaGroup: grp, 361 }) 362 363 // works with vitality rank for the snap too 364 t := config.NewTransaction(st) 365 err = t.Set("core", "resilience.vitality-hint", "bar,foosnap_instance") 366 c.Assert(err, IsNil) 367 t.Commit() 368 369 opts, err = servicestate.SnapServiceOptions(st, "foosnap_instance", nil) 370 c.Assert(err, IsNil) 371 c.Check(opts, DeepEquals, &wrappers.SnapServiceOptions{ 372 VitalityRank: 2, 373 QuotaGroup: grp, 374 }) 375 }