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