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