github.com/stulluk/snapd@v0.0.0-20210611110309-f6d5d5bd24b0/overlord/servicestate/service_control_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package servicestate_test
    21  
    22  import (
    23  	"io/ioutil"
    24  	"os"
    25  	"path/filepath"
    26  	"sort"
    27  	"strings"
    28  	"testing"
    29  	"time"
    30  
    31  	. "gopkg.in/check.v1"
    32  
    33  	"github.com/snapcore/snapd/client"
    34  	"github.com/snapcore/snapd/dirs"
    35  	"github.com/snapcore/snapd/overlord"
    36  	"github.com/snapcore/snapd/overlord/servicestate"
    37  	"github.com/snapcore/snapd/overlord/snapstate"
    38  	"github.com/snapcore/snapd/overlord/state"
    39  	"github.com/snapcore/snapd/snap"
    40  	"github.com/snapcore/snapd/snap/snaptest"
    41  	"github.com/snapcore/snapd/systemd"
    42  	"github.com/snapcore/snapd/testutil"
    43  )
    44  
    45  func TestServiceControl(t *testing.T) { TestingT(t) }
    46  
    47  type serviceControlSuite struct {
    48  	testutil.BaseTest
    49  	state      *state.State
    50  	o          *overlord.Overlord
    51  	se         *overlord.StateEngine
    52  	serviceMgr *servicestate.ServiceManager
    53  	sysctlArgs [][]string
    54  }
    55  
    56  var _ = Suite(&serviceControlSuite{})
    57  
    58  const servicesSnapYaml1 = `name: test-snap
    59  version: 1.0
    60  apps:
    61    someapp:
    62      command: cmd
    63    abc:
    64      daemon: simple
    65      after: [bar]
    66    foo:
    67      daemon: simple
    68    bar:
    69      daemon: simple
    70      after: [foo]
    71  `
    72  
    73  func (s *serviceControlSuite) SetUpTest(c *C) {
    74  	s.BaseTest.SetUpTest(c)
    75  
    76  	dirs.SetRootDir(c.MkDir())
    77  	s.AddCleanup(func() { dirs.SetRootDir("") })
    78  
    79  	s.o = overlord.Mock()
    80  	s.state = s.o.State()
    81  
    82  	s.sysctlArgs = nil
    83  	systemctlRestorer := systemd.MockSystemctl(func(cmd ...string) (buf []byte, err error) {
    84  		s.sysctlArgs = append(s.sysctlArgs, cmd)
    85  		if cmd[0] == "show" {
    86  			return []byte("ActiveState=inactive\n"), nil
    87  		}
    88  		return nil, nil
    89  	})
    90  	s.AddCleanup(systemctlRestorer)
    91  
    92  	s.serviceMgr = servicestate.Manager(s.state, s.o.TaskRunner())
    93  	s.o.AddManager(s.serviceMgr)
    94  	s.o.AddManager(s.o.TaskRunner())
    95  	s.se = s.o.StateEngine()
    96  	c.Assert(s.o.StartUp(), IsNil)
    97  }
    98  
    99  func (s *serviceControlSuite) mockTestSnap(c *C) *snap.Info {
   100  	si := snap.SideInfo{
   101  		RealName: "test-snap",
   102  		Revision: snap.R(7),
   103  	}
   104  	info := snaptest.MockSnap(c, servicesSnapYaml1, &si)
   105  	snapstate.Set(s.state, "test-snap", &snapstate.SnapState{
   106  		Active:   true,
   107  		Sequence: []*snap.SideInfo{&si},
   108  		Current:  snap.R(7),
   109  		SnapType: "app",
   110  	})
   111  
   112  	// mock systemd service units, this is required when testing "stop"
   113  	err := os.MkdirAll(filepath.Join(dirs.GlobalRootDir, "etc/systemd/system/"), 0775)
   114  	c.Assert(err, IsNil)
   115  	err = ioutil.WriteFile(filepath.Join(dirs.GlobalRootDir, "etc/systemd/system/snap.test-snap.foo.service"), nil, 0644)
   116  	c.Assert(err, IsNil)
   117  
   118  	return info
   119  }
   120  
   121  func verifyControlTasks(c *C, tasks []*state.Task, expectedAction, actionModifier string,
   122  	expectedServices []string, expectedExplicitServices []string) {
   123  	// sanity, ensures test checks below are hit
   124  	c.Assert(len(tasks) > 0, Equals, true)
   125  
   126  	// group service names by snaps
   127  	bySnap := make(map[string][]string)
   128  	for _, name := range expectedServices {
   129  		// split service name, e.g. snap.test-snap.foo.service
   130  		parts := strings.Split(name, ".")
   131  		c.Assert(parts, HasLen, 4)
   132  		snapName := parts[1]
   133  		serviceName := parts[2]
   134  		bySnap[snapName] = append(bySnap[snapName], serviceName)
   135  	}
   136  
   137  	var execCommandTasks int
   138  	var serviceControlTasks int
   139  	// snaps from service-control tasks
   140  	seenSnaps := make(map[string]bool)
   141  
   142  	var i int
   143  	for i < len(tasks) {
   144  		var argv []string
   145  		kind := tasks[i].Kind()
   146  		if kind == "exec-command" {
   147  			execCommandTasks++
   148  			var ignore bool
   149  			c.Assert(tasks[i].Get("ignore", &ignore), IsNil)
   150  			c.Check(ignore, Equals, true)
   151  			switch expectedAction {
   152  			case "start":
   153  				if actionModifier != "" {
   154  					c.Assert(tasks[i].Get("argv", &argv), IsNil)
   155  					c.Check(argv, DeepEquals, append([]string{"systemctl", actionModifier}, expectedServices...))
   156  					i++
   157  					wt := tasks[i].WaitTasks()
   158  					c.Assert(wt, HasLen, 1)
   159  					c.Assert(wt[0].ID(), Equals, tasks[i-1].ID())
   160  				}
   161  				c.Assert(tasks[i].Get("argv", &argv), IsNil)
   162  				c.Check(argv, DeepEquals, append([]string{"systemctl", "start"}, expectedServices...))
   163  			case "stop":
   164  				if actionModifier != "" {
   165  					c.Assert(tasks[i].Get("argv", &argv), IsNil)
   166  					c.Check(argv, DeepEquals, append([]string{"systemctl", actionModifier}, expectedServices...))
   167  					i++
   168  					wt := tasks[i].WaitTasks()
   169  					c.Assert(wt, HasLen, 1)
   170  					c.Assert(wt[0].ID(), Equals, tasks[i-1].ID())
   171  				}
   172  				c.Assert(tasks[i].Get("argv", &argv), IsNil)
   173  				c.Check(argv, DeepEquals, append([]string{"systemctl", "stop"}, expectedServices...))
   174  			case "restart":
   175  				if actionModifier != "" {
   176  					c.Assert(tasks[i].Get("argv", &argv), IsNil)
   177  					c.Check(argv, DeepEquals, append([]string{"systemctl", "reload-or-restart"}, expectedServices...))
   178  				} else {
   179  					c.Assert(tasks[i].Get("argv", &argv), IsNil)
   180  					c.Check(argv, DeepEquals, append([]string{"systemctl", "restart"}, expectedServices...))
   181  				}
   182  			default:
   183  				c.Fatalf("unhandled action %s", expectedAction)
   184  			}
   185  		} else if kind == "service-control" {
   186  			serviceControlTasks++
   187  			var sa servicestate.ServiceAction
   188  			c.Assert(tasks[i].Get("service-action", &sa), IsNil)
   189  			switch expectedAction {
   190  			case "start":
   191  				c.Check(sa.Action, Equals, "start")
   192  				if actionModifier != "" {
   193  					c.Check(sa.ActionModifier, Equals, actionModifier)
   194  				}
   195  			case "stop":
   196  				c.Check(sa.Action, Equals, "stop")
   197  				if actionModifier != "" {
   198  					c.Check(sa.ActionModifier, Equals, actionModifier)
   199  				}
   200  			case "restart":
   201  				if actionModifier == "reload" {
   202  					c.Check(sa.Action, Equals, "reload-or-restart")
   203  				} else {
   204  					c.Check(sa.Action, Equals, "restart")
   205  				}
   206  			default:
   207  				c.Fatalf("unhandled action %s", expectedAction)
   208  			}
   209  			seenSnaps[sa.SnapName] = true
   210  			obtainedServices := sa.Services
   211  			sort.Strings(obtainedServices)
   212  			sort.Strings(bySnap[sa.SnapName])
   213  			c.Assert(obtainedServices, DeepEquals, bySnap[sa.SnapName])
   214  			c.Assert(sa.ExplicitServices, DeepEquals, expectedExplicitServices)
   215  		} else {
   216  			c.Fatalf("unexpected task: %s", tasks[i].Kind())
   217  		}
   218  		i++
   219  	}
   220  
   221  	c.Check(execCommandTasks > 0, Equals, true)
   222  
   223  	// we should have one service-control task for every snap
   224  	c.Assert(serviceControlTasks, Equals, len(bySnap))
   225  	c.Assert(len(bySnap), Equals, len(seenSnaps))
   226  	for snapName := range bySnap {
   227  		c.Assert(seenSnaps[snapName], Equals, true)
   228  	}
   229  }
   230  
   231  func makeControlChange(c *C, st *state.State, inst *servicestate.Instruction, info *snap.Info, appNames []string) *state.Change {
   232  	apps := []*snap.AppInfo{}
   233  	for _, name := range appNames {
   234  		c.Assert(info.Apps[name], NotNil)
   235  		apps = append(apps, info.Apps[name])
   236  	}
   237  
   238  	flags := &servicestate.Flags{CreateExecCommandTasks: true}
   239  	tss, err := servicestate.Control(st, apps, inst, flags, nil)
   240  	c.Assert(err, IsNil)
   241  
   242  	chg := st.NewChange("service-control", "...")
   243  	for _, ts := range tss {
   244  		chg.AddAll(ts)
   245  	}
   246  	return chg
   247  }
   248  
   249  func (s *serviceControlSuite) TestControlDoesntCreateExecCommandTasksIfNoFlags(c *C) {
   250  	st := s.state
   251  	st.Lock()
   252  	defer st.Unlock()
   253  
   254  	info := s.mockTestSnap(c)
   255  	inst := &servicestate.Instruction{
   256  		Action: "start",
   257  		Names:  []string{"foo"},
   258  	}
   259  
   260  	flags := &servicestate.Flags{}
   261  	tss, err := servicestate.Control(st, []*snap.AppInfo{info.Apps["foo"]}, inst, flags, nil)
   262  	c.Assert(err, IsNil)
   263  	// service-control is the only task
   264  	c.Assert(tss, HasLen, 1)
   265  	c.Assert(tss[0].Tasks(), HasLen, 1)
   266  	c.Check(tss[0].Tasks()[0].Kind(), Equals, "service-control")
   267  }
   268  
   269  func (s *serviceControlSuite) TestControlConflict(c *C) {
   270  	st := s.state
   271  	st.Lock()
   272  	defer st.Unlock()
   273  
   274  	inf := s.mockTestSnap(c)
   275  
   276  	// create conflicting change
   277  	t := st.NewTask("link-snap", "...")
   278  	snapsup := &snapstate.SnapSetup{SideInfo: &snap.SideInfo{RealName: "test-snap"}}
   279  	t.Set("snap-setup", snapsup)
   280  	chg := st.NewChange("manip", "...")
   281  	chg.AddTask(t)
   282  
   283  	inst := &servicestate.Instruction{Action: "start", Names: []string{"foo"}}
   284  	_, err := servicestate.Control(st, []*snap.AppInfo{inf.Apps["foo"]}, inst, nil, nil)
   285  	c.Check(err, ErrorMatches, `snap "test-snap" has "manip" change in progress`)
   286  }
   287  
   288  func (s *serviceControlSuite) TestControlStartInstruction(c *C) {
   289  	st := s.state
   290  	st.Lock()
   291  	defer st.Unlock()
   292  
   293  	inf := s.mockTestSnap(c)
   294  
   295  	inst := &servicestate.Instruction{
   296  		Action: "start",
   297  	}
   298  
   299  	chg := makeControlChange(c, st, inst, inf, []string{"foo"})
   300  	verifyControlTasks(c, chg.Tasks(), "start", "",
   301  		[]string{"snap.test-snap.foo.service"}, nil)
   302  }
   303  
   304  func (s *serviceControlSuite) TestControlStartEnableMultipleInstruction(c *C) {
   305  	st := s.state
   306  	st.Lock()
   307  	defer st.Unlock()
   308  
   309  	inf := s.mockTestSnap(c)
   310  
   311  	inst := &servicestate.Instruction{
   312  		Action:       "start",
   313  		StartOptions: client.StartOptions{Enable: true},
   314  	}
   315  
   316  	chg := makeControlChange(c, st, inst, inf, []string{"foo", "bar"})
   317  	verifyControlTasks(c, chg.Tasks(), "start", "enable",
   318  		[]string{"snap.test-snap.foo.service", "snap.test-snap.bar.service"}, nil)
   319  }
   320  
   321  func (s *serviceControlSuite) TestControlStopInstruction(c *C) {
   322  	st := s.state
   323  	st.Lock()
   324  	defer st.Unlock()
   325  
   326  	inf := s.mockTestSnap(c)
   327  
   328  	inst := &servicestate.Instruction{
   329  		Action: "stop",
   330  	}
   331  
   332  	chg := makeControlChange(c, st, inst, inf, []string{"foo"})
   333  	verifyControlTasks(c, chg.Tasks(), "stop", "",
   334  		[]string{"snap.test-snap.foo.service"}, nil)
   335  }
   336  
   337  func (s *serviceControlSuite) TestControlStopDisableInstruction(c *C) {
   338  	st := s.state
   339  	st.Lock()
   340  	defer st.Unlock()
   341  
   342  	inf := s.mockTestSnap(c)
   343  
   344  	inst := &servicestate.Instruction{
   345  		Action:      "stop",
   346  		StopOptions: client.StopOptions{Disable: true},
   347  	}
   348  
   349  	chg := makeControlChange(c, st, inst, inf, []string{"bar"})
   350  	verifyControlTasks(c, chg.Tasks(), "stop", "disable",
   351  		[]string{"snap.test-snap.bar.service"}, nil)
   352  }
   353  
   354  func (s *serviceControlSuite) TestControlRestartInstruction(c *C) {
   355  	st := s.state
   356  	st.Lock()
   357  	defer st.Unlock()
   358  
   359  	inf := s.mockTestSnap(c)
   360  
   361  	inst := &servicestate.Instruction{
   362  		Action: "restart",
   363  	}
   364  
   365  	chg := makeControlChange(c, st, inst, inf, []string{"bar"})
   366  	verifyControlTasks(c, chg.Tasks(), "restart", "",
   367  		[]string{"snap.test-snap.bar.service"}, nil)
   368  }
   369  
   370  func (s *serviceControlSuite) TestControlRestartReloadMultipleInstruction(c *C) {
   371  	st := s.state
   372  	st.Lock()
   373  	defer st.Unlock()
   374  
   375  	inf := s.mockTestSnap(c)
   376  
   377  	inst := &servicestate.Instruction{
   378  		Action:         "restart",
   379  		RestartOptions: client.RestartOptions{Reload: true},
   380  	}
   381  
   382  	chg := makeControlChange(c, st, inst, inf, []string{"foo", "bar"})
   383  	verifyControlTasks(c, chg.Tasks(), "restart", "reload",
   384  		[]string{"snap.test-snap.foo.service", "snap.test-snap.bar.service"},
   385  		nil)
   386  }
   387  
   388  func (s *serviceControlSuite) TestControlUnknownInstruction(c *C) {
   389  	st := s.state
   390  	st.Lock()
   391  	defer st.Unlock()
   392  
   393  	info := s.mockTestSnap(c)
   394  	inst := &servicestate.Instruction{
   395  		Action:         "boo",
   396  		RestartOptions: client.RestartOptions{Reload: true},
   397  	}
   398  
   399  	_, err := servicestate.Control(st, []*snap.AppInfo{info.Apps["foo"]}, inst, nil, nil)
   400  	c.Assert(err, ErrorMatches, `unknown action "boo"`)
   401  }
   402  
   403  func (s *serviceControlSuite) TestControlStopDisableMultipleInstruction(c *C) {
   404  	st := s.state
   405  	st.Lock()
   406  	defer st.Unlock()
   407  
   408  	inf := s.mockTestSnap(c)
   409  
   410  	inst := &servicestate.Instruction{
   411  		Action:      "stop",
   412  		StopOptions: client.StopOptions{Disable: true},
   413  	}
   414  
   415  	chg := makeControlChange(c, st, inst, inf, []string{"foo", "bar"})
   416  	verifyControlTasks(c, chg.Tasks(), "stop", "disable",
   417  		[]string{"snap.test-snap.foo.service", "snap.test-snap.bar.service"},
   418  		nil)
   419  }
   420  
   421  func (s *serviceControlSuite) TestWithExplicitServices(c *C) {
   422  	st := s.state
   423  	st.Lock()
   424  	defer st.Unlock()
   425  
   426  	inf := s.mockTestSnap(c)
   427  
   428  	inst := &servicestate.Instruction{
   429  		Action: "start",
   430  		Names:  []string{"test-snap.foo", "test-snap.abc"},
   431  	}
   432  
   433  	chg := makeControlChange(c, st, inst, inf, []string{"abc", "foo", "bar"})
   434  	verifyControlTasks(c, chg.Tasks(), "start", "",
   435  		[]string{"snap.test-snap.abc.service", "snap.test-snap.foo.service", "snap.test-snap.bar.service"},
   436  		[]string{"snap.test-snap.abc.service", "snap.test-snap.foo.service"})
   437  }
   438  
   439  func (s *serviceControlSuite) TestWithExplicitServicesAndSnapName(c *C) {
   440  	st := s.state
   441  	st.Lock()
   442  	defer st.Unlock()
   443  
   444  	inf := s.mockTestSnap(c)
   445  
   446  	inst := &servicestate.Instruction{
   447  		Action: "start",
   448  		Names:  []string{"test-snap.foo", "test-snap", "test-snap.bar"},
   449  	}
   450  
   451  	chg := makeControlChange(c, st, inst, inf, []string{"abc", "foo", "bar"})
   452  	verifyControlTasks(c, chg.Tasks(), "start", "",
   453  		[]string{"snap.test-snap.abc.service", "snap.test-snap.foo.service", "snap.test-snap.bar.service"},
   454  		[]string{"snap.test-snap.bar.service", "snap.test-snap.foo.service"})
   455  }
   456  
   457  func (s *serviceControlSuite) TestNoServiceCommandError(c *C) {
   458  	st := s.state
   459  	st.Lock()
   460  
   461  	chg := st.NewChange("service-control", "...")
   462  	t := st.NewTask("service-control", "...")
   463  	chg.AddTask(t)
   464  
   465  	st.Unlock()
   466  	defer s.se.Stop()
   467  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   468  	st.Lock()
   469  
   470  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   471  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*internal error: cannot get service-action: no state entry for key.*`)
   472  }
   473  
   474  func (s *serviceControlSuite) TestNoopWhenNoServices(c *C) {
   475  	st := s.state
   476  	st.Lock()
   477  
   478  	si := snap.SideInfo{RealName: "test-snap", Revision: snap.R(7)}
   479  	snaptest.MockSnap(c, `name: test-snap`, &si)
   480  	snapstate.Set(st, "test-snap", &snapstate.SnapState{
   481  		Active:   true,
   482  		Sequence: []*snap.SideInfo{&si},
   483  		Current:  snap.R(7),
   484  		SnapType: "app",
   485  	})
   486  
   487  	chg := st.NewChange("service-control", "...")
   488  	t := st.NewTask("service-control", "...")
   489  	cmd := &servicestate.ServiceAction{SnapName: "test-snap"}
   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.Check(t.Status(), Equals, state.DoneStatus)
   499  }
   500  
   501  func (s *serviceControlSuite) TestUnhandledServiceAction(c *C) {
   502  	st := s.state
   503  	st.Lock()
   504  
   505  	s.mockTestSnap(c)
   506  
   507  	chg := st.NewChange("service-control", "...")
   508  	t := st.NewTask("service-control", "...")
   509  	cmd := &servicestate.ServiceAction{SnapName: "test-snap", Action: "foo"}
   510  	t.Set("service-action", cmd)
   511  	chg.AddTask(t)
   512  
   513  	st.Unlock()
   514  	defer s.se.Stop()
   515  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   516  	st.Lock()
   517  
   518  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   519  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*unhandled service action: "foo".*`)
   520  }
   521  
   522  func (s *serviceControlSuite) TestUnknownService(c *C) {
   523  	st := s.state
   524  	st.Lock()
   525  
   526  	s.mockTestSnap(c)
   527  
   528  	chg := st.NewChange("service-control", "...")
   529  	t := st.NewTask("service-control", "...")
   530  	cmd := &servicestate.ServiceAction{
   531  		SnapName: "test-snap",
   532  		Action:   "start",
   533  		Services: []string{"baz"},
   534  	}
   535  	t.Set("service-action", cmd)
   536  	chg.AddTask(t)
   537  
   538  	st.Unlock()
   539  	defer s.se.Stop()
   540  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   541  	st.Lock()
   542  
   543  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   544  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*no such service: baz.*`)
   545  }
   546  
   547  func (s *serviceControlSuite) TestNotAService(c *C) {
   548  	st := s.state
   549  	st.Lock()
   550  
   551  	s.mockTestSnap(c)
   552  
   553  	chg := st.NewChange("service-control", "...")
   554  	t := st.NewTask("service-control", "...")
   555  	cmd := &servicestate.ServiceAction{
   556  		SnapName: "test-snap",
   557  		Action:   "start",
   558  		Services: []string{"someapp"},
   559  	}
   560  	t.Set("service-action", cmd)
   561  	chg.AddTask(t)
   562  
   563  	st.Unlock()
   564  	defer s.se.Stop()
   565  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   566  	st.Lock()
   567  
   568  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   569  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*someapp is not a service.*`)
   570  }
   571  
   572  func (s *serviceControlSuite) TestStartAllServices(c *C) {
   573  	st := s.state
   574  	st.Lock()
   575  
   576  	s.mockTestSnap(c)
   577  
   578  	chg := st.NewChange("service-control", "...")
   579  	t := st.NewTask("service-control", "...")
   580  	cmd := &servicestate.ServiceAction{
   581  		SnapName: "test-snap",
   582  		Action:   "start",
   583  	}
   584  	t.Set("service-action", cmd)
   585  	chg.AddTask(t)
   586  
   587  	st.Unlock()
   588  	defer s.se.Stop()
   589  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   590  	st.Lock()
   591  
   592  	c.Assert(t.Status(), Equals, state.DoneStatus)
   593  
   594  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   595  		{"start", "snap.test-snap.foo.service"},
   596  		{"start", "snap.test-snap.bar.service"},
   597  		{"start", "snap.test-snap.abc.service"},
   598  	})
   599  }
   600  
   601  func (s *serviceControlSuite) TestStartListedServices(c *C) {
   602  	st := s.state
   603  	st.Lock()
   604  
   605  	s.mockTestSnap(c)
   606  
   607  	chg := st.NewChange("service-control", "...")
   608  	t := st.NewTask("service-control", "...")
   609  	cmd := &servicestate.ServiceAction{
   610  		SnapName: "test-snap",
   611  		Action:   "start",
   612  		Services: []string{"foo"},
   613  	}
   614  	t.Set("service-action", cmd)
   615  	chg.AddTask(t)
   616  
   617  	st.Unlock()
   618  	defer s.se.Stop()
   619  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   620  	st.Lock()
   621  
   622  	c.Assert(t.Status(), Equals, state.DoneStatus)
   623  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   624  		{"start", "snap.test-snap.foo.service"},
   625  	})
   626  }
   627  
   628  func (s *serviceControlSuite) TestStartEnableServices(c *C) {
   629  	st := s.state
   630  	st.Lock()
   631  
   632  	s.mockTestSnap(c)
   633  
   634  	chg := st.NewChange("service-control", "...")
   635  	t := st.NewTask("service-control", "...")
   636  	cmd := &servicestate.ServiceAction{
   637  		SnapName:       "test-snap",
   638  		Action:         "start",
   639  		ActionModifier: "enable",
   640  		Services:       []string{"foo"},
   641  	}
   642  	t.Set("service-action", cmd)
   643  	chg.AddTask(t)
   644  
   645  	st.Unlock()
   646  	defer s.se.Stop()
   647  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   648  	st.Lock()
   649  
   650  	c.Assert(t.Status(), Equals, state.DoneStatus)
   651  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   652  		{"enable", "snap.test-snap.foo.service"},
   653  		{"start", "snap.test-snap.foo.service"},
   654  	})
   655  }
   656  
   657  func (s *serviceControlSuite) TestStartEnableMultipleServices(c *C) {
   658  	st := s.state
   659  	st.Lock()
   660  
   661  	s.mockTestSnap(c)
   662  
   663  	chg := st.NewChange("service-control", "...")
   664  	t := st.NewTask("service-control", "...")
   665  	cmd := &servicestate.ServiceAction{
   666  		SnapName:       "test-snap",
   667  		Action:         "start",
   668  		ActionModifier: "enable",
   669  	}
   670  	t.Set("service-action", cmd)
   671  	chg.AddTask(t)
   672  
   673  	st.Unlock()
   674  	defer s.se.Stop()
   675  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   676  	st.Lock()
   677  
   678  	c.Assert(t.Status(), Equals, state.DoneStatus)
   679  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   680  		{"enable", "snap.test-snap.foo.service"},
   681  		{"enable", "snap.test-snap.bar.service"},
   682  		{"enable", "snap.test-snap.abc.service"},
   683  		{"start", "snap.test-snap.foo.service"},
   684  		{"start", "snap.test-snap.bar.service"},
   685  		{"start", "snap.test-snap.abc.service"},
   686  	})
   687  }
   688  
   689  func (s *serviceControlSuite) TestStopServices(c *C) {
   690  	st := s.state
   691  	st.Lock()
   692  
   693  	s.mockTestSnap(c)
   694  
   695  	chg := st.NewChange("service-control", "...")
   696  	t := st.NewTask("service-control", "...")
   697  	cmd := &servicestate.ServiceAction{
   698  		SnapName: "test-snap",
   699  		Action:   "stop",
   700  		Services: []string{"foo"},
   701  	}
   702  	t.Set("service-action", cmd)
   703  	chg.AddTask(t)
   704  
   705  	st.Unlock()
   706  	defer s.se.Stop()
   707  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   708  	st.Lock()
   709  
   710  	c.Assert(t.Status(), Equals, state.DoneStatus)
   711  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   712  		{"stop", "snap.test-snap.foo.service"},
   713  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   714  	})
   715  }
   716  
   717  func (s *serviceControlSuite) TestStopDisableServices(c *C) {
   718  	st := s.state
   719  	st.Lock()
   720  
   721  	s.mockTestSnap(c)
   722  
   723  	chg := st.NewChange("service-control", "...")
   724  	t := st.NewTask("service-control", "...")
   725  	cmd := &servicestate.ServiceAction{
   726  		SnapName:       "test-snap",
   727  		Action:         "stop",
   728  		ActionModifier: "disable",
   729  		Services:       []string{"foo"},
   730  	}
   731  	t.Set("service-action", cmd)
   732  	chg.AddTask(t)
   733  
   734  	st.Unlock()
   735  	defer s.se.Stop()
   736  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   737  	st.Lock()
   738  
   739  	c.Assert(t.Status(), Equals, state.DoneStatus)
   740  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   741  		{"stop", "snap.test-snap.foo.service"},
   742  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   743  		{"disable", "snap.test-snap.foo.service"},
   744  	})
   745  }
   746  
   747  func (s *serviceControlSuite) TestRestartServices(c *C) {
   748  	st := s.state
   749  	st.Lock()
   750  
   751  	s.mockTestSnap(c)
   752  
   753  	chg := st.NewChange("service-control", "...")
   754  	t := st.NewTask("service-control", "...")
   755  	cmd := &servicestate.ServiceAction{
   756  		SnapName: "test-snap",
   757  		Action:   "restart",
   758  		Services: []string{"foo"},
   759  	}
   760  	t.Set("service-action", cmd)
   761  	chg.AddTask(t)
   762  
   763  	st.Unlock()
   764  	defer s.se.Stop()
   765  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   766  	st.Lock()
   767  
   768  	c.Assert(t.Status(), Equals, state.DoneStatus)
   769  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   770  		{"stop", "snap.test-snap.foo.service"},
   771  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   772  		{"start", "snap.test-snap.foo.service"},
   773  	})
   774  }
   775  
   776  func (s *serviceControlSuite) TestRestartAllServices(c *C) {
   777  	st := s.state
   778  	st.Lock()
   779  
   780  	s.mockTestSnap(c)
   781  
   782  	chg := st.NewChange("service-control", "...")
   783  	t := st.NewTask("service-control", "...")
   784  	cmd := &servicestate.ServiceAction{
   785  		SnapName: "test-snap",
   786  		Action:   "restart",
   787  		Services: []string{"abc", "foo", "bar"},
   788  	}
   789  	t.Set("service-action", cmd)
   790  	chg.AddTask(t)
   791  
   792  	st.Unlock()
   793  	defer s.se.Stop()
   794  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   795  	st.Lock()
   796  
   797  	c.Assert(t.Status(), Equals, state.DoneStatus)
   798  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   799  		{"stop", "snap.test-snap.foo.service"},
   800  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   801  		{"start", "snap.test-snap.foo.service"},
   802  		{"stop", "snap.test-snap.bar.service"},
   803  		{"show", "--property=ActiveState", "snap.test-snap.bar.service"},
   804  		{"start", "snap.test-snap.bar.service"},
   805  		{"stop", "snap.test-snap.abc.service"},
   806  		{"show", "--property=ActiveState", "snap.test-snap.abc.service"},
   807  		{"start", "snap.test-snap.abc.service"},
   808  	})
   809  }
   810  
   811  func (s *serviceControlSuite) TestReloadServices(c *C) {
   812  	st := s.state
   813  	st.Lock()
   814  
   815  	s.mockTestSnap(c)
   816  
   817  	chg := st.NewChange("service-control", "...")
   818  	t := st.NewTask("service-control", "...")
   819  	cmd := &servicestate.ServiceAction{
   820  		SnapName: "test-snap",
   821  		Action:   "reload-or-restart",
   822  		Services: []string{"foo"},
   823  	}
   824  	t.Set("service-action", cmd)
   825  	chg.AddTask(t)
   826  
   827  	st.Unlock()
   828  	defer s.se.Stop()
   829  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   830  	st.Lock()
   831  
   832  	c.Assert(t.Status(), Equals, state.DoneStatus)
   833  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   834  		{"reload-or-restart", "snap.test-snap.foo.service"},
   835  	})
   836  }
   837  
   838  func (s *serviceControlSuite) TestReloadAllServices(c *C) {
   839  	st := s.state
   840  	st.Lock()
   841  
   842  	s.mockTestSnap(c)
   843  
   844  	chg := st.NewChange("service-control", "...")
   845  	t := st.NewTask("service-control", "...")
   846  	cmd := &servicestate.ServiceAction{
   847  		SnapName: "test-snap",
   848  		Action:   "reload-or-restart",
   849  		Services: []string{"foo", "abc", "bar"},
   850  	}
   851  	t.Set("service-action", cmd)
   852  	chg.AddTask(t)
   853  
   854  	st.Unlock()
   855  	defer s.se.Stop()
   856  	c.Assert(s.o.Settle(5*time.Second), IsNil)
   857  	st.Lock()
   858  
   859  	c.Assert(t.Status(), Equals, state.DoneStatus)
   860  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   861  		{"reload-or-restart", "snap.test-snap.foo.service"},
   862  		{"reload-or-restart", "snap.test-snap.bar.service"},
   863  		{"reload-or-restart", "snap.test-snap.abc.service"},
   864  	})
   865  }
   866  
   867  func (s *serviceControlSuite) TestConflict(c *C) {
   868  	st := s.state
   869  	st.Lock()
   870  	defer st.Unlock()
   871  
   872  	s.mockTestSnap(c)
   873  
   874  	chg := st.NewChange("service-control", "...")
   875  	t := st.NewTask("service-control", "...")
   876  	cmd := &servicestate.ServiceAction{
   877  		SnapName: "test-snap",
   878  		Action:   "reload-or-restart",
   879  		Services: []string{"foo"},
   880  	}
   881  	t.Set("service-action", cmd)
   882  	chg.AddTask(t)
   883  
   884  	_, err := snapstate.Remove(st, "test-snap", snap.Revision{}, nil)
   885  	c.Assert(err, ErrorMatches, `snap "test-snap" has "service-control" change in progress`)
   886  }
   887  
   888  func (s *serviceControlSuite) TestUpdateSnapstateServices(c *C) {
   889  	var tests = []struct {
   890  		enable                    []string
   891  		disable                   []string
   892  		expectedSnapstateEnabled  []string
   893  		expectedSnapstateDisabled []string
   894  		changed                   bool
   895  	}{
   896  		// These test scenarios share a single SnapState instance and accumulate
   897  		// changes to ServicesEnabledByHooks and ServicesDisabledByHooks.
   898  		{
   899  			changed: false,
   900  		},
   901  		{
   902  			enable: []string{"a"},
   903  			expectedSnapstateEnabled: []string{"a"},
   904  			changed:                  true,
   905  		},
   906  		// enable again does nothing
   907  		{
   908  			enable: []string{"a"},
   909  			expectedSnapstateEnabled: []string{"a"},
   910  			changed:                  false,
   911  		},
   912  		{
   913  			disable:                   []string{"a"},
   914  			expectedSnapstateDisabled: []string{"a"},
   915  			changed:                   true,
   916  		},
   917  		{
   918  			enable: []string{"a", "c"},
   919  			expectedSnapstateEnabled: []string{"a", "c"},
   920  			changed:                  true,
   921  		},
   922  		{
   923  			disable:                   []string{"b"},
   924  			expectedSnapstateEnabled:  []string{"a", "c"},
   925  			expectedSnapstateDisabled: []string{"b"},
   926  			changed:                   true,
   927  		},
   928  		{
   929  			disable:                   []string{"b", "c"},
   930  			expectedSnapstateEnabled:  []string{"a"},
   931  			expectedSnapstateDisabled: []string{"b", "c"},
   932  			changed:                   true,
   933  		},
   934  	}
   935  
   936  	snapst := snapstate.SnapState{}
   937  
   938  	for _, tst := range tests {
   939  		var enable, disable []*snap.AppInfo
   940  		for _, srv := range tst.enable {
   941  			enable = append(enable, &snap.AppInfo{Name: srv})
   942  		}
   943  		for _, srv := range tst.disable {
   944  			disable = append(disable, &snap.AppInfo{Name: srv})
   945  		}
   946  		result, err := servicestate.UpdateSnapstateServices(&snapst, enable, disable)
   947  		c.Assert(err, IsNil)
   948  		c.Check(result, Equals, tst.changed)
   949  		c.Check(snapst.ServicesEnabledByHooks, DeepEquals, tst.expectedSnapstateEnabled)
   950  		c.Check(snapst.ServicesDisabledByHooks, DeepEquals, tst.expectedSnapstateDisabled)
   951  	}
   952  
   953  	services := []*snap.AppInfo{{Name: "foo"}}
   954  	_, err := servicestate.UpdateSnapstateServices(nil, services, services)
   955  	c.Assert(err, ErrorMatches, `internal error: cannot handle enabled and disabled services at the same time`)
   956  }