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