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