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