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