github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/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 defer st.Unlock() 461 462 chg := st.NewChange("service-control", "...") 463 t := st.NewTask("service-control", "...") 464 chg.AddTask(t) 465 466 st.Unlock() 467 defer s.se.Stop() 468 err := s.o.Settle(5 * time.Second) 469 st.Lock() 470 c.Assert(err, IsNil) 471 472 c.Assert(t.Status(), Equals, state.ErrorStatus) 473 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*internal error: cannot get service-action: no state entry for key.*`) 474 } 475 476 func (s *serviceControlSuite) TestNoopWhenNoServices(c *C) { 477 st := s.state 478 st.Lock() 479 defer st.Unlock() 480 481 si := snap.SideInfo{RealName: "test-snap", Revision: snap.R(7)} 482 snaptest.MockSnap(c, `name: test-snap`, &si) 483 snapstate.Set(st, "test-snap", &snapstate.SnapState{ 484 Active: true, 485 Sequence: []*snap.SideInfo{&si}, 486 Current: snap.R(7), 487 SnapType: "app", 488 }) 489 490 chg := st.NewChange("service-control", "...") 491 t := st.NewTask("service-control", "...") 492 cmd := &servicestate.ServiceAction{SnapName: "test-snap"} 493 t.Set("service-action", cmd) 494 chg.AddTask(t) 495 496 st.Unlock() 497 defer s.se.Stop() 498 err := s.o.Settle(5 * time.Second) 499 st.Lock() 500 c.Assert(err, IsNil) 501 502 c.Check(t.Status(), Equals, state.DoneStatus) 503 } 504 505 func (s *serviceControlSuite) TestUnhandledServiceAction(c *C) { 506 st := s.state 507 st.Lock() 508 defer st.Unlock() 509 510 s.mockTestSnap(c) 511 512 chg := st.NewChange("service-control", "...") 513 t := st.NewTask("service-control", "...") 514 cmd := &servicestate.ServiceAction{SnapName: "test-snap", Action: "foo"} 515 t.Set("service-action", cmd) 516 chg.AddTask(t) 517 518 st.Unlock() 519 defer s.se.Stop() 520 err := s.o.Settle(5 * time.Second) 521 st.Lock() 522 c.Assert(err, IsNil) 523 524 c.Assert(t.Status(), Equals, state.ErrorStatus) 525 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*unhandled service action: "foo".*`) 526 } 527 528 func (s *serviceControlSuite) TestUnknownService(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{ 538 SnapName: "test-snap", 539 Action: "start", 540 Services: []string{"baz"}, 541 } 542 t.Set("service-action", cmd) 543 chg.AddTask(t) 544 545 st.Unlock() 546 defer s.se.Stop() 547 err := s.o.Settle(5 * time.Second) 548 st.Lock() 549 c.Assert(err, IsNil) 550 551 c.Assert(t.Status(), Equals, state.ErrorStatus) 552 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*no such service: baz.*`) 553 } 554 555 func (s *serviceControlSuite) TestNotAService(c *C) { 556 st := s.state 557 st.Lock() 558 defer st.Unlock() 559 560 s.mockTestSnap(c) 561 562 chg := st.NewChange("service-control", "...") 563 t := st.NewTask("service-control", "...") 564 cmd := &servicestate.ServiceAction{ 565 SnapName: "test-snap", 566 Action: "start", 567 Services: []string{"someapp"}, 568 } 569 t.Set("service-action", cmd) 570 chg.AddTask(t) 571 572 st.Unlock() 573 defer s.se.Stop() 574 err := s.o.Settle(5 * time.Second) 575 st.Lock() 576 c.Assert(err, IsNil) 577 578 c.Assert(t.Status(), Equals, state.ErrorStatus) 579 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*someapp is not a service.*`) 580 } 581 582 func (s *serviceControlSuite) TestStartAllServices(c *C) { 583 st := s.state 584 st.Lock() 585 defer st.Unlock() 586 587 s.mockTestSnap(c) 588 589 chg := st.NewChange("service-control", "...") 590 t := st.NewTask("service-control", "...") 591 cmd := &servicestate.ServiceAction{ 592 SnapName: "test-snap", 593 Action: "start", 594 } 595 t.Set("service-action", cmd) 596 chg.AddTask(t) 597 598 st.Unlock() 599 defer s.se.Stop() 600 err := s.o.Settle(5 * time.Second) 601 st.Lock() 602 c.Assert(err, IsNil) 603 604 c.Assert(t.Status(), Equals, state.DoneStatus) 605 606 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 607 {"start", "snap.test-snap.foo.service"}, 608 {"start", "snap.test-snap.bar.service"}, 609 {"start", "snap.test-snap.abc.service"}, 610 }) 611 } 612 613 func (s *serviceControlSuite) TestStartListedServices(c *C) { 614 st := s.state 615 st.Lock() 616 defer st.Unlock() 617 618 s.mockTestSnap(c) 619 620 chg := st.NewChange("service-control", "...") 621 t := st.NewTask("service-control", "...") 622 cmd := &servicestate.ServiceAction{ 623 SnapName: "test-snap", 624 Action: "start", 625 Services: []string{"foo"}, 626 } 627 t.Set("service-action", cmd) 628 chg.AddTask(t) 629 630 st.Unlock() 631 defer s.se.Stop() 632 err := s.o.Settle(5 * time.Second) 633 st.Lock() 634 c.Assert(err, IsNil) 635 636 c.Assert(t.Status(), Equals, state.DoneStatus) 637 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 638 {"start", "snap.test-snap.foo.service"}, 639 }) 640 } 641 642 func (s *serviceControlSuite) TestStartEnableServices(c *C) { 643 st := s.state 644 st.Lock() 645 defer st.Unlock() 646 647 s.mockTestSnap(c) 648 649 chg := st.NewChange("service-control", "...") 650 t := st.NewTask("service-control", "...") 651 cmd := &servicestate.ServiceAction{ 652 SnapName: "test-snap", 653 Action: "start", 654 ActionModifier: "enable", 655 Services: []string{"foo"}, 656 } 657 t.Set("service-action", cmd) 658 chg.AddTask(t) 659 660 st.Unlock() 661 defer s.se.Stop() 662 err := s.o.Settle(5 * time.Second) 663 st.Lock() 664 c.Assert(err, IsNil) 665 666 c.Assert(t.Status(), Equals, state.DoneStatus) 667 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 668 {"enable", "snap.test-snap.foo.service"}, 669 {"start", "snap.test-snap.foo.service"}, 670 }) 671 } 672 673 func (s *serviceControlSuite) TestStartEnableMultipleServices(c *C) { 674 st := s.state 675 st.Lock() 676 defer st.Unlock() 677 678 s.mockTestSnap(c) 679 680 chg := st.NewChange("service-control", "...") 681 t := st.NewTask("service-control", "...") 682 cmd := &servicestate.ServiceAction{ 683 SnapName: "test-snap", 684 Action: "start", 685 ActionModifier: "enable", 686 } 687 t.Set("service-action", cmd) 688 chg.AddTask(t) 689 690 st.Unlock() 691 defer s.se.Stop() 692 err := s.o.Settle(5 * time.Second) 693 st.Lock() 694 c.Assert(err, IsNil) 695 696 c.Assert(t.Status(), Equals, state.DoneStatus) 697 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 698 {"enable", "snap.test-snap.foo.service"}, 699 {"enable", "snap.test-snap.bar.service"}, 700 {"enable", "snap.test-snap.abc.service"}, 701 {"start", "snap.test-snap.foo.service"}, 702 {"start", "snap.test-snap.bar.service"}, 703 {"start", "snap.test-snap.abc.service"}, 704 }) 705 } 706 707 func (s *serviceControlSuite) TestStopServices(c *C) { 708 st := s.state 709 st.Lock() 710 defer st.Unlock() 711 712 s.mockTestSnap(c) 713 714 chg := st.NewChange("service-control", "...") 715 t := st.NewTask("service-control", "...") 716 cmd := &servicestate.ServiceAction{ 717 SnapName: "test-snap", 718 Action: "stop", 719 Services: []string{"foo"}, 720 } 721 t.Set("service-action", cmd) 722 chg.AddTask(t) 723 724 st.Unlock() 725 defer s.se.Stop() 726 err := s.o.Settle(5 * time.Second) 727 st.Lock() 728 c.Assert(err, IsNil) 729 730 c.Assert(t.Status(), Equals, state.DoneStatus) 731 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 732 {"stop", "snap.test-snap.foo.service"}, 733 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 734 }) 735 } 736 737 func (s *serviceControlSuite) TestStopDisableServices(c *C) { 738 st := s.state 739 st.Lock() 740 defer st.Unlock() 741 742 s.mockTestSnap(c) 743 744 chg := st.NewChange("service-control", "...") 745 t := st.NewTask("service-control", "...") 746 cmd := &servicestate.ServiceAction{ 747 SnapName: "test-snap", 748 Action: "stop", 749 ActionModifier: "disable", 750 Services: []string{"foo"}, 751 } 752 t.Set("service-action", cmd) 753 chg.AddTask(t) 754 755 st.Unlock() 756 defer s.se.Stop() 757 err := s.o.Settle(5 * time.Second) 758 st.Lock() 759 c.Assert(err, IsNil) 760 761 c.Assert(t.Status(), Equals, state.DoneStatus) 762 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 763 {"stop", "snap.test-snap.foo.service"}, 764 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 765 {"disable", "snap.test-snap.foo.service"}, 766 }) 767 } 768 769 func (s *serviceControlSuite) TestRestartServices(c *C) { 770 st := s.state 771 st.Lock() 772 defer st.Unlock() 773 774 s.mockTestSnap(c) 775 776 chg := st.NewChange("service-control", "...") 777 t := st.NewTask("service-control", "...") 778 cmd := &servicestate.ServiceAction{ 779 SnapName: "test-snap", 780 Action: "restart", 781 Services: []string{"foo"}, 782 } 783 t.Set("service-action", cmd) 784 chg.AddTask(t) 785 786 st.Unlock() 787 defer s.se.Stop() 788 err := s.o.Settle(5 * time.Second) 789 st.Lock() 790 c.Assert(err, IsNil) 791 792 c.Assert(t.Status(), Equals, state.DoneStatus) 793 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 794 {"stop", "snap.test-snap.foo.service"}, 795 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 796 {"start", "snap.test-snap.foo.service"}, 797 }) 798 } 799 800 func (s *serviceControlSuite) TestRestartAllServices(c *C) { 801 st := s.state 802 st.Lock() 803 defer st.Unlock() 804 805 s.mockTestSnap(c) 806 807 chg := st.NewChange("service-control", "...") 808 t := st.NewTask("service-control", "...") 809 cmd := &servicestate.ServiceAction{ 810 SnapName: "test-snap", 811 Action: "restart", 812 Services: []string{"abc", "foo", "bar"}, 813 } 814 t.Set("service-action", cmd) 815 chg.AddTask(t) 816 817 st.Unlock() 818 defer s.se.Stop() 819 err := s.o.Settle(5 * time.Second) 820 st.Lock() 821 c.Assert(err, IsNil) 822 823 c.Assert(t.Status(), Equals, state.DoneStatus) 824 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 825 {"stop", "snap.test-snap.foo.service"}, 826 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 827 {"start", "snap.test-snap.foo.service"}, 828 {"stop", "snap.test-snap.bar.service"}, 829 {"show", "--property=ActiveState", "snap.test-snap.bar.service"}, 830 {"start", "snap.test-snap.bar.service"}, 831 {"stop", "snap.test-snap.abc.service"}, 832 {"show", "--property=ActiveState", "snap.test-snap.abc.service"}, 833 {"start", "snap.test-snap.abc.service"}, 834 }) 835 } 836 837 func (s *serviceControlSuite) TestReloadServices(c *C) { 838 st := s.state 839 st.Lock() 840 defer st.Unlock() 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"}, 850 } 851 t.Set("service-action", cmd) 852 chg.AddTask(t) 853 854 st.Unlock() 855 defer s.se.Stop() 856 err := s.o.Settle(5 * time.Second) 857 st.Lock() 858 c.Assert(err, IsNil) 859 860 c.Assert(t.Status(), Equals, state.DoneStatus) 861 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 862 {"reload-or-restart", "snap.test-snap.foo.service"}, 863 }) 864 } 865 866 func (s *serviceControlSuite) TestReloadAllServices(c *C) { 867 st := s.state 868 st.Lock() 869 defer st.Unlock() 870 871 s.mockTestSnap(c) 872 873 chg := st.NewChange("service-control", "...") 874 t := st.NewTask("service-control", "...") 875 cmd := &servicestate.ServiceAction{ 876 SnapName: "test-snap", 877 Action: "reload-or-restart", 878 Services: []string{"foo", "abc", "bar"}, 879 } 880 t.Set("service-action", cmd) 881 chg.AddTask(t) 882 883 st.Unlock() 884 defer s.se.Stop() 885 err := s.o.Settle(5 * time.Second) 886 st.Lock() 887 c.Assert(err, IsNil) 888 889 c.Assert(t.Status(), Equals, state.DoneStatus) 890 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 891 {"reload-or-restart", "snap.test-snap.foo.service"}, 892 {"reload-or-restart", "snap.test-snap.bar.service"}, 893 {"reload-or-restart", "snap.test-snap.abc.service"}, 894 }) 895 } 896 897 func (s *serviceControlSuite) TestConflict(c *C) { 898 st := s.state 899 st.Lock() 900 defer st.Unlock() 901 902 s.mockTestSnap(c) 903 904 chg := st.NewChange("service-control", "...") 905 t := st.NewTask("service-control", "...") 906 cmd := &servicestate.ServiceAction{ 907 SnapName: "test-snap", 908 Action: "reload-or-restart", 909 Services: []string{"foo"}, 910 } 911 t.Set("service-action", cmd) 912 chg.AddTask(t) 913 914 _, err := snapstate.Remove(st, "test-snap", snap.Revision{}, nil) 915 c.Assert(err, ErrorMatches, `snap "test-snap" has "service-control" change in progress`) 916 } 917 918 func (s *serviceControlSuite) TestUpdateSnapstateServices(c *C) { 919 var tests = []struct { 920 enable []string 921 disable []string 922 expectedSnapstateEnabled []string 923 expectedSnapstateDisabled []string 924 changed bool 925 }{ 926 // These test scenarios share a single SnapState instance and accumulate 927 // changes to ServicesEnabledByHooks and ServicesDisabledByHooks. 928 { 929 changed: false, 930 }, 931 { 932 enable: []string{"a"}, 933 expectedSnapstateEnabled: []string{"a"}, 934 changed: true, 935 }, 936 // enable again does nothing 937 { 938 enable: []string{"a"}, 939 expectedSnapstateEnabled: []string{"a"}, 940 changed: false, 941 }, 942 { 943 disable: []string{"a"}, 944 expectedSnapstateDisabled: []string{"a"}, 945 changed: true, 946 }, 947 { 948 enable: []string{"a", "c"}, 949 expectedSnapstateEnabled: []string{"a", "c"}, 950 changed: true, 951 }, 952 { 953 disable: []string{"b"}, 954 expectedSnapstateEnabled: []string{"a", "c"}, 955 expectedSnapstateDisabled: []string{"b"}, 956 changed: true, 957 }, 958 { 959 disable: []string{"b", "c"}, 960 expectedSnapstateEnabled: []string{"a"}, 961 expectedSnapstateDisabled: []string{"b", "c"}, 962 changed: true, 963 }, 964 } 965 966 snapst := snapstate.SnapState{} 967 968 for _, tst := range tests { 969 var enable, disable []*snap.AppInfo 970 for _, srv := range tst.enable { 971 enable = append(enable, &snap.AppInfo{Name: srv}) 972 } 973 for _, srv := range tst.disable { 974 disable = append(disable, &snap.AppInfo{Name: srv}) 975 } 976 result, err := servicestate.UpdateSnapstateServices(&snapst, enable, disable) 977 c.Assert(err, IsNil) 978 c.Check(result, Equals, tst.changed) 979 c.Check(snapst.ServicesEnabledByHooks, DeepEquals, tst.expectedSnapstateEnabled) 980 c.Check(snapst.ServicesDisabledByHooks, DeepEquals, tst.expectedSnapstateDisabled) 981 } 982 983 services := []*snap.AppInfo{{Name: "foo"}} 984 _, err := servicestate.UpdateSnapstateServices(nil, services, services) 985 c.Assert(err, ErrorMatches, `internal error: cannot handle enabled and disabled services at the same time`) 986 }