github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/hookstate/ctlcmd/services_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 ctlcmd_test 21 22 import ( 23 "context" 24 "fmt" 25 "sort" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/client" 30 "github.com/snapcore/snapd/dirs" 31 "github.com/snapcore/snapd/overlord/auth" 32 "github.com/snapcore/snapd/overlord/hookstate" 33 "github.com/snapcore/snapd/overlord/hookstate/ctlcmd" 34 "github.com/snapcore/snapd/overlord/hookstate/hooktest" 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/overlord/state" 39 "github.com/snapcore/snapd/snap" 40 "github.com/snapcore/snapd/snap/snaptest" 41 "github.com/snapcore/snapd/store" 42 "github.com/snapcore/snapd/store/storetest" 43 "github.com/snapcore/snapd/systemd" 44 "github.com/snapcore/snapd/testutil" 45 ) 46 47 type fakeStore struct { 48 storetest.Store 49 } 50 51 func (f *fakeStore) SnapAction(_ context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) { 52 if assertQuery != nil { 53 panic("no assertion query support") 54 } 55 if actions[0].Action == "install" { 56 installs := make([]store.SnapActionResult, 0, len(actions)) 57 for _, a := range actions { 58 snapName, instanceKey := snap.SplitInstanceName(a.InstanceName) 59 if instanceKey != "" { 60 panic(fmt.Sprintf("unexpected instance name %q in snap install action", a.InstanceName)) 61 } 62 63 installs = append(installs, store.SnapActionResult{Info: &snap.Info{ 64 DownloadInfo: snap.DownloadInfo{ 65 Size: 1, 66 }, 67 SideInfo: snap.SideInfo{ 68 RealName: snapName, 69 Revision: snap.R(2), 70 }, 71 Architectures: []string{"all"}, 72 }}) 73 } 74 75 return installs, nil, nil 76 } 77 78 snaps := []store.SnapActionResult{{Info: &snap.Info{ 79 SideInfo: snap.SideInfo{ 80 RealName: "test-snap", 81 Revision: snap.R(2), 82 SnapID: "test-snap-id", 83 }, 84 Architectures: []string{"all"}, 85 }}, {Info: &snap.Info{ 86 SideInfo: snap.SideInfo{ 87 RealName: "other-snap", 88 Revision: snap.R(2), 89 SnapID: "other-snap-id", 90 }, 91 Architectures: []string{"all"}, 92 }}} 93 return snaps, nil, nil 94 } 95 96 type servicectlSuite struct { 97 testutil.BaseTest 98 st *state.State 99 fakeStore fakeStore 100 mockContext *hookstate.Context 101 mockHandler *hooktest.MockHandler 102 } 103 104 var _ = Suite(&servicectlSuite{}) 105 106 const testSnapYaml = `name: test-snap 107 version: 1.0 108 summary: test-snap 109 apps: 110 normal-app: 111 command: bin/dummy 112 test-service: 113 command: bin/service 114 daemon: simple 115 reload-command: bin/reload 116 another-service: 117 command: bin/service 118 daemon: simple 119 reload-command: bin/reload 120 user-service: 121 command: bin/user-service 122 daemon: simple 123 daemon-scope: user 124 ` 125 126 const otherSnapYaml = `name: other-snap 127 version: 1.0 128 summary: other-snap 129 apps: 130 test-service: 131 command: bin/service 132 daemon: simple 133 reload-command: bin/reload 134 ` 135 136 func mockServiceChangeFunc(testServiceControlInputs func(appInfos []*snap.AppInfo, inst *servicestate.Instruction)) func() { 137 return ctlcmd.MockServicestateControlFunc(func(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, flags *servicestate.Flags, context *hookstate.Context) ([]*state.TaskSet, error) { 138 testServiceControlInputs(appInfos, inst) 139 return nil, fmt.Errorf("forced error") 140 }) 141 } 142 143 func (s *servicectlSuite) SetUpTest(c *C) { 144 s.BaseTest.SetUpTest(c) 145 oldRoot := dirs.GlobalRootDir 146 dirs.SetRootDir(c.MkDir()) 147 148 testutil.MockCommand(c, "systemctl", "") 149 150 s.BaseTest.AddCleanup(func() { 151 dirs.SetRootDir(oldRoot) 152 }) 153 s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {})) 154 155 s.mockHandler = hooktest.NewMockHandler() 156 157 s.st = state.New(nil) 158 s.st.Lock() 159 defer s.st.Unlock() 160 161 snapstate.ReplaceStore(s.st, &s.fakeStore) 162 163 // mock installed snaps 164 info1 := snaptest.MockSnapCurrent(c, string(testSnapYaml), &snap.SideInfo{ 165 Revision: snap.R(1), 166 }) 167 info2 := snaptest.MockSnapCurrent(c, string(otherSnapYaml), &snap.SideInfo{ 168 Revision: snap.R(1), 169 }) 170 snapstate.Set(s.st, info1.InstanceName(), &snapstate.SnapState{ 171 Active: true, 172 Sequence: []*snap.SideInfo{ 173 { 174 RealName: info1.SnapName(), 175 Revision: info1.Revision, 176 SnapID: "test-snap-id", 177 }, 178 }, 179 Current: info1.Revision, 180 }) 181 snapstate.Set(s.st, info2.InstanceName(), &snapstate.SnapState{ 182 Active: true, 183 Sequence: []*snap.SideInfo{ 184 { 185 RealName: info2.SnapName(), 186 Revision: info2.Revision, 187 SnapID: "other-snap-id", 188 }, 189 }, 190 Current: info2.Revision, 191 }) 192 193 task := s.st.NewTask("test-task", "my test task") 194 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"} 195 196 var err error 197 s.mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 198 c.Assert(err, IsNil) 199 200 s.st.Set("seeded", true) 201 s.st.Set("refresh-privacy-key", "privacy-key") 202 s.AddCleanup(snapstatetest.UseFallbackDeviceModel()) 203 } 204 205 func (s *servicectlSuite) TestStopCommand(c *C) { 206 var serviceChangeFuncCalled bool 207 restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) { 208 serviceChangeFuncCalled = true 209 c.Assert(appInfos, HasLen, 1) 210 c.Assert(appInfos[0].Name, Equals, "test-service") 211 c.Assert(inst, DeepEquals, &servicestate.Instruction{ 212 Action: "stop", 213 Names: []string{"test-snap.test-service"}, 214 StopOptions: client.StopOptions{ 215 Disable: false, 216 }, 217 }, 218 ) 219 }) 220 defer restore() 221 _, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "test-snap.test-service"}, 0) 222 c.Assert(err, NotNil) 223 c.Check(err, ErrorMatches, "forced error") 224 c.Assert(serviceChangeFuncCalled, Equals, true) 225 } 226 227 func (s *servicectlSuite) TestStopCommandUnknownService(c *C) { 228 var serviceChangeFuncCalled bool 229 restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) { 230 serviceChangeFuncCalled = true 231 }) 232 defer restore() 233 _, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "test-snap.fooservice"}, 0) 234 c.Assert(err, NotNil) 235 c.Assert(err, ErrorMatches, `unknown service: "test-snap.fooservice"`) 236 c.Assert(serviceChangeFuncCalled, Equals, false) 237 } 238 239 func (s *servicectlSuite) TestStopCommandFailsOnOtherSnap(c *C) { 240 var serviceChangeFuncCalled bool 241 restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) { 242 serviceChangeFuncCalled = true 243 }) 244 defer restore() 245 // verify that snapctl is not allowed to control services of other snaps (only the one of its hook) 246 _, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "other-snap.test-service"}, 0) 247 c.Check(err, NotNil) 248 c.Assert(err, ErrorMatches, `unknown service: "other-snap.test-service"`) 249 c.Assert(serviceChangeFuncCalled, Equals, false) 250 } 251 252 func (s *servicectlSuite) TestStartCommand(c *C) { 253 var serviceChangeFuncCalled bool 254 restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) { 255 serviceChangeFuncCalled = true 256 c.Assert(appInfos, HasLen, 1) 257 c.Assert(appInfos[0].Name, Equals, "test-service") 258 c.Assert(inst, DeepEquals, &servicestate.Instruction{ 259 Action: "start", 260 Names: []string{"test-snap.test-service"}, 261 StartOptions: client.StartOptions{ 262 Enable: false, 263 }, 264 }, 265 ) 266 }) 267 defer restore() 268 _, _, err := ctlcmd.Run(s.mockContext, []string{"start", "test-snap.test-service"}, 0) 269 c.Check(err, NotNil) 270 c.Check(err, ErrorMatches, "forced error") 271 c.Assert(serviceChangeFuncCalled, Equals, true) 272 } 273 274 func (s *servicectlSuite) TestRestartCommand(c *C) { 275 var serviceChangeFuncCalled bool 276 restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) { 277 serviceChangeFuncCalled = true 278 c.Assert(appInfos, HasLen, 1) 279 c.Assert(appInfos[0].Name, Equals, "test-service") 280 c.Assert(inst, DeepEquals, &servicestate.Instruction{ 281 Action: "restart", 282 Names: []string{"test-snap.test-service"}, 283 RestartOptions: client.RestartOptions{ 284 Reload: false, 285 }, 286 }, 287 ) 288 }) 289 defer restore() 290 _, _, err := ctlcmd.Run(s.mockContext, []string{"restart", "test-snap.test-service"}, 0) 291 c.Check(err, NotNil) 292 c.Check(err, ErrorMatches, "forced error") 293 c.Assert(serviceChangeFuncCalled, Equals, true) 294 } 295 296 func (s *servicectlSuite) TestConflictingChange(c *C) { 297 s.st.Lock() 298 task := s.st.NewTask("link-snap", "conflicting task") 299 snapsup := snapstate.SnapSetup{ 300 SideInfo: &snap.SideInfo{ 301 RealName: "test-snap", 302 SnapID: "test-snap-id-1", 303 Revision: snap.R(1), 304 }, 305 } 306 task.Set("snap-setup", snapsup) 307 chg := s.st.NewChange("conflicting change", "install change") 308 chg.AddTask(task) 309 s.st.Unlock() 310 311 _, _, err := ctlcmd.Run(s.mockContext, []string{"start", "test-snap.test-service"}, 0) 312 c.Check(err, NotNil) 313 c.Check(err, ErrorMatches, `snap "test-snap" has "conflicting change" change in progress`) 314 } 315 316 var ( 317 installTaskKinds = []string{ 318 "prerequisites", 319 "download-snap", 320 "validate-snap", 321 "mount-snap", 322 "copy-snap-data", 323 "setup-profiles", 324 "link-snap", 325 "auto-connect", 326 "set-auto-aliases", 327 "setup-aliases", 328 "run-hook[install]", 329 "start-snap-services", 330 "run-hook[configure]", 331 "run-hook[check-health]", 332 } 333 334 refreshTaskKinds = []string{ 335 "prerequisites", 336 "download-snap", 337 "validate-snap", 338 "mount-snap", 339 "run-hook[pre-refresh]", 340 "stop-snap-services", 341 "remove-aliases", 342 "unlink-current-snap", 343 "copy-snap-data", 344 "setup-profiles", 345 "link-snap", 346 "auto-connect", 347 "set-auto-aliases", 348 "setup-aliases", 349 "run-hook[post-refresh]", 350 "start-snap-services", 351 "cleanup", 352 "run-hook[configure]", 353 "run-hook[check-health]", 354 } 355 ) 356 357 func (s *servicectlSuite) TestQueuedCommands(c *C) { 358 s.st.Lock() 359 360 chg := s.st.NewChange("install change", "install change") 361 installed, tts, err := snapstate.InstallMany(s.st, []string{"one", "two"}, 0) 362 c.Assert(err, IsNil) 363 c.Check(installed, DeepEquals, []string{"one", "two"}) 364 c.Assert(tts, HasLen, 2) 365 c.Assert(taskKinds(tts[0].Tasks()), DeepEquals, installTaskKinds) 366 c.Assert(taskKinds(tts[1].Tasks()), DeepEquals, installTaskKinds) 367 chg.AddAll(tts[0]) 368 chg.AddAll(tts[1]) 369 370 s.st.Unlock() 371 372 for _, ts := range tts { 373 tsTasks := ts.Tasks() 374 // assumes configure task is last 375 task := tsTasks[len(tsTasks)-1] 376 c.Assert(task.Kind(), Equals, "run-hook") 377 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} 378 context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 379 c.Assert(err, IsNil) 380 381 _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0) 382 c.Check(err, IsNil) 383 _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0) 384 c.Check(err, IsNil) 385 _, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}, 0) 386 c.Check(err, IsNil) 387 } 388 389 s.st.Lock() 390 defer s.st.Unlock() 391 392 expectedTaskKinds := append(installTaskKinds, "exec-command", "service-control", "exec-command", "service-control", "exec-command", "service-control") 393 checkLaneTasks := func(lane int) { 394 laneTasks := chg.LaneTasks(lane) 395 c.Assert(taskKinds(laneTasks), DeepEquals, expectedTaskKinds) 396 c.Check(laneTasks[12].Summary(), Matches, `Run configure hook of .* snap if present`) 397 c.Check(laneTasks[14].Summary(), Equals, "stop of [test-snap.test-service]") 398 c.Check(laneTasks[16].Summary(), Equals, "start of [test-snap.test-service]") 399 c.Check(laneTasks[18].Summary(), Equals, "restart of [test-snap.test-service]") 400 } 401 checkLaneTasks(1) 402 checkLaneTasks(2) 403 } 404 405 func (s *servicectlSuite) testQueueCommandsOrdering(c *C, finalTaskKind string) { 406 s.st.Lock() 407 408 chg := s.st.NewChange("seeding change", "seeding change") 409 finalTask := s.st.NewTask(finalTaskKind, "") 410 chg.AddTask(finalTask) 411 configure := s.st.NewTask("run-hook", "") 412 chg.AddTask(configure) 413 414 s.st.Unlock() 415 416 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} 417 context, err := hookstate.NewContext(configure, configure.State(), setup, s.mockHandler, "") 418 c.Assert(err, IsNil) 419 420 _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0) 421 c.Check(err, IsNil) 422 _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0) 423 c.Check(err, IsNil) 424 425 s.st.Lock() 426 defer s.st.Unlock() 427 428 var finalWaitTasks []string 429 for _, t := range finalTask.WaitTasks() { 430 taskInfo := fmt.Sprintf("%s:%s", t.Kind(), t.Summary()) 431 finalWaitTasks = append(finalWaitTasks, taskInfo) 432 433 var wait []string 434 var hasRunHook bool 435 for _, wt := range t.WaitTasks() { 436 if wt.Kind() != "run-hook" { 437 taskInfo = fmt.Sprintf("%s:%s", wt.Kind(), wt.Summary()) 438 wait = append(wait, taskInfo) 439 } else { 440 hasRunHook = true 441 } 442 } 443 c.Assert(hasRunHook, Equals, true) 444 445 switch t.Kind() { 446 case "exec-command": 447 var argv []string 448 c.Assert(t.Get("argv", &argv), IsNil) 449 c.Check(argv, HasLen, 3) 450 switch argv[1] { 451 case "stop": 452 c.Check(wait, HasLen, 0) 453 case "start": 454 c.Check(wait, DeepEquals, []string{ 455 `exec-command:stop of [test-snap.test-service]`, 456 `service-control:Run service command "stop" for services ["test-service"] of snap "test-snap"`}) 457 default: 458 c.Fatalf("unexpected command: %q", argv[1]) 459 } 460 case "service-control": 461 var sa servicestate.ServiceAction 462 c.Assert(t.Get("service-action", &sa), IsNil) 463 c.Check(sa.Services, DeepEquals, []string{"test-service"}) 464 switch sa.Action { 465 case "stop": 466 c.Check(wait, DeepEquals, []string{ 467 "exec-command:stop of [test-snap.test-service]"}) 468 case "start": 469 c.Check(wait, DeepEquals, []string{ 470 "exec-command:start of [test-snap.test-service]", 471 "exec-command:stop of [test-snap.test-service]", 472 `service-control:Run service command "stop" for services ["test-service"] of snap "test-snap"`}) 473 } 474 default: 475 c.Fatalf("unexpected task: %s", t.Kind()) 476 } 477 478 } 479 c.Check(finalWaitTasks, DeepEquals, []string{ 480 `exec-command:stop of [test-snap.test-service]`, 481 `service-control:Run service command "stop" for services ["test-service"] of snap "test-snap"`, 482 `exec-command:start of [test-snap.test-service]`, 483 `service-control:Run service command "start" for services ["test-service"] of snap "test-snap"`}) 484 c.Check(finalTask.HaltTasks(), HasLen, 0) 485 } 486 487 func (s *servicectlSuite) TestQueuedCommandsRunBeforeMarkSeeded(c *C) { 488 s.testQueueCommandsOrdering(c, "mark-seeded") 489 } 490 491 func (s *servicectlSuite) TestQueuedCommandsRunBeforeSetModel(c *C) { 492 s.testQueueCommandsOrdering(c, "set-model") 493 } 494 495 func (s *servicectlSuite) TestQueuedCommandsUpdateMany(c *C) { 496 oldAutoAliases := snapstate.AutoAliases 497 snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) { 498 return nil, nil 499 } 500 defer func() { snapstate.AutoAliases = oldAutoAliases }() 501 502 s.st.Lock() 503 504 chg := s.st.NewChange("update many change", "update change") 505 installed, tts, err := snapstate.UpdateMany(context.Background(), s.st, []string{"test-snap", "other-snap"}, 0, nil) 506 c.Assert(err, IsNil) 507 sort.Strings(installed) 508 c.Check(installed, DeepEquals, []string{"other-snap", "test-snap"}) 509 c.Assert(tts, HasLen, 3) 510 c.Assert(taskKinds(tts[0].Tasks()), DeepEquals, refreshTaskKinds) 511 c.Assert(taskKinds(tts[1].Tasks()), DeepEquals, refreshTaskKinds) 512 c.Assert(taskKinds(tts[2].Tasks()), DeepEquals, []string{"check-rerefresh"}) 513 c.Assert(tts[2].Tasks()[0].Kind(), Equals, "check-rerefresh") 514 chg.AddAll(tts[0]) 515 chg.AddAll(tts[1]) 516 517 s.st.Unlock() 518 519 for _, ts := range tts[:2] { 520 tsTasks := ts.Tasks() 521 // assumes configure task is last 522 task := tsTasks[len(tsTasks)-1] 523 c.Assert(task.Kind(), Equals, "run-hook") 524 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} 525 context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 526 c.Assert(err, IsNil) 527 528 _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0) 529 c.Check(err, IsNil) 530 _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0) 531 c.Check(err, IsNil) 532 _, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}, 0) 533 c.Check(err, IsNil) 534 } 535 536 s.st.Lock() 537 defer s.st.Unlock() 538 539 expectedTaskKinds := append(refreshTaskKinds, "exec-command", "service-control", "exec-command", "service-control", "exec-command", "service-control") 540 for i := 1; i <= 2; i++ { 541 laneTasks := chg.LaneTasks(i) 542 c.Assert(taskKinds(laneTasks), DeepEquals, expectedTaskKinds) 543 c.Check(laneTasks[17].Summary(), Matches, `Run configure hook of .* snap if present`) 544 c.Check(laneTasks[19].Summary(), Equals, "stop of [test-snap.test-service]") 545 c.Check(laneTasks[20].Summary(), Equals, `Run service command "stop" for services ["test-service"] of snap "test-snap"`) 546 c.Check(laneTasks[21].Summary(), Equals, "start of [test-snap.test-service]") 547 c.Check(laneTasks[22].Summary(), Equals, `Run service command "start" for services ["test-service"] of snap "test-snap"`) 548 c.Check(laneTasks[23].Summary(), Equals, "restart of [test-snap.test-service]") 549 c.Check(laneTasks[24].Summary(), Equals, `Run service command "restart" for services ["test-service"] of snap "test-snap"`) 550 } 551 } 552 553 func (s *servicectlSuite) TestQueuedCommandsSingleLane(c *C) { 554 s.st.Lock() 555 556 chg := s.st.NewChange("install change", "install change") 557 ts, err := snapstate.Install(context.Background(), s.st, "one", &snapstate.RevisionOptions{Revision: snap.R(1)}, 0, snapstate.Flags{}) 558 c.Assert(err, IsNil) 559 c.Assert(taskKinds(ts.Tasks()), DeepEquals, installTaskKinds) 560 chg.AddAll(ts) 561 562 s.st.Unlock() 563 564 tsTasks := ts.Tasks() 565 // assumes configure task is last 566 task := tsTasks[len(tsTasks)-1] 567 c.Assert(task.Kind(), Equals, "run-hook") 568 setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"} 569 context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "") 570 c.Assert(err, IsNil) 571 572 _, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0) 573 c.Check(err, IsNil) 574 _, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0) 575 c.Check(err, IsNil) 576 _, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}, 0) 577 c.Check(err, IsNil) 578 579 s.st.Lock() 580 defer s.st.Unlock() 581 582 laneTasks := chg.LaneTasks(0) 583 c.Assert(taskKinds(laneTasks), DeepEquals, append(installTaskKinds, "exec-command", "service-control", "exec-command", "service-control", "exec-command", "service-control")) 584 c.Check(laneTasks[12].Summary(), Matches, `Run configure hook of .* snap if present`) 585 c.Check(laneTasks[14].Summary(), Equals, "stop of [test-snap.test-service]") 586 c.Check(laneTasks[16].Summary(), Equals, "start of [test-snap.test-service]") 587 c.Check(laneTasks[18].Summary(), Equals, "restart of [test-snap.test-service]") 588 } 589 590 func (s *servicectlSuite) TestTwoServices(c *C) { 591 restore := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) { 592 switch args[0] { 593 case "show": 594 c.Check(args[2], Matches, `snap\.test-snap\.\w+-service\.service`) 595 return []byte(fmt.Sprintf(`Id=%s 596 Type=simple 597 ActiveState=active 598 UnitFileState=enabled 599 `, args[2])), nil 600 case "--user": 601 c.Check(args[1], Equals, "--global") 602 c.Check(args[2], Equals, "is-enabled") 603 return []byte("enabled\n"), nil 604 default: 605 c.Errorf("unexpected systemctl command: %v", args) 606 return nil, fmt.Errorf("should not be reached") 607 } 608 }) 609 defer restore() 610 611 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"services"}, 0) 612 c.Assert(err, IsNil) 613 c.Check(string(stdout), Equals, ` 614 Service Startup Current Notes 615 test-snap.another-service enabled active - 616 test-snap.test-service enabled active - 617 test-snap.user-service enabled - user 618 `[1:]) 619 c.Check(string(stderr), Equals, "") 620 } 621 622 func (s *servicectlSuite) TestServices(c *C) { 623 restore := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) { 624 c.Assert(args[0], Equals, "show") 625 c.Check(args[2], Equals, "snap.test-snap.test-service.service") 626 return []byte(`Id=snap.test-snap.test-service.service 627 Type=simple 628 ActiveState=active 629 UnitFileState=enabled 630 `), nil 631 }) 632 defer restore() 633 634 stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"services", "test-snap.test-service"}, 0) 635 c.Assert(err, IsNil) 636 c.Check(string(stdout), Equals, ` 637 Service Startup Current Notes 638 test-snap.test-service enabled active - 639 `[1:]) 640 c.Check(string(stderr), Equals, "") 641 }