github.com/ethanhsieh/snapd@v0.0.0-20210615102523-3db9b8e4edc5/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  	defer st.Unlock()
   461  
   462  	chg := st.NewChange("service-control", "...")
   463  	t := st.NewTask("service-control", "...")
   464  	chg.AddTask(t)
   465  
   466  	st.Unlock()
   467  	defer s.se.Stop()
   468  	err := s.o.Settle(5 * time.Second)
   469  	st.Lock()
   470  	c.Assert(err, IsNil)
   471  
   472  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   473  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*internal error: cannot get service-action: no state entry for key.*`)
   474  }
   475  
   476  func (s *serviceControlSuite) TestNoopWhenNoServices(c *C) {
   477  	st := s.state
   478  	st.Lock()
   479  	defer st.Unlock()
   480  
   481  	si := snap.SideInfo{RealName: "test-snap", Revision: snap.R(7)}
   482  	snaptest.MockSnap(c, `name: test-snap`, &si)
   483  	snapstate.Set(st, "test-snap", &snapstate.SnapState{
   484  		Active:   true,
   485  		Sequence: []*snap.SideInfo{&si},
   486  		Current:  snap.R(7),
   487  		SnapType: "app",
   488  	})
   489  
   490  	chg := st.NewChange("service-control", "...")
   491  	t := st.NewTask("service-control", "...")
   492  	cmd := &servicestate.ServiceAction{SnapName: "test-snap"}
   493  	t.Set("service-action", cmd)
   494  	chg.AddTask(t)
   495  
   496  	st.Unlock()
   497  	defer s.se.Stop()
   498  	err := s.o.Settle(5 * time.Second)
   499  	st.Lock()
   500  	c.Assert(err, IsNil)
   501  
   502  	c.Check(t.Status(), Equals, state.DoneStatus)
   503  }
   504  
   505  func (s *serviceControlSuite) TestUnhandledServiceAction(c *C) {
   506  	st := s.state
   507  	st.Lock()
   508  	defer st.Unlock()
   509  
   510  	s.mockTestSnap(c)
   511  
   512  	chg := st.NewChange("service-control", "...")
   513  	t := st.NewTask("service-control", "...")
   514  	cmd := &servicestate.ServiceAction{SnapName: "test-snap", Action: "foo"}
   515  	t.Set("service-action", cmd)
   516  	chg.AddTask(t)
   517  
   518  	st.Unlock()
   519  	defer s.se.Stop()
   520  	err := s.o.Settle(5 * time.Second)
   521  	st.Lock()
   522  	c.Assert(err, IsNil)
   523  
   524  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   525  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*unhandled service action: "foo".*`)
   526  }
   527  
   528  func (s *serviceControlSuite) TestUnknownService(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{
   538  		SnapName: "test-snap",
   539  		Action:   "start",
   540  		Services: []string{"baz"},
   541  	}
   542  	t.Set("service-action", cmd)
   543  	chg.AddTask(t)
   544  
   545  	st.Unlock()
   546  	defer s.se.Stop()
   547  	err := s.o.Settle(5 * time.Second)
   548  	st.Lock()
   549  	c.Assert(err, IsNil)
   550  
   551  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   552  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*no such service: baz.*`)
   553  }
   554  
   555  func (s *serviceControlSuite) TestNotAService(c *C) {
   556  	st := s.state
   557  	st.Lock()
   558  	defer st.Unlock()
   559  
   560  	s.mockTestSnap(c)
   561  
   562  	chg := st.NewChange("service-control", "...")
   563  	t := st.NewTask("service-control", "...")
   564  	cmd := &servicestate.ServiceAction{
   565  		SnapName: "test-snap",
   566  		Action:   "start",
   567  		Services: []string{"someapp"},
   568  	}
   569  	t.Set("service-action", cmd)
   570  	chg.AddTask(t)
   571  
   572  	st.Unlock()
   573  	defer s.se.Stop()
   574  	err := s.o.Settle(5 * time.Second)
   575  	st.Lock()
   576  	c.Assert(err, IsNil)
   577  
   578  	c.Assert(t.Status(), Equals, state.ErrorStatus)
   579  	c.Check(chg.Err(), ErrorMatches, `cannot perform the following tasks:\n.*someapp is not a service.*`)
   580  }
   581  
   582  func (s *serviceControlSuite) TestStartAllServices(c *C) {
   583  	st := s.state
   584  	st.Lock()
   585  	defer st.Unlock()
   586  
   587  	s.mockTestSnap(c)
   588  
   589  	chg := st.NewChange("service-control", "...")
   590  	t := st.NewTask("service-control", "...")
   591  	cmd := &servicestate.ServiceAction{
   592  		SnapName: "test-snap",
   593  		Action:   "start",
   594  	}
   595  	t.Set("service-action", cmd)
   596  	chg.AddTask(t)
   597  
   598  	st.Unlock()
   599  	defer s.se.Stop()
   600  	err := s.o.Settle(5 * time.Second)
   601  	st.Lock()
   602  	c.Assert(err, IsNil)
   603  
   604  	c.Assert(t.Status(), Equals, state.DoneStatus)
   605  
   606  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   607  		{"start", "snap.test-snap.foo.service"},
   608  		{"start", "snap.test-snap.bar.service"},
   609  		{"start", "snap.test-snap.abc.service"},
   610  	})
   611  }
   612  
   613  func (s *serviceControlSuite) TestStartListedServices(c *C) {
   614  	st := s.state
   615  	st.Lock()
   616  	defer st.Unlock()
   617  
   618  	s.mockTestSnap(c)
   619  
   620  	chg := st.NewChange("service-control", "...")
   621  	t := st.NewTask("service-control", "...")
   622  	cmd := &servicestate.ServiceAction{
   623  		SnapName: "test-snap",
   624  		Action:   "start",
   625  		Services: []string{"foo"},
   626  	}
   627  	t.Set("service-action", cmd)
   628  	chg.AddTask(t)
   629  
   630  	st.Unlock()
   631  	defer s.se.Stop()
   632  	err := s.o.Settle(5 * time.Second)
   633  	st.Lock()
   634  	c.Assert(err, IsNil)
   635  
   636  	c.Assert(t.Status(), Equals, state.DoneStatus)
   637  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   638  		{"start", "snap.test-snap.foo.service"},
   639  	})
   640  }
   641  
   642  func (s *serviceControlSuite) TestStartEnableServices(c *C) {
   643  	st := s.state
   644  	st.Lock()
   645  	defer st.Unlock()
   646  
   647  	s.mockTestSnap(c)
   648  
   649  	chg := st.NewChange("service-control", "...")
   650  	t := st.NewTask("service-control", "...")
   651  	cmd := &servicestate.ServiceAction{
   652  		SnapName:       "test-snap",
   653  		Action:         "start",
   654  		ActionModifier: "enable",
   655  		Services:       []string{"foo"},
   656  	}
   657  	t.Set("service-action", cmd)
   658  	chg.AddTask(t)
   659  
   660  	st.Unlock()
   661  	defer s.se.Stop()
   662  	err := s.o.Settle(5 * time.Second)
   663  	st.Lock()
   664  	c.Assert(err, IsNil)
   665  
   666  	c.Assert(t.Status(), Equals, state.DoneStatus)
   667  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   668  		{"enable", "snap.test-snap.foo.service"},
   669  		{"start", "snap.test-snap.foo.service"},
   670  	})
   671  }
   672  
   673  func (s *serviceControlSuite) TestStartEnableMultipleServices(c *C) {
   674  	st := s.state
   675  	st.Lock()
   676  	defer st.Unlock()
   677  
   678  	s.mockTestSnap(c)
   679  
   680  	chg := st.NewChange("service-control", "...")
   681  	t := st.NewTask("service-control", "...")
   682  	cmd := &servicestate.ServiceAction{
   683  		SnapName:       "test-snap",
   684  		Action:         "start",
   685  		ActionModifier: "enable",
   686  	}
   687  	t.Set("service-action", cmd)
   688  	chg.AddTask(t)
   689  
   690  	st.Unlock()
   691  	defer s.se.Stop()
   692  	err := s.o.Settle(5 * time.Second)
   693  	st.Lock()
   694  	c.Assert(err, IsNil)
   695  
   696  	c.Assert(t.Status(), Equals, state.DoneStatus)
   697  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   698  		{"enable", "snap.test-snap.foo.service"},
   699  		{"enable", "snap.test-snap.bar.service"},
   700  		{"enable", "snap.test-snap.abc.service"},
   701  		{"start", "snap.test-snap.foo.service"},
   702  		{"start", "snap.test-snap.bar.service"},
   703  		{"start", "snap.test-snap.abc.service"},
   704  	})
   705  }
   706  
   707  func (s *serviceControlSuite) TestStopServices(c *C) {
   708  	st := s.state
   709  	st.Lock()
   710  	defer st.Unlock()
   711  
   712  	s.mockTestSnap(c)
   713  
   714  	chg := st.NewChange("service-control", "...")
   715  	t := st.NewTask("service-control", "...")
   716  	cmd := &servicestate.ServiceAction{
   717  		SnapName: "test-snap",
   718  		Action:   "stop",
   719  		Services: []string{"foo"},
   720  	}
   721  	t.Set("service-action", cmd)
   722  	chg.AddTask(t)
   723  
   724  	st.Unlock()
   725  	defer s.se.Stop()
   726  	err := s.o.Settle(5 * time.Second)
   727  	st.Lock()
   728  	c.Assert(err, IsNil)
   729  
   730  	c.Assert(t.Status(), Equals, state.DoneStatus)
   731  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   732  		{"stop", "snap.test-snap.foo.service"},
   733  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   734  	})
   735  }
   736  
   737  func (s *serviceControlSuite) TestStopDisableServices(c *C) {
   738  	st := s.state
   739  	st.Lock()
   740  	defer st.Unlock()
   741  
   742  	s.mockTestSnap(c)
   743  
   744  	chg := st.NewChange("service-control", "...")
   745  	t := st.NewTask("service-control", "...")
   746  	cmd := &servicestate.ServiceAction{
   747  		SnapName:       "test-snap",
   748  		Action:         "stop",
   749  		ActionModifier: "disable",
   750  		Services:       []string{"foo"},
   751  	}
   752  	t.Set("service-action", cmd)
   753  	chg.AddTask(t)
   754  
   755  	st.Unlock()
   756  	defer s.se.Stop()
   757  	err := s.o.Settle(5 * time.Second)
   758  	st.Lock()
   759  	c.Assert(err, IsNil)
   760  
   761  	c.Assert(t.Status(), Equals, state.DoneStatus)
   762  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   763  		{"stop", "snap.test-snap.foo.service"},
   764  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   765  		{"disable", "snap.test-snap.foo.service"},
   766  	})
   767  }
   768  
   769  func (s *serviceControlSuite) TestRestartServices(c *C) {
   770  	st := s.state
   771  	st.Lock()
   772  	defer st.Unlock()
   773  
   774  	s.mockTestSnap(c)
   775  
   776  	chg := st.NewChange("service-control", "...")
   777  	t := st.NewTask("service-control", "...")
   778  	cmd := &servicestate.ServiceAction{
   779  		SnapName: "test-snap",
   780  		Action:   "restart",
   781  		Services: []string{"foo"},
   782  	}
   783  	t.Set("service-action", cmd)
   784  	chg.AddTask(t)
   785  
   786  	st.Unlock()
   787  	defer s.se.Stop()
   788  	err := s.o.Settle(5 * time.Second)
   789  	st.Lock()
   790  	c.Assert(err, IsNil)
   791  
   792  	c.Assert(t.Status(), Equals, state.DoneStatus)
   793  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   794  		{"stop", "snap.test-snap.foo.service"},
   795  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   796  		{"start", "snap.test-snap.foo.service"},
   797  	})
   798  }
   799  
   800  func (s *serviceControlSuite) TestRestartAllServices(c *C) {
   801  	st := s.state
   802  	st.Lock()
   803  	defer st.Unlock()
   804  
   805  	s.mockTestSnap(c)
   806  
   807  	chg := st.NewChange("service-control", "...")
   808  	t := st.NewTask("service-control", "...")
   809  	cmd := &servicestate.ServiceAction{
   810  		SnapName: "test-snap",
   811  		Action:   "restart",
   812  		Services: []string{"abc", "foo", "bar"},
   813  	}
   814  	t.Set("service-action", cmd)
   815  	chg.AddTask(t)
   816  
   817  	st.Unlock()
   818  	defer s.se.Stop()
   819  	err := s.o.Settle(5 * time.Second)
   820  	st.Lock()
   821  	c.Assert(err, IsNil)
   822  
   823  	c.Assert(t.Status(), Equals, state.DoneStatus)
   824  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   825  		{"stop", "snap.test-snap.foo.service"},
   826  		{"show", "--property=ActiveState", "snap.test-snap.foo.service"},
   827  		{"start", "snap.test-snap.foo.service"},
   828  		{"stop", "snap.test-snap.bar.service"},
   829  		{"show", "--property=ActiveState", "snap.test-snap.bar.service"},
   830  		{"start", "snap.test-snap.bar.service"},
   831  		{"stop", "snap.test-snap.abc.service"},
   832  		{"show", "--property=ActiveState", "snap.test-snap.abc.service"},
   833  		{"start", "snap.test-snap.abc.service"},
   834  	})
   835  }
   836  
   837  func (s *serviceControlSuite) TestReloadServices(c *C) {
   838  	st := s.state
   839  	st.Lock()
   840  	defer st.Unlock()
   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"},
   850  	}
   851  	t.Set("service-action", cmd)
   852  	chg.AddTask(t)
   853  
   854  	st.Unlock()
   855  	defer s.se.Stop()
   856  	err := s.o.Settle(5 * time.Second)
   857  	st.Lock()
   858  	c.Assert(err, IsNil)
   859  
   860  	c.Assert(t.Status(), Equals, state.DoneStatus)
   861  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   862  		{"reload-or-restart", "snap.test-snap.foo.service"},
   863  	})
   864  }
   865  
   866  func (s *serviceControlSuite) TestReloadAllServices(c *C) {
   867  	st := s.state
   868  	st.Lock()
   869  	defer st.Unlock()
   870  
   871  	s.mockTestSnap(c)
   872  
   873  	chg := st.NewChange("service-control", "...")
   874  	t := st.NewTask("service-control", "...")
   875  	cmd := &servicestate.ServiceAction{
   876  		SnapName: "test-snap",
   877  		Action:   "reload-or-restart",
   878  		Services: []string{"foo", "abc", "bar"},
   879  	}
   880  	t.Set("service-action", cmd)
   881  	chg.AddTask(t)
   882  
   883  	st.Unlock()
   884  	defer s.se.Stop()
   885  	err := s.o.Settle(5 * time.Second)
   886  	st.Lock()
   887  	c.Assert(err, IsNil)
   888  
   889  	c.Assert(t.Status(), Equals, state.DoneStatus)
   890  	c.Check(s.sysctlArgs, DeepEquals, [][]string{
   891  		{"reload-or-restart", "snap.test-snap.foo.service"},
   892  		{"reload-or-restart", "snap.test-snap.bar.service"},
   893  		{"reload-or-restart", "snap.test-snap.abc.service"},
   894  	})
   895  }
   896  
   897  func (s *serviceControlSuite) TestConflict(c *C) {
   898  	st := s.state
   899  	st.Lock()
   900  	defer st.Unlock()
   901  
   902  	s.mockTestSnap(c)
   903  
   904  	chg := st.NewChange("service-control", "...")
   905  	t := st.NewTask("service-control", "...")
   906  	cmd := &servicestate.ServiceAction{
   907  		SnapName: "test-snap",
   908  		Action:   "reload-or-restart",
   909  		Services: []string{"foo"},
   910  	}
   911  	t.Set("service-action", cmd)
   912  	chg.AddTask(t)
   913  
   914  	_, err := snapstate.Remove(st, "test-snap", snap.Revision{}, nil)
   915  	c.Assert(err, ErrorMatches, `snap "test-snap" has "service-control" change in progress`)
   916  }
   917  
   918  func (s *serviceControlSuite) TestUpdateSnapstateServices(c *C) {
   919  	var tests = []struct {
   920  		enable                    []string
   921  		disable                   []string
   922  		expectedSnapstateEnabled  []string
   923  		expectedSnapstateDisabled []string
   924  		changed                   bool
   925  	}{
   926  		// These test scenarios share a single SnapState instance and accumulate
   927  		// changes to ServicesEnabledByHooks and ServicesDisabledByHooks.
   928  		{
   929  			changed: false,
   930  		},
   931  		{
   932  			enable: []string{"a"},
   933  			expectedSnapstateEnabled: []string{"a"},
   934  			changed:                  true,
   935  		},
   936  		// enable again does nothing
   937  		{
   938  			enable: []string{"a"},
   939  			expectedSnapstateEnabled: []string{"a"},
   940  			changed:                  false,
   941  		},
   942  		{
   943  			disable:                   []string{"a"},
   944  			expectedSnapstateDisabled: []string{"a"},
   945  			changed:                   true,
   946  		},
   947  		{
   948  			enable: []string{"a", "c"},
   949  			expectedSnapstateEnabled: []string{"a", "c"},
   950  			changed:                  true,
   951  		},
   952  		{
   953  			disable:                   []string{"b"},
   954  			expectedSnapstateEnabled:  []string{"a", "c"},
   955  			expectedSnapstateDisabled: []string{"b"},
   956  			changed:                   true,
   957  		},
   958  		{
   959  			disable:                   []string{"b", "c"},
   960  			expectedSnapstateEnabled:  []string{"a"},
   961  			expectedSnapstateDisabled: []string{"b", "c"},
   962  			changed:                   true,
   963  		},
   964  	}
   965  
   966  	snapst := snapstate.SnapState{}
   967  
   968  	for _, tst := range tests {
   969  		var enable, disable []*snap.AppInfo
   970  		for _, srv := range tst.enable {
   971  			enable = append(enable, &snap.AppInfo{Name: srv})
   972  		}
   973  		for _, srv := range tst.disable {
   974  			disable = append(disable, &snap.AppInfo{Name: srv})
   975  		}
   976  		result, err := servicestate.UpdateSnapstateServices(&snapst, enable, disable)
   977  		c.Assert(err, IsNil)
   978  		c.Check(result, Equals, tst.changed)
   979  		c.Check(snapst.ServicesEnabledByHooks, DeepEquals, tst.expectedSnapstateEnabled)
   980  		c.Check(snapst.ServicesDisabledByHooks, DeepEquals, tst.expectedSnapstateDisabled)
   981  	}
   982  
   983  	services := []*snap.AppInfo{{Name: "foo"}}
   984  	_, err := servicestate.UpdateSnapstateServices(nil, services, services)
   985  	c.Assert(err, ErrorMatches, `internal error: cannot handle enabled and disabled services at the same time`)
   986  }