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  }