github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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 "testing" 27 "time" 28 29 . "gopkg.in/check.v1" 30 31 "github.com/snapcore/snapd/client" 32 "github.com/snapcore/snapd/dirs" 33 "github.com/snapcore/snapd/overlord" 34 "github.com/snapcore/snapd/overlord/servicestate" 35 "github.com/snapcore/snapd/overlord/snapstate" 36 "github.com/snapcore/snapd/overlord/state" 37 "github.com/snapcore/snapd/snap" 38 "github.com/snapcore/snapd/snap/snaptest" 39 "github.com/snapcore/snapd/systemd" 40 "github.com/snapcore/snapd/testutil" 41 ) 42 43 func TestServiceControl(t *testing.T) { TestingT(t) } 44 45 type serviceControlSuite struct { 46 testutil.BaseTest 47 state *state.State 48 o *overlord.Overlord 49 se *overlord.StateEngine 50 serviceMgr *servicestate.ServiceManager 51 sysctlArgs [][]string 52 } 53 54 var _ = Suite(&serviceControlSuite{}) 55 56 const servicesSnapYaml1 = `name: test-snap 57 version: 1.0 58 apps: 59 someapp: 60 command: cmd 61 foo: 62 daemon: simple 63 bar: 64 daemon: simple 65 after: [foo] 66 ` 67 68 func (s *serviceControlSuite) SetUpTest(c *C) { 69 s.BaseTest.SetUpTest(c) 70 71 dirs.SetRootDir(c.MkDir()) 72 s.AddCleanup(func() { dirs.SetRootDir("") }) 73 74 s.o = overlord.Mock() 75 s.state = s.o.State() 76 77 s.sysctlArgs = nil 78 systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) (buf []byte, err error) { 79 s.sysctlArgs = append(s.sysctlArgs, cmd) 80 if cmd[0] == "show" { 81 return []byte("ActiveState=inactive\n"), nil 82 } 83 return nil, nil 84 }) 85 s.AddCleanup(systemctlRestorer) 86 87 s.serviceMgr = servicestate.Manager(s.state, s.o.TaskRunner()) 88 s.o.AddManager(s.serviceMgr) 89 s.o.AddManager(s.o.TaskRunner()) 90 s.se = s.o.StateEngine() 91 c.Assert(s.o.StartUp(), IsNil) 92 } 93 94 func (s *serviceControlSuite) mockTestSnap(c *C) *snap.Info { 95 si := snap.SideInfo{ 96 RealName: "test-snap", 97 Revision: snap.R(7), 98 } 99 info := snaptest.MockSnap(c, servicesSnapYaml1, &si) 100 snapstate.Set(s.state, "test-snap", &snapstate.SnapState{ 101 Active: true, 102 Sequence: []*snap.SideInfo{&si}, 103 Current: snap.R(7), 104 SnapType: "app", 105 }) 106 107 // mock systemd service units, this is required when testing "stop" 108 err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc/systemd/system/"), 0775) 109 c.Assert(err, IsNil) 110 err = ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "etc/systemd/system/snap.test-snap.foo.service"), nil, 0644) 111 c.Assert(err, IsNil) 112 113 return info 114 } 115 116 func verifyControlTasks(c *C, tasks []*state.Task, expectedAction, supportAction string, expectedServices ...string) { 117 // sanity, ensures test checks below are hit 118 c.Assert(len(tasks) > 0, Equals, true) 119 var i int 120 for i < len(tasks) { 121 var argv []string 122 if tasks[i].Kind() == "exec-command" { 123 switch expectedAction { 124 case "start": 125 if supportAction != "" { 126 c.Assert(tasks[i].Get("argv", &argv), IsNil) 127 c.Check(argv, DeepEquals, append([]string{"systemctl", supportAction}, expectedServices...)) 128 i++ 129 wt := tasks[i].WaitTasks() 130 c.Assert(wt, HasLen, 1) 131 c.Assert(wt[0].ID(), Equals, tasks[i-1].ID()) 132 } 133 c.Assert(tasks[i].Get("argv", &argv), IsNil) 134 c.Check(argv, DeepEquals, append([]string{"systemctl", "start"}, expectedServices...)) 135 case "stop": 136 if supportAction != "" { 137 c.Assert(tasks[i].Get("argv", &argv), IsNil) 138 c.Check(argv, DeepEquals, append([]string{"systemctl", supportAction}, expectedServices...)) 139 i++ 140 wt := tasks[i].WaitTasks() 141 c.Assert(wt, HasLen, 1) 142 c.Assert(wt[0].ID(), Equals, tasks[i-1].ID()) 143 } 144 c.Assert(tasks[i].Get("argv", &argv), IsNil) 145 c.Check(argv, DeepEquals, append([]string{"systemctl", "stop"}, expectedServices...)) 146 case "restart": 147 if supportAction != "" { 148 c.Assert(tasks[i].Get("argv", &argv), IsNil) 149 c.Check(argv, DeepEquals, append([]string{"systemctl", "reload-or-restart"}, expectedServices...)) 150 } else { 151 c.Assert(tasks[i].Get("argv", &argv), IsNil) 152 c.Check(argv, DeepEquals, append([]string{"systemctl", "restart"}, expectedServices...)) 153 } 154 default: 155 c.Fatalf("unhandled action %s", expectedAction) 156 } 157 } else { 158 c.Fatalf("unexpected task: %s", tasks[i].Kind()) 159 } 160 i++ 161 } 162 } 163 164 func makeControlChange(c *C, st *state.State, inst *servicestate.Instruction, info *snap.Info) *state.Change { 165 apps := []*snap.AppInfo{} 166 for _, name := range inst.Names { 167 c.Assert(info.Apps[name], NotNil) 168 apps = append(apps, info.Apps[name]) 169 } 170 st.Unlock() 171 tss, err := servicestate.Control(st, apps, inst, nil) 172 st.Lock() 173 c.Assert(err, IsNil) 174 175 chg := st.NewChange("service-control", "...") 176 for _, ts := range tss { 177 chg.AddAll(ts) 178 } 179 return chg 180 } 181 182 func (s *serviceControlSuite) TestControlConflict(c *C) { 183 st := s.state 184 st.Lock() 185 186 inf := s.mockTestSnap(c) 187 188 // create conflicting change 189 t := st.NewTask("link-snap", "...") 190 snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "test-snap"}} 191 t.Set("snap-setup", snapsup) 192 chg := st.NewChange("manip", "...") 193 chg.AddTask(t) 194 st.Unlock() 195 196 inst := &servicestate.Instruction{Action: "start", Names: []string{"foo"}} 197 _, err := servicestate.Control(st, []*snap.AppInfo{inf.Apps["foo"]}, inst, nil) 198 c.Check(err, ErrorMatches, `snap "test-snap" has "manip" change in progress`) 199 } 200 201 func (s *serviceControlSuite) TestControlStartInstruction(c *C) { 202 st := s.state 203 st.Lock() 204 defer st.Unlock() 205 206 inf := s.mockTestSnap(c) 207 208 inst := &servicestate.Instruction{ 209 Action: "start", 210 Names: []string{"foo"}, 211 } 212 213 chg := makeControlChange(c, st, inst, inf) 214 verifyControlTasks(c, chg.Tasks(), "start", "", "snap.test-snap.foo.service") 215 } 216 217 func (s *serviceControlSuite) TestControlStartEnableMultipleInstruction(c *C) { 218 st := s.state 219 st.Lock() 220 defer st.Unlock() 221 222 inf := s.mockTestSnap(c) 223 224 inst := &servicestate.Instruction{ 225 Action: "start", 226 Names: []string{"foo", "bar"}, 227 StartOptions: client.StartOptions{Enable: true}, 228 } 229 230 chg := makeControlChange(c, st, inst, inf) 231 verifyControlTasks(c, chg.Tasks(), "start", "enable", "snap.test-snap.foo.service", "snap.test-snap.bar.service") 232 } 233 234 func (s *serviceControlSuite) TestControlStopInstruction(c *C) { 235 st := s.state 236 st.Lock() 237 defer st.Unlock() 238 239 inf := s.mockTestSnap(c) 240 241 inst := &servicestate.Instruction{ 242 Action: "stop", 243 Names: []string{"foo"}, 244 } 245 246 chg := makeControlChange(c, st, inst, inf) 247 verifyControlTasks(c, chg.Tasks(), "stop", "", "snap.test-snap.foo.service") 248 } 249 250 func (s *serviceControlSuite) TestControlStopDisableInstruction(c *C) { 251 st := s.state 252 st.Lock() 253 defer st.Unlock() 254 255 inf := s.mockTestSnap(c) 256 257 inst := &servicestate.Instruction{ 258 Action: "stop", 259 Names: []string{"bar"}, 260 StopOptions: client.StopOptions{Disable: true}, 261 } 262 263 chg := makeControlChange(c, st, inst, inf) 264 verifyControlTasks(c, chg.Tasks(), "stop", "disable", "snap.test-snap.bar.service") 265 } 266 267 func (s *serviceControlSuite) TestControlRestartInstruction(c *C) { 268 st := s.state 269 st.Lock() 270 defer st.Unlock() 271 272 inf := s.mockTestSnap(c) 273 274 inst := &servicestate.Instruction{ 275 Action: "restart", 276 Names: []string{"bar"}, 277 } 278 279 chg := makeControlChange(c, st, inst, inf) 280 verifyControlTasks(c, chg.Tasks(), "restart", "", "snap.test-snap.bar.service") 281 } 282 283 func (s *serviceControlSuite) TestControlRestartReloadMultipleInstruction(c *C) { 284 st := s.state 285 st.Lock() 286 defer st.Unlock() 287 288 inf := s.mockTestSnap(c) 289 290 inst := &servicestate.Instruction{ 291 Action: "restart", 292 Names: []string{"foo", "bar"}, 293 RestartOptions: client.RestartOptions{Reload: true}, 294 } 295 296 chg := makeControlChange(c, st, inst, inf) 297 verifyControlTasks(c, chg.Tasks(), "restart", "reload", "snap.test-snap.foo.service", "snap.test-snap.bar.service") 298 } 299 300 func (s *serviceControlSuite) TestControlUnknownInstruction(c *C) { 301 st := s.state 302 st.Lock() 303 defer st.Unlock() 304 305 inst := &servicestate.Instruction{ 306 Action: "boo", 307 Names: []string{"foo"}, 308 RestartOptions: client.RestartOptions{Reload: true}, 309 } 310 311 _, err := servicestate.Control(st, []*snap.AppInfo{}, inst, nil) 312 c.Assert(err, ErrorMatches, `unknown action "boo"`) 313 } 314 315 func (s *serviceControlSuite) TestControlStopDisableMultipleInstruction(c *C) { 316 st := s.state 317 st.Lock() 318 defer st.Unlock() 319 320 inf := s.mockTestSnap(c) 321 322 inst := &servicestate.Instruction{ 323 Action: "stop", 324 Names: []string{"foo", "bar"}, 325 StopOptions: client.StopOptions{Disable: true}, 326 } 327 328 chg := makeControlChange(c, st, inst, inf) 329 verifyControlTasks(c, chg.Tasks(), "stop", "disable", "snap.test-snap.foo.service", "snap.test-snap.bar.service") 330 } 331 332 func (s *serviceControlSuite) TestNoServiceCommandError(c *C) { 333 st := s.state 334 st.Lock() 335 336 chg := st.NewChange("service-control", "...") 337 t := st.NewTask("service-control", "...") 338 chg.AddTask(t) 339 340 st.Unlock() 341 defer s.se.Stop() 342 c.Assert(s.o.Settle(5*time.Second), IsNil) 343 st.Lock() 344 345 c.Assert(t.Status(), Equals, state.ErrorStatus) 346 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*internal error: cannot get service-action: no state entry for key.*`) 347 } 348 349 func (s *serviceControlSuite) TestNoopWhenNoServices(c *C) { 350 st := s.state 351 st.Lock() 352 353 si := snap.SideInfo{RealName: "test-snap", Revision: snap.R(7)} 354 snaptest.MockSnap(c, `name: test-snap`, &si) 355 snapstate.Set(st, "test-snap", &snapstate.SnapState{ 356 Active: true, 357 Sequence: []*snap.SideInfo{&si}, 358 Current: snap.R(7), 359 SnapType: "app", 360 }) 361 362 chg := st.NewChange("service-control", "...") 363 t := st.NewTask("service-control", "...") 364 cmd := &servicestate.ServiceAction{SnapName: "test-snap"} 365 t.Set("service-action", cmd) 366 chg.AddTask(t) 367 368 st.Unlock() 369 defer s.se.Stop() 370 c.Assert(s.o.Settle(5*time.Second), IsNil) 371 st.Lock() 372 373 c.Check(t.Status(), Equals, state.DoneStatus) 374 } 375 376 func (s *serviceControlSuite) TestUnhandledServiceAction(c *C) { 377 st := s.state 378 st.Lock() 379 380 s.mockTestSnap(c) 381 382 chg := st.NewChange("service-control", "...") 383 t := st.NewTask("service-control", "...") 384 cmd := &servicestate.ServiceAction{SnapName: "test-snap", Action: "foo"} 385 t.Set("service-action", cmd) 386 chg.AddTask(t) 387 388 st.Unlock() 389 defer s.se.Stop() 390 c.Assert(s.o.Settle(5*time.Second), IsNil) 391 st.Lock() 392 393 c.Assert(t.Status(), Equals, state.ErrorStatus) 394 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*unhandled service action: "foo".*`) 395 } 396 397 func (s *serviceControlSuite) TestUnknownService(c *C) { 398 st := s.state 399 st.Lock() 400 401 s.mockTestSnap(c) 402 403 chg := st.NewChange("service-control", "...") 404 t := st.NewTask("service-control", "...") 405 cmd := &servicestate.ServiceAction{ 406 SnapName: "test-snap", 407 Action: "start", 408 Services: []string{"baz"}, 409 } 410 t.Set("service-action", cmd) 411 chg.AddTask(t) 412 413 st.Unlock() 414 defer s.se.Stop() 415 c.Assert(s.o.Settle(5*time.Second), IsNil) 416 st.Lock() 417 418 c.Assert(t.Status(), Equals, state.ErrorStatus) 419 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*no such service: baz.*`) 420 } 421 422 func (s *serviceControlSuite) TestNotAService(c *C) { 423 st := s.state 424 st.Lock() 425 426 s.mockTestSnap(c) 427 428 chg := st.NewChange("service-control", "...") 429 t := st.NewTask("service-control", "...") 430 cmd := &servicestate.ServiceAction{ 431 SnapName: "test-snap", 432 Action: "start", 433 Services: []string{"someapp"}, 434 } 435 t.Set("service-action", cmd) 436 chg.AddTask(t) 437 438 st.Unlock() 439 defer s.se.Stop() 440 c.Assert(s.o.Settle(5*time.Second), IsNil) 441 st.Lock() 442 443 c.Assert(t.Status(), Equals, state.ErrorStatus) 444 c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*someapp is not a service.*`) 445 } 446 447 func (s *serviceControlSuite) TestStartAllServices(c *C) { 448 st := s.state 449 st.Lock() 450 451 s.mockTestSnap(c) 452 453 chg := st.NewChange("service-control", "...") 454 t := st.NewTask("service-control", "...") 455 cmd := &servicestate.ServiceAction{ 456 SnapName: "test-snap", 457 Action: "start", 458 } 459 t.Set("service-action", cmd) 460 chg.AddTask(t) 461 462 st.Unlock() 463 defer s.se.Stop() 464 c.Assert(s.o.Settle(5*time.Second), IsNil) 465 st.Lock() 466 467 c.Assert(t.Status(), Equals, state.DoneStatus) 468 469 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 470 {"--root", dirs.GlobalRootDir, "is-enabled", "snap.test-snap.foo.service"}, 471 {"--root", dirs.GlobalRootDir, "is-enabled", "snap.test-snap.bar.service"}, 472 {"start", "snap.test-snap.foo.service"}, 473 {"start", "snap.test-snap.bar.service"}, 474 }) 475 } 476 477 func (s *serviceControlSuite) TestStartListedServices(c *C) { 478 st := s.state 479 st.Lock() 480 481 s.mockTestSnap(c) 482 483 chg := st.NewChange("service-control", "...") 484 t := st.NewTask("service-control", "...") 485 cmd := &servicestate.ServiceAction{ 486 SnapName: "test-snap", 487 Action: "start", 488 Services: []string{"foo"}, 489 } 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.Assert(t.Status(), Equals, state.DoneStatus) 499 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 500 {"--root", dirs.GlobalRootDir, "is-enabled", "snap.test-snap.foo.service"}, 501 {"start", "snap.test-snap.foo.service"}, 502 }) 503 } 504 505 func (s *serviceControlSuite) TestStartEnableServices(c *C) { 506 st := s.state 507 st.Lock() 508 509 s.mockTestSnap(c) 510 511 chg := st.NewChange("service-control", "...") 512 t := st.NewTask("service-control", "...") 513 cmd := &servicestate.ServiceAction{ 514 SnapName: "test-snap", 515 Action: "start", 516 ActionModifier: "enable", 517 Services: []string{"foo"}, 518 } 519 t.Set("service-action", cmd) 520 chg.AddTask(t) 521 522 st.Unlock() 523 defer s.se.Stop() 524 c.Assert(s.o.Settle(5*time.Second), IsNil) 525 st.Lock() 526 527 c.Assert(t.Status(), Equals, state.DoneStatus) 528 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 529 {"--root", dirs.GlobalRootDir, "is-enabled", "snap.test-snap.foo.service"}, 530 {"start", "snap.test-snap.foo.service"}, 531 }) 532 } 533 534 func (s *serviceControlSuite) TestStartEnableMultipleServices(c *C) { 535 st := s.state 536 st.Lock() 537 538 s.mockTestSnap(c) 539 540 chg := st.NewChange("service-control", "...") 541 t := st.NewTask("service-control", "...") 542 cmd := &servicestate.ServiceAction{ 543 SnapName: "test-snap", 544 Action: "start", 545 ActionModifier: "enable", 546 } 547 t.Set("service-action", cmd) 548 chg.AddTask(t) 549 550 st.Unlock() 551 defer s.se.Stop() 552 c.Assert(s.o.Settle(5*time.Second), IsNil) 553 st.Lock() 554 555 c.Assert(t.Status(), Equals, state.DoneStatus) 556 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 557 {"--root", dirs.GlobalRootDir, "is-enabled", "snap.test-snap.foo.service"}, 558 {"--root", dirs.GlobalRootDir, "is-enabled", "snap.test-snap.bar.service"}, 559 {"start", "snap.test-snap.foo.service"}, 560 {"start", "snap.test-snap.bar.service"}, 561 }) 562 } 563 564 func (s *serviceControlSuite) TestStopServices(c *C) { 565 st := s.state 566 st.Lock() 567 568 s.mockTestSnap(c) 569 570 chg := st.NewChange("service-control", "...") 571 t := st.NewTask("service-control", "...") 572 cmd := &servicestate.ServiceAction{ 573 SnapName: "test-snap", 574 Action: "stop", 575 Services: []string{"foo"}, 576 } 577 t.Set("service-action", cmd) 578 chg.AddTask(t) 579 580 st.Unlock() 581 defer s.se.Stop() 582 c.Assert(s.o.Settle(5*time.Second), IsNil) 583 st.Lock() 584 585 c.Assert(t.Status(), Equals, state.DoneStatus) 586 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 587 {"stop", "snap.test-snap.foo.service"}, 588 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 589 }) 590 } 591 592 func (s *serviceControlSuite) TestStopDisableServices(c *C) { 593 st := s.state 594 st.Lock() 595 596 s.mockTestSnap(c) 597 598 chg := st.NewChange("service-control", "...") 599 t := st.NewTask("service-control", "...") 600 cmd := &servicestate.ServiceAction{ 601 SnapName: "test-snap", 602 Action: "stop", 603 ActionModifier: "disable", 604 Services: []string{"foo"}, 605 } 606 t.Set("service-action", cmd) 607 chg.AddTask(t) 608 609 st.Unlock() 610 defer s.se.Stop() 611 c.Assert(s.o.Settle(5*time.Second), IsNil) 612 st.Lock() 613 614 c.Assert(t.Status(), Equals, state.DoneStatus) 615 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 616 {"stop", "snap.test-snap.foo.service"}, 617 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 618 {"--root", dirs.GlobalRootDir, "disable", "snap.test-snap.foo.service"}, 619 }) 620 } 621 622 func (s *serviceControlSuite) TestRestartServices(c *C) { 623 st := s.state 624 st.Lock() 625 626 s.mockTestSnap(c) 627 628 chg := st.NewChange("service-control", "...") 629 t := st.NewTask("service-control", "...") 630 cmd := &servicestate.ServiceAction{ 631 SnapName: "test-snap", 632 Action: "restart", 633 Services: []string{"foo"}, 634 } 635 t.Set("service-action", cmd) 636 chg.AddTask(t) 637 638 st.Unlock() 639 defer s.se.Stop() 640 c.Assert(s.o.Settle(5*time.Second), IsNil) 641 st.Lock() 642 643 c.Assert(t.Status(), Equals, state.DoneStatus) 644 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 645 {"stop", "snap.test-snap.foo.service"}, 646 {"show", "--property=ActiveState", "snap.test-snap.foo.service"}, 647 {"start", "snap.test-snap.foo.service"}, 648 }) 649 } 650 651 func (s *serviceControlSuite) TestReloadServices(c *C) { 652 st := s.state 653 st.Lock() 654 655 s.mockTestSnap(c) 656 657 chg := st.NewChange("service-control", "...") 658 t := st.NewTask("service-control", "...") 659 cmd := &servicestate.ServiceAction{ 660 SnapName: "test-snap", 661 Action: "reload-or-restart", 662 Services: []string{"foo"}, 663 } 664 t.Set("service-action", cmd) 665 chg.AddTask(t) 666 667 st.Unlock() 668 defer s.se.Stop() 669 c.Assert(s.o.Settle(5*time.Second), IsNil) 670 st.Lock() 671 672 c.Assert(t.Status(), Equals, state.DoneStatus) 673 c.Check(s.sysctlArgs, DeepEquals, [][]string{ 674 {"reload-or-restart", "snap.test-snap.foo.service"}, 675 }) 676 } 677 678 func (s *serviceControlSuite) TestConflict(c *C) { 679 st := s.state 680 st.Lock() 681 defer st.Unlock() 682 683 s.mockTestSnap(c) 684 685 chg := st.NewChange("service-control", "...") 686 t := st.NewTask("service-control", "...") 687 cmd := &servicestate.ServiceAction{ 688 SnapName: "test-snap", 689 Action: "reload-or-restart", 690 Services: []string{"foo"}, 691 } 692 t.Set("service-action", cmd) 693 chg.AddTask(t) 694 695 _, err := snapstate.Remove(st, "test-snap", snap.Revision{}, nil) 696 c.Assert(err, ErrorMatches, `snap "test-snap" has "service-control" change in progress`) 697 } 698 699 func (s *serviceControlSuite) TestUpdateSnapstateServices(c *C) { 700 var tests = []struct { 701 enable []string 702 disable []string 703 expectedSnapstateEnabled []string 704 expectedSnapstateDisabled []string 705 changed bool 706 }{ 707 // These test scenarios share a single SnapState instance and accumulate 708 // changes to ServicesEnabledByHooks and ServicesDisabledByHooks. 709 { 710 changed: false, 711 }, 712 { 713 enable: []string{"a"}, 714 expectedSnapstateEnabled: []string{"a"}, 715 changed: true, 716 }, 717 // enable again does nothing 718 { 719 enable: []string{"a"}, 720 expectedSnapstateEnabled: []string{"a"}, 721 changed: false, 722 }, 723 { 724 disable: []string{"a"}, 725 expectedSnapstateDisabled: []string{"a"}, 726 changed: true, 727 }, 728 { 729 enable: []string{"a", "c"}, 730 expectedSnapstateEnabled: []string{"a", "c"}, 731 changed: true, 732 }, 733 { 734 disable: []string{"b"}, 735 expectedSnapstateEnabled: []string{"a", "c"}, 736 expectedSnapstateDisabled: []string{"b"}, 737 changed: true, 738 }, 739 { 740 disable: []string{"b", "c"}, 741 expectedSnapstateEnabled: []string{"a"}, 742 expectedSnapstateDisabled: []string{"b", "c"}, 743 changed: true, 744 }, 745 } 746 747 snapst := snapstate.SnapState{} 748 749 for _, tst := range tests { 750 var enable, disable []*snap.AppInfo 751 for _, srv := range tst.enable { 752 enable = append(enable, &snap.AppInfo{Name: srv}) 753 } 754 for _, srv := range tst.disable { 755 disable = append(disable, &snap.AppInfo{Name: srv}) 756 } 757 result, err := servicestate.UpdateSnapstateServices(&snapst, enable, disable) 758 c.Assert(err, IsNil) 759 c.Check(result, Equals, tst.changed) 760 c.Check(snapst.ServicesEnabledByHooks, DeepEquals, tst.expectedSnapstateEnabled) 761 c.Check(snapst.ServicesDisabledByHooks, DeepEquals, tst.expectedSnapstateDisabled) 762 } 763 764 services := []*snap.AppInfo{{Name: "foo"}} 765 _, err := servicestate.UpdateSnapstateServices(nil, services, services) 766 c.Assert(err, ErrorMatches, `internal error: cannot handle enabled and disabled services at the same time`) 767 }