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  }