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