github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/servicestate/service_control_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 servicestate_test 21 22 import ( 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "sort" 27 "strings" 28 "testing" 29 "time" 30 31 . "gopkg.in/check.v1" 32 33 "github.com/snapcore/snapd/client" 34 "github.com/snapcore/snapd/dirs" 35 "github.com/snapcore/snapd/overlord" 36 "github.com/snapcore/snapd/overlord/servicestate" 37 "github.com/snapcore/snapd/overlord/snapstate" 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/systemd" 42 "github.com/snapcore/snapd/systemd/systemdtest" 43 "github.com/snapcore/snapd/testutil" 44 ) 45 46 func TestServiceControl(t *testing.T) { TestingT(t) } 47 48 type serviceControlSuite struct { 49 testutil.BaseTest 50 state *state.State 51 o *overlord.Overlord 52 se *overlord.StateEngine 53 serviceMgr *servicestate.ServiceManager 54 sysctlArgs [][]string 55 } 56 57 var _ = Suite(&serviceControlSuite{}) 58 59 const servicesSnapYaml1 = `name: test-snap 60 version: 1.0 61 apps: 62 someapp: 63 command: cmd 64 abc: 65 daemon: simple 66 after: [bar] 67 foo: 68 daemon: simple 69 bar: 70 daemon: simple 71 after: [foo] 72 ` 73 74 func (s *serviceControlSuite) SetUpTest(c *C) { 75 s.BaseTest.SetUpTest(c) 76 77 dirs.SetRootDir(c.MkDir()) 78 s.AddCleanup(func() { dirs.SetRootDir("") }) 79 80 s.o = overlord.Mock() 81 s.state = s.o.State() 82 83 s.sysctlArgs = nil 84 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) (buf []byte, err error) { 85 // When calling the "snap restart" command, the initial status will be 86 // retrieved first. Services which are not running will be 87 // ignored. Therefore, mock this "show" operation by pretending that 88 // all requested services are active: 89 if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, nil); out != nil { 90 return out, nil 91 } 92 93 s.sysctlArgs = append(s.sysctlArgs, cmd) 94 if cmd[0] == "show" { 95 return []byte("ActiveState=inactive\n"), nil 96 } 97 return nil, nil 98 }) 99 s.AddCleanup(systemctlRestorer) 100 101 s.serviceMgr = servicestate.Manager(s.state, s.o.TaskRunner()) 102 s.o.AddManager(s.serviceMgr) 103 s.o.AddManager(s.o.TaskRunner()) 104 s.se = s.o.StateEngine() 105 c.Assert(s.o.StartUp(), IsNil) 106 } 107 108 func (s *serviceControlSuite) mockTestSnap(c *C) *snap.Info { 109 si := snap.SideInfo{ 110 RealName: "test-snap", 111 Revision: snap.R(7), 112 } 113 info := snaptest.MockSnap(c, servicesSnapYaml1, &si) 114 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 115 Active: true, 116 Sequence: []*snap.SideInfo{&si}, 117 Current: snap.R(7), 118 SnapType: "app", 119 }) 120 121 // mock systemd service units, this is required when testing "stop" 122 err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc/systemd/system/"), 0775) 123 c.Assert(err, IsNil) 124 err = ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "etc/systemd/system/snap.test-snap.foo.service"), nil, 0644) 125 c.Assert(err, IsNil) 126 127 return info 128 } 129 130 func verifyUnsortedInvocations(c *C, sysctlArgs [][]string, action string, 131 expectedArguments []string) { 132 /* We don't care about the order of the invocations, as long as 133 * they all carry the same action */ 134 arguments := []string{} 135 for _, params := range sysctlArgs { 136 c.Check(params[0], Equals, action) 137 arguments = append(arguments, params[1]) 138 } 139 sort.Strings(arguments) 140 c.Check(arguments, DeepEquals, expectedArguments) 141 } 142 143 func verifyControlTasks(c *C, tasks []*state.Task, expectedAction, actionModifier string, 144 expectedServices []string, expectedExplicitServices []string) { 145 // sanity, ensures test checks below are hit 146 c.Assert(len(tasks) > 0, Equals, true) 147 148 // group service names by snaps 149 bySnap := make(map[string][]string) 150 for _, name := range expectedServices { 151 // split service name, e.g. snap.test-snap.foo.service 152 parts := strings.Split(name, ".") 153 c.Assert(parts, HasLen, 4) 154 snapName := parts[1] 155 serviceName := parts[2] 156 bySnap[snapName] = append(bySnap[snapName], serviceName) 157 } 158 159 var execCommandTasks int 160 var serviceControlTasks int 161 // snaps from service-control tasks 162 seenSnaps := make(map[string]bool) 163 164 var i int 165 for i < len(tasks) { 166 var argv []string 167 kind := tasks[i].Kind() 168 if kind == "exec-command" { 169 execCommandTasks++ 170 var ignore bool 171 c.Assert(tasks[i].Get("ignore", &ignore), IsNil) 172 c.Check(ignore, Equals, true) 173 switch expectedAction { 174 case "start": 175 if actionModifier != "" { 176 c.Assert(tasks[i].Get("argv", &argv), IsNil) 177 c.Check(argv, DeepEquals, append([]string{"systemctl", actionModifier}, expectedServices...)) 178 i++ 179 wt := tasks[i].WaitTasks() 180 c.Assert(wt, HasLen, 1) 181 c.Assert(wt[0].ID(), Equals, tasks[i-1].ID()) 182 } 183 c.Assert(tasks[i].Get("argv", &argv), IsNil) 184 c.Check(argv, DeepEquals, append([]string{"systemctl", "start"}, expectedServices...)) 185 case "stop": 186 if actionModifier != "" { 187 c.Assert(tasks[i].Get("argv", &argv), IsNil) 188 c.Check(argv, DeepEquals, append([]string{"systemctl", actionModifier}, expectedServices...)) 189 i++ 190 wt := tasks[i].WaitTasks() 191 c.Assert(wt, HasLen, 1) 192 c.Assert(wt[0].ID(), Equals, tasks[i-1].ID()) 193 } 194 c.Assert(tasks[i].Get("argv", &argv), IsNil) 195 c.Check(argv, DeepEquals, append([]string{"systemctl", "stop"}, expectedServices...)) 196 case "restart": 197 if actionModifier != "" { 198 c.Assert(tasks[i].Get("argv", &argv), IsNil) 199 c.Check(argv, DeepEquals, append([]string{"systemctl", "reload-or-restart"}, expectedServices...)) 200 } else { 201 c.Assert(tasks[i].Get("argv", &argv), IsNil) 202 c.Check(argv, DeepEquals, append([]string{"systemctl", "restart"}, expectedServices...)) 203 } 204 default: 205 c.Fatalf("unhandled action %s", expectedAction) 206 } 207 } else if kind == "service-control" { 208 serviceControlTasks++ 209 var sa servicestate.ServiceAction 210 c.Assert(tasks[i].Get("service-action", &sa), IsNil) 211 switch expectedAction { 212 case "start": 213 c.Check(sa.Action, Equals, "start") 214 if actionModifier != "" { 215 c.Check(sa.ActionModifier, Equals, actionModifier) 216 } 217 case "stop": 218 c.Check(sa.Action, Equals, "stop") 219 if actionModifier != "" { 220 c.Check(sa.ActionModifier, Equals, actionModifier) 221 } 222 case "restart": 223 if actionModifier == "reload" { 224 c.Check(sa.Action, Equals, "reload-or-restart") 225 } else { 226 c.Check(sa.Action, Equals, "restart") 227 } 228 default: 229 c.Fatalf("unhandled action %s", expectedAction) 230 } 231 seenSnaps[sa.SnapName] = true 232 obtainedServices := sa.Services 233 sort.Strings(obtainedServices) 234 sort.Strings(bySnap[sa.SnapName]) 235 c.Assert(obtainedServices, DeepEquals, bySnap[sa.SnapName]) 236 c.Assert(sa.ExplicitServices, DeepEquals, expectedExplicitServices) 237 } else { 238 c.Fatalf("unexpected task: %s", tasks[i].Kind()) 239 } 240 i++ 241 } 242 243 c.Check(execCommandTasks > 0, Equals, true) 244 245 // we should have one service-control task for every snap 246 c.Assert(serviceControlTasks, Equals, len(bySnap)) 247 c.Assert(len(bySnap), Equals, len(seenSnaps)) 248 for snapName := range bySnap { 249 c.Assert(seenSnaps[snapName], Equals, true) 250 } 251 } 252 253 func makeControlChange(c *C, st *state.State, inst *servicestate.Instruction, info *snap.Info, appNames []string) *state.Change { 254 apps := []*snap.AppInfo{} 255 for _, name := range appNames { 256 c.Assert(info.Apps[name], NotNil) 257 apps = append(apps, info.Apps[name]) 258 } 259 260 flags := &servicestate.Flags{CreateExecCommandTasks: true} 261 tss, err := servicestate.Control(st, apps, inst, flags, nil) 262 c.Assert(err, IsNil) 263 264 chg := st.NewChange("service-control", "...") 265 for _, ts := range tss { 266 chg.AddAll(ts) 267 } 268 return chg 269 } 270 271 func (s *serviceControlSuite) TestControlDoesntCreateExecCommandTasksIfNoFlags(c *C) { 272 st := s.state 273 st.Lock() 274 defer st.Unlock() 275 276 info := s.mockTestSnap(c) 277 inst := &servicestate.Instruction{ 278 Action: "start", 279 Names: []string{"foo"}, 280 } 281 282 flags := &servicestate.Flags{} 283 tss, err := servicestate.Control(st, []*snap.AppInfo{info.Apps["foo"]}, inst, flags, nil) 284 c.Assert(err, IsNil) 285 // service-control is the only task 286 c.Assert(tss, HasLen, 1) 287 c.Assert(tss[0].Tasks(), HasLen, 1) 288 c.Check(tss[0].Tasks()[0].Kind(), Equals, "service-control") 289 } 290 291 func (s *serviceControlSuite) TestControlConflict(c *C) { 292 st := s.state 293 st.Lock() 294 defer st.Unlock() 295 296 inf := s.mockTestSnap(c) 297 298 // create conflicting change 299 t := st.NewTask("link-snap", "...") 300 snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "test-snap"}} 301 t.Set("snap-setup", snapsup) 302 chg := st.NewChange("manip", "...") 303 chg.AddTask(t) 304 305 inst := &servicestate.Instruction{Action: "start", Names: []string{"foo"}} 306 _, err := servicestate.Control(st, []*snap.AppInfo{inf.Apps["foo"]}, inst, nil, nil) 307 c.Check(err, ErrorMatches, `snap "test-snap" has "manip" change in progress`) 308 } 309 310 func (s *serviceControlSuite) TestControlStartInstruction(c *C) { 311 st := s.state 312 st.Lock() 313 defer st.Unlock() 314 315 inf := s.mockTestSnap(c) 316 317 inst := &servicestate.Instruction{ 318 Action: "start", 319 } 320 321 chg := makeControlChange(c, st, inst, inf, []string{"foo"}) 322 verifyControlTasks(c, chg.Tasks(), "start", "", 323 []string{"snap.test-snap.foo.service"}, nil) 324 } 325 326 func (s *serviceControlSuite) TestControlStartEnableMultipleInstruction(c *C) { 327 st := s.state 328 st.Lock() 329 defer st.Unlock() 330 331 inf := s.mockTestSnap(c) 332 333 inst := &servicestate.Instruction{ 334 Action: "start", 335 StartOptions: client.StartOptions{Enable: true}, 336 } 337 338 chg := makeControlChange(c, st, inst, inf, []string{"foo", "bar"}) 339 verifyControlTasks(c, chg.Tasks(), "start", "enable", 340 []string{"snap.test-snap.foo.service", "snap.test-snap.bar.service"}, nil) 341 } 342 343 func (s *serviceControlSuite) TestControlStopInstruction(c *C) { 344 st := s.state 345 st.Lock() 346 defer st.Unlock() 347 348 inf := s.mockTestSnap(c) 349 350 inst := &servicestate.Instruction{ 351 Action: "stop", 352 } 353 354 chg := makeControlChange(c, st, inst, inf, []string{"foo"}) 355 verifyControlTasks(c, chg.Tasks(), "stop", "", 356 []string{"snap.test-snap.foo.service"}, nil) 357 } 358 359 func (s *serviceControlSuite) TestControlStopDisableInstruction(c *C) { 360 st := s.state 361 st.Lock() 362 defer st.Unlock() 363 364 inf := s.mockTestSnap(c) 365 366 inst := &servicestate.Instruction{ 367 Action: "stop", 368 StopOptions: client.StopOptions{Disable: true}, 369 } 370 371 chg := makeControlChange(c, st, inst, inf, []string{"bar"}) 372 verifyControlTasks(c, chg.Tasks(), "stop", "disable", 373 []string{"snap.test-snap.bar.service"}, nil) 374 } 375 376 func (s *serviceControlSuite) TestControlRestartInstruction(c *C) { 377 st := s.state 378 st.Lock() 379 defer st.Unlock() 380 381 inf := s.mockTestSnap(c) 382 383 inst := &servicestate.Instruction{ 384 Action: "restart", 385 } 386 387 chg := makeControlChange(c, st, inst, inf, []string{"bar"}) 388 verifyControlTasks(c, chg.Tasks(), "restart", "", 389 []string{"snap.test-snap.bar.service"}, nil) 390 } 391 392 func (s *serviceControlSuite) TestControlRestartReloadMultipleInstruction(c *C) { 393 st := s.state 394 st.Lock() 395 defer st.Unlock() 396 397 inf := s.mockTestSnap(c) 398 399 inst := &servicestate.Instruction{ 400 Action: "restart", 401 RestartOptions: client.RestartOptions{Reload: true}, 402 } 403 404 chg := makeControlChange(c, st, inst, inf, []string{"foo", "bar"}) 405 verifyControlTasks(c, chg.Tasks(), "restart", "reload", 406 []string{"snap.test-snap.foo.service", "snap.test-snap.bar.service"}, 407 nil) 408 } 409 410 func (s *serviceControlSuite) TestControlUnknownInstruction(c *C) { 411 st := s.state 412 st.Lock() 413 defer st.Unlock() 414 415 info := s.mockTestSnap(c) 416 inst := &servicestate.Instruction{ 417 Action: "boo", 418 RestartOptions: client.RestartOptions{Reload: true}, 419 } 420 421 _, err := servicestate.Control(st, []*snap.AppInfo{info.Apps["foo"]}, inst, nil, nil) 422 c.Assert(err, ErrorMatches, `unknown action "boo"`) 423 } 424 425 func (s *serviceControlSuite) TestControlStopDisableMultipleInstruction(c *C) { 426 st := s.state 427 st.Lock() 428 defer st.Unlock() 429 430 inf := s.mockTestSnap(c) 431 432 inst := &servicestate.Instruction{ 433 Action: "stop", 434 StopOptions: client.StopOptions{Disable: true}, 435 } 436 437 chg := makeControlChange(c, st, inst, inf, []string{"foo", "bar"}) 438 verifyControlTasks(c, chg.Tasks(), "stop", "disable", 439 []string{"snap.test-snap.foo.service", "snap.test-snap.bar.service"}, 440 nil) 441 } 442 443 func (s *serviceControlSuite) TestWithExplicitServices(c *C) { 444 st := s.state 445 st.Lock() 446 defer st.Unlock() 447 448 inf := s.mockTestSnap(c) 449 450 inst := &servicestate.Instruction{ 451 Action: "start", 452 Names: []string{"test-snap.foo", "test-snap.abc"}, 453 } 454 455 chg := makeControlChange(c, st, inst, inf, []string{"abc", "foo", "bar"}) 456 verifyControlTasks(c, chg.Tasks(), "start", "", 457 []string{"snap.test-snap.abc.service", "snap.test-snap.foo.service", "snap.test-snap.bar.service"}, 458 []string{"snap.test-snap.abc.service", "snap.test-snap.foo.service"}) 459 } 460 461 func (s *serviceControlSuite) TestWithExplicitServicesAndSnapName(c *C) { 462 st := s.state 463 st.Lock() 464 defer st.Unlock() 465 466 inf := s.mockTestSnap(c) 467 468 inst := &servicestate.Instruction{ 469 Action: "start", 470 Names: []string{"test-snap.foo", "test-snap", "test-snap.bar"}, 471 } 472 473 chg := makeControlChange(c, st, inst, inf, []string{"abc", "foo", "bar"}) 474 verifyControlTasks(c, chg.Tasks(), "start", "", 475 []string{"snap.test-snap.abc.service", "snap.test-snap.foo.service", "snap.test-snap.bar.service"}, 476 []string{"snap.test-snap.bar.service", "snap.test-snap.foo.service"}) 477 } 478 479 func (s *serviceControlSuite) TestNoServiceCommandError(c *C) { 480 st := s.state 481 st.Lock() 482 defer st.Unlock() 483 484 chg := st.NewChange("service-control", "...") 485 t := st.NewTask("service-control", "...") 486 chg.AddTask(t) 487 488 st.Unlock() 489 defer s.se.Stop() 490 err := s.o.Settle(5 * time.Second) 491 st.Lock() 492 c.Assert(err, IsNil) 493 494 c.Assert(t.Status(), Equals, state.ErrorStatus) 495 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*internal error: cannot get service-action: no state entry for key.*`) 496 } 497 498 func (s *serviceControlSuite) TestNoopWhenNoServices(c *C) { 499 st := s.state 500 st.Lock() 501 defer st.Unlock() 502 503 si := snap.SideInfo{RealName: "test-snap", Revision: snap.R(7)} 504 snaptest.MockSnap(c, `name: test-snap`, &si) 505 snapstate.Set(st, "test-snap", &snapstate.SnapState{ 506 Active: true, 507 Sequence: []*snap.SideInfo{&si}, 508 Current: snap.R(7), 509 SnapType: "app", 510 }) 511 512 chg := st.NewChange("service-control", "...") 513 t := st.NewTask("service-control", "...") 514 cmd := &servicestate.ServiceAction{SnapName: "test-snap"} 515 t.Set("service-action", cmd) 516 chg.AddTask(t) 517 518 st.Unlock() 519 defer s.se.Stop() 520 err := s.o.Settle(5 * time.Second) 521 st.Lock() 522 c.Assert(err, IsNil) 523 524 c.Check(t.Status(), Equals, state.DoneStatus) 525 } 526 527 func (s *serviceControlSuite) TestUnhandledServiceAction(c *C) { 528 st := s.state 529 st.Lock() 530 defer st.Unlock() 531 532 s.mockTestSnap(c) 533 534 chg := st.NewChange("service-control", "...") 535 t := st.NewTask("service-control", "...") 536 cmd := &servicestate.ServiceAction{SnapName: "test-snap", Action: "foo"} 537 t.Set("service-action", cmd) 538 chg.AddTask(t) 539 540 st.Unlock() 541 defer s.se.Stop() 542 err := s.o.Settle(5 * time.Second) 543 st.Lock() 544 c.Assert(err, IsNil) 545 546 c.Assert(t.Status(), Equals, state.ErrorStatus) 547 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*unhandled service action: "foo".*`) 548 } 549 550 func (s *serviceControlSuite) TestUnknownService(c *C) { 551 st := s.state 552 st.Lock() 553 defer st.Unlock() 554 555 s.mockTestSnap(c) 556 557 chg := st.NewChange("service-control", "...") 558 t := st.NewTask("service-control", "...") 559 cmd := &servicestate.ServiceAction{ 560 SnapName: "test-snap", 561 Action: "start", 562 Services: []string{"baz"}, 563 } 564 t.Set("service-action", cmd) 565 chg.AddTask(t) 566 567 st.Unlock() 568 defer s.se.Stop() 569 err := s.o.Settle(5 * time.Second) 570 st.Lock() 571 c.Assert(err, IsNil) 572 573 c.Assert(t.Status(), Equals, state.ErrorStatus) 574 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*no such service: baz.*`) 575 } 576 577 func (s *serviceControlSuite) TestNotAService(c *C) { 578 st := s.state 579 st.Lock() 580 defer st.Unlock() 581 582 s.mockTestSnap(c) 583 584 chg := st.NewChange("service-control", "...") 585 t := st.NewTask("service-control", "...") 586 cmd := &servicestate.ServiceAction{ 587 SnapName: "test-snap", 588 Action: "start", 589 Services: []string{"someapp"}, 590 } 591 t.Set("service-action", cmd) 592 chg.AddTask(t) 593 594 st.Unlock() 595 defer s.se.Stop() 596 err := s.o.Settle(5 * time.Second) 597 st.Lock() 598 c.Assert(err, IsNil) 599 600 c.Assert(t.Status(), Equals, state.ErrorStatus) 601 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*someapp is not a service.*`) 602 } 603 604 func (s *serviceControlSuite) TestStartAllServices(c *C) { 605 st := s.state 606 st.Lock() 607 defer st.Unlock() 608 609 s.mockTestSnap(c) 610 611 chg := st.NewChange("service-control", "...") 612 t := st.NewTask("service-control", "...") 613 cmd := &servicestate.ServiceAction{ 614 SnapName: "test-snap", 615 Action: "start", 616 } 617 t.Set("service-action", cmd) 618 chg.AddTask(t) 619 620 st.Unlock() 621 defer s.se.Stop() 622 err := s.o.Settle(5 * time.Second) 623 st.Lock() 624 c.Assert(err, IsNil) 625 626 c.Assert(t.Status(), Equals, state.DoneStatus) 627 628 /* We don't care about the order of the is-enabled invocations, as long as 629 * they all happen before the first invocation of "start" */ 630 verifyUnsortedInvocations(c, s.sysctlArgs[:3], "is-enabled", []string{ 631 "snap.test-snap.abc.service", 632 "snap.test-snap.bar.service", 633 "snap.test-snap.foo.service", 634 }) 635 c.Check(s.sysctlArgs[3:], DeepEquals, [][]string{ 636 {"start", "snap.test-snap.foo.service"}, 637 {"start", "snap.test-snap.bar.service"}, 638 {"start", "snap.test-snap.abc.service"}, 639 }) 640 } 641 642 func (s *serviceControlSuite) TestStartListedServices(c *C) { 643 st := s.state 644 st.Lock() 645 defer st.Unlock() 646 647 s.mockTestSnap(c) 648 649 chg := st.NewChange("service-control", "...") 650 t := st.NewTask("service-control", "...") 651 cmd := &servicestate.ServiceAction{ 652 SnapName: "test-snap", 653 Action: "start", 654 Services: []string{"foo"}, 655 } 656 t.Set("service-action", cmd) 657 chg.AddTask(t) 658 659 st.Unlock() 660 defer s.se.Stop() 661 err := s.o.Settle(5 * time.Second) 662 st.Lock() 663 c.Assert(err, IsNil) 664 665 c.Assert(t.Status(), Equals, state.DoneStatus) 666 verifyUnsortedInvocations(c, s.sysctlArgs[:3], "is-enabled", []string{ 667 "snap.test-snap.abc.service", 668 "snap.test-snap.bar.service", 669 "snap.test-snap.foo.service", 670 }) 671 c.Check(s.sysctlArgs[3:], DeepEquals, [][]string{ 672 {"start", "snap.test-snap.foo.service"}, 673 }) 674 } 675 676 func (s *serviceControlSuite) TestStartEnableServices(c *C) { 677 st := s.state 678 st.Lock() 679 defer st.Unlock() 680 681 s.mockTestSnap(c) 682 683 chg := st.NewChange("service-control", "...") 684 t := st.NewTask("service-control", "...") 685 cmd := &servicestate.ServiceAction{ 686 SnapName: "test-snap", 687 Action: "start", 688 ActionModifier: "enable", 689 Services: []string{"foo"}, 690 } 691 t.Set("service-action", cmd) 692 chg.AddTask(t) 693 694 st.Unlock() 695 defer s.se.Stop() 696 err := s.o.Settle(5 * time.Second) 697 st.Lock() 698 c.Assert(err, IsNil) 699 700 c.Assert(t.Status(), Equals, state.DoneStatus) 701 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 702 {"enable", "snap.test-snap.foo.service"}, 703 {"start", "snap.test-snap.foo.service"}, 704 }) 705 } 706 707 func (s *serviceControlSuite) TestStartEnableMultipleServices(c *C) { 708 st := s.state 709 st.Lock() 710 defer st.Unlock() 711 712 s.mockTestSnap(c) 713 714 chg := st.NewChange("service-control", "...") 715 t := st.NewTask("service-control", "...") 716 cmd := &servicestate.ServiceAction{ 717 SnapName: "test-snap", 718 Action: "start", 719 ActionModifier: "enable", 720 } 721 t.Set("service-action", cmd) 722 chg.AddTask(t) 723 724 st.Unlock() 725 defer s.se.Stop() 726 err := s.o.Settle(5 * time.Second) 727 st.Lock() 728 c.Assert(err, IsNil) 729 730 c.Assert(t.Status(), Equals, state.DoneStatus) 731 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 732 {"enable", "snap.test-snap.foo.service"}, 733 {"enable", "snap.test-snap.bar.service"}, 734 {"enable", "snap.test-snap.abc.service"}, 735 {"start", "snap.test-snap.foo.service"}, 736 {"start", "snap.test-snap.bar.service"}, 737 {"start", "snap.test-snap.abc.service"}, 738 }) 739 } 740 741 func (s *serviceControlSuite) TestStopServices(c *C) { 742 st := s.state 743 st.Lock() 744 defer st.Unlock() 745 746 s.mockTestSnap(c) 747 748 chg := st.NewChange("service-control", "...") 749 t := st.NewTask("service-control", "...") 750 cmd := &servicestate.ServiceAction{ 751 SnapName: "test-snap", 752 Action: "stop", 753 Services: []string{"foo"}, 754 } 755 t.Set("service-action", cmd) 756 chg.AddTask(t) 757 758 st.Unlock() 759 defer s.se.Stop() 760 err := s.o.Settle(5 * time.Second) 761 st.Lock() 762 c.Assert(err, IsNil) 763 764 c.Assert(t.Status(), Equals, state.DoneStatus) 765 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 766 {"stop", "snap.test-snap.foo.service"}, 767 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 768 }) 769 } 770 771 func (s *serviceControlSuite) TestStopDisableServices(c *C) { 772 st := s.state 773 st.Lock() 774 defer st.Unlock() 775 776 s.mockTestSnap(c) 777 778 chg := st.NewChange("service-control", "...") 779 t := st.NewTask("service-control", "...") 780 cmd := &servicestate.ServiceAction{ 781 SnapName: "test-snap", 782 Action: "stop", 783 ActionModifier: "disable", 784 Services: []string{"foo"}, 785 } 786 t.Set("service-action", cmd) 787 chg.AddTask(t) 788 789 st.Unlock() 790 defer s.se.Stop() 791 err := s.o.Settle(5 * time.Second) 792 st.Lock() 793 c.Assert(err, IsNil) 794 795 c.Assert(t.Status(), Equals, state.DoneStatus) 796 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 797 {"stop", "snap.test-snap.foo.service"}, 798 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 799 {"disable", "snap.test-snap.foo.service"}, 800 }) 801 } 802 803 func (s *serviceControlSuite) TestRestartServices(c *C) { 804 st := s.state 805 st.Lock() 806 defer st.Unlock() 807 808 s.mockTestSnap(c) 809 810 chg := st.NewChange("service-control", "...") 811 t := st.NewTask("service-control", "...") 812 cmd := &servicestate.ServiceAction{ 813 SnapName: "test-snap", 814 Action: "restart", 815 Services: []string{"foo"}, 816 } 817 t.Set("service-action", cmd) 818 chg.AddTask(t) 819 820 st.Unlock() 821 defer s.se.Stop() 822 err := s.o.Settle(5 * time.Second) 823 st.Lock() 824 c.Assert(err, IsNil) 825 826 c.Assert(t.Status(), Equals, state.DoneStatus) 827 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 828 {"stop", "snap.test-snap.foo.service"}, 829 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 830 {"start", "snap.test-snap.foo.service"}, 831 }) 832 } 833 834 func (s *serviceControlSuite) testRestartWithExplicitServicesCommon(c *C, 835 explicitServices []string, expectedInvocations [][]string) { 836 st := s.state 837 st.Lock() 838 defer st.Unlock() 839 840 s.mockTestSnap(c) 841 842 srvAbc := "snap.test-snap.abc.service" 843 srvFoo := "snap.test-snap.foo.service" 844 srvBar := "snap.test-snap.bar.service" 845 846 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) (buf []byte, err error) { 847 states := map[string]systemdtest.ServiceState{ 848 srvAbc: {ActiveState: "inactive", UnitFileState: "enabled"}, 849 srvFoo: {ActiveState: "inactive", UnitFileState: "enabled"}, 850 srvBar: {ActiveState: "active", UnitFileState: "enabled"}, 851 } 852 if out := systemdtest.HandleMockAllUnitsActiveOutput(cmd, states); out != nil { 853 return out, nil 854 } 855 856 s.sysctlArgs = append(s.sysctlArgs, cmd) 857 if cmd[0] == "show" { 858 return []byte("ActiveState=inactive\n"), nil 859 } 860 return nil, nil 861 }) 862 s.AddCleanup(systemctlRestorer) 863 864 chg := st.NewChange("service-control", "...") 865 t := st.NewTask("service-control", "...") 866 cmd := &servicestate.ServiceAction{ 867 SnapName: "test-snap", 868 Action: "restart", 869 Services: []string{"abc", "foo", "bar"}, 870 // these services will be restarted even if inactive 871 ExplicitServices: explicitServices, 872 } 873 t.Set("service-action", cmd) 874 chg.AddTask(t) 875 876 st.Unlock() 877 defer s.se.Stop() 878 err := s.o.Settle(5 * time.Second) 879 st.Lock() 880 c.Assert(err, IsNil) 881 882 c.Assert(t.Status(), Equals, state.DoneStatus) 883 // We expect to get foo and bar restarted: "bar" because it was already 884 // running and "foo" because it was explicitly mentioned (despite being 885 // inactive). "abc" was not running and not explicitly mentioned, so it 886 // shouldn't get restarted. 887 c.Check(s.sysctlArgs, DeepEquals, expectedInvocations) 888 } 889 890 func (s *serviceControlSuite) TestRestartWithSomeExplicitServices(c *C) { 891 srvFoo := "snap.test-snap.foo.service" 892 srvBar := "snap.test-snap.bar.service" 893 s.testRestartWithExplicitServicesCommon(c, 894 []string{srvFoo}, 895 [][]string{ 896 {"stop", srvFoo}, 897 {"show", "--property=ActiveState", srvFoo}, 898 {"start", srvFoo}, 899 {"stop", srvBar}, 900 {"show", "--property=ActiveState", srvBar}, 901 {"start", srvBar}, 902 }) 903 } 904 905 func (s *serviceControlSuite) TestRestartWithAllExplicitServices(c *C) { 906 srvAbc := "snap.test-snap.abc.service" 907 srvFoo := "snap.test-snap.foo.service" 908 srvBar := "snap.test-snap.bar.service" 909 s.testRestartWithExplicitServicesCommon(c, 910 []string{srvAbc, srvBar, srvFoo}, 911 [][]string{ 912 {"stop", srvFoo}, 913 {"show", "--property=ActiveState", srvFoo}, 914 {"start", srvFoo}, 915 {"stop", srvBar}, 916 {"show", "--property=ActiveState", srvBar}, 917 {"start", srvBar}, 918 {"stop", srvAbc}, 919 {"show", "--property=ActiveState", srvAbc}, 920 {"start", srvAbc}, 921 }) 922 } 923 924 func (s *serviceControlSuite) TestRestartAllServices(c *C) { 925 st := s.state 926 st.Lock() 927 defer st.Unlock() 928 929 s.mockTestSnap(c) 930 931 chg := st.NewChange("service-control", "...") 932 t := st.NewTask("service-control", "...") 933 cmd := &servicestate.ServiceAction{ 934 SnapName: "test-snap", 935 Action: "restart", 936 Services: []string{"abc", "foo", "bar"}, 937 } 938 t.Set("service-action", cmd) 939 chg.AddTask(t) 940 941 st.Unlock() 942 defer s.se.Stop() 943 err := s.o.Settle(5 * time.Second) 944 st.Lock() 945 c.Assert(err, IsNil) 946 947 c.Assert(t.Status(), Equals, state.DoneStatus) 948 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 949 {"stop", "snap.test-snap.foo.service"}, 950 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 951 {"start", "snap.test-snap.foo.service"}, 952 {"stop", "snap.test-snap.bar.service"}, 953 {"show", "--property=ActiveState", "snap.test-snap.bar.service"}, 954 {"start", "snap.test-snap.bar.service"}, 955 {"stop", "snap.test-snap.abc.service"}, 956 {"show", "--property=ActiveState", "snap.test-snap.abc.service"}, 957 {"start", "snap.test-snap.abc.service"}, 958 }) 959 } 960 961 func (s *serviceControlSuite) TestReloadServices(c *C) { 962 st := s.state 963 st.Lock() 964 defer st.Unlock() 965 966 s.mockTestSnap(c) 967 968 chg := st.NewChange("service-control", "...") 969 t := st.NewTask("service-control", "...") 970 cmd := &servicestate.ServiceAction{ 971 SnapName: "test-snap", 972 Action: "reload-or-restart", 973 Services: []string{"foo"}, 974 } 975 t.Set("service-action", cmd) 976 chg.AddTask(t) 977 978 st.Unlock() 979 defer s.se.Stop() 980 err := s.o.Settle(5 * time.Second) 981 st.Lock() 982 c.Assert(err, IsNil) 983 984 c.Assert(t.Status(), Equals, state.DoneStatus) 985 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 986 {"reload-or-restart", "snap.test-snap.foo.service"}, 987 }) 988 } 989 990 func (s *serviceControlSuite) TestReloadAllServices(c *C) { 991 st := s.state 992 st.Lock() 993 defer st.Unlock() 994 995 s.mockTestSnap(c) 996 997 chg := st.NewChange("service-control", "...") 998 t := st.NewTask("service-control", "...") 999 cmd := &servicestate.ServiceAction{ 1000 SnapName: "test-snap", 1001 Action: "reload-or-restart", 1002 Services: []string{"foo", "abc", "bar"}, 1003 } 1004 t.Set("service-action", cmd) 1005 chg.AddTask(t) 1006 1007 st.Unlock() 1008 defer s.se.Stop() 1009 err := s.o.Settle(5 * time.Second) 1010 st.Lock() 1011 c.Assert(err, IsNil) 1012 1013 c.Assert(t.Status(), Equals, state.DoneStatus) 1014 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 1015 {"reload-or-restart", "snap.test-snap.foo.service"}, 1016 {"reload-or-restart", "snap.test-snap.bar.service"}, 1017 {"reload-or-restart", "snap.test-snap.abc.service"}, 1018 }) 1019 } 1020 1021 func (s *serviceControlSuite) TestConflict(c *C) { 1022 st := s.state 1023 st.Lock() 1024 defer st.Unlock() 1025 1026 s.mockTestSnap(c) 1027 1028 chg := st.NewChange("service-control", "...") 1029 t := st.NewTask("service-control", "...") 1030 cmd := &servicestate.ServiceAction{ 1031 SnapName: "test-snap", 1032 Action: "reload-or-restart", 1033 Services: []string{"foo"}, 1034 } 1035 t.Set("service-action", cmd) 1036 chg.AddTask(t) 1037 1038 _, err := snapstate.Remove(st, "test-snap", snap.Revision{}, nil) 1039 c.Assert(err, ErrorMatches, `snap "test-snap" has "service-control" change in progress`) 1040 } 1041 1042 func (s *serviceControlSuite) TestUpdateSnapstateServices(c *C) { 1043 var tests = []struct { 1044 enable []string 1045 disable []string 1046 expectedSnapstateEnabled []string 1047 expectedSnapstateDisabled []string 1048 changed bool 1049 }{ 1050 // These test scenarios share a single SnapState instance and accumulate 1051 // changes to ServicesEnabledByHooks and ServicesDisabledByHooks. 1052 { 1053 changed: false, 1054 }, 1055 { 1056 enable: []string{"a"}, 1057 expectedSnapstateEnabled: []string{"a"}, 1058 changed: true, 1059 }, 1060 // enable again does nothing 1061 { 1062 enable: []string{"a"}, 1063 expectedSnapstateEnabled: []string{"a"}, 1064 changed: false, 1065 }, 1066 { 1067 disable: []string{"a"}, 1068 expectedSnapstateDisabled: []string{"a"}, 1069 changed: true, 1070 }, 1071 { 1072 enable: []string{"a", "c"}, 1073 expectedSnapstateEnabled: []string{"a", "c"}, 1074 changed: true, 1075 }, 1076 { 1077 disable: []string{"b"}, 1078 expectedSnapstateEnabled: []string{"a", "c"}, 1079 expectedSnapstateDisabled: []string{"b"}, 1080 changed: true, 1081 }, 1082 { 1083 disable: []string{"b", "c"}, 1084 expectedSnapstateEnabled: []string{"a"}, 1085 expectedSnapstateDisabled: []string{"b", "c"}, 1086 changed: true, 1087 }, 1088 } 1089 1090 snapst := snapstate.SnapState{} 1091 1092 for _, tst := range tests { 1093 var enable, disable []*snap.AppInfo 1094 for _, srv := range tst.enable { 1095 enable = append(enable, &snap.AppInfo{Name: srv}) 1096 } 1097 for _, srv := range tst.disable { 1098 disable = append(disable, &snap.AppInfo{Name: srv}) 1099 } 1100 result, err := servicestate.UpdateSnapstateServices(&snapst, enable, disable) 1101 c.Assert(err, IsNil) 1102 c.Check(result, Equals, tst.changed) 1103 c.Check(snapst.ServicesEnabledByHooks, DeepEquals, tst.expectedSnapstateEnabled) 1104 c.Check(snapst.ServicesDisabledByHooks, DeepEquals, tst.expectedSnapstateDisabled) 1105 } 1106 1107 services := []*snap.AppInfo{{Name: "foo"}} 1108 _, err := servicestate.UpdateSnapstateServices(nil, services, services) 1109 c.Assert(err, ErrorMatches, `internal error: cannot handle enabled and disabled services at the same time`) 1110 }