github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/hookstate/ctlcmd/services_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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 ctlcmd_test
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"sort"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/client"
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/overlord/auth"
    32  	"github.com/snapcore/snapd/overlord/hookstate"
    33  	"github.com/snapcore/snapd/overlord/hookstate/ctlcmd"
    34  	"github.com/snapcore/snapd/overlord/hookstate/hooktest"
    35  	"github.com/snapcore/snapd/overlord/servicestate"
    36  	"github.com/snapcore/snapd/overlord/snapstate"
    37  	"github.com/snapcore/snapd/overlord/snapstate/snapstatetest"
    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/store"
    42  	"github.com/snapcore/snapd/store/storetest"
    43  	"github.com/snapcore/snapd/systemd"
    44  	"github.com/snapcore/snapd/testutil"
    45  )
    46  
    47  type fakeStore struct {
    48  	storetest.Store
    49  }
    50  
    51  func (f *fakeStore) SnapAction(_ context.Context, currentSnaps []*store.CurrentSnap, actions []*store.SnapAction, assertQuery store.AssertionQuery, user *auth.UserState, opts *store.RefreshOptions) ([]store.SnapActionResult, []store.AssertionResult, error) {
    52  	if assertQuery != nil {
    53  		panic("no assertion query support")
    54  	}
    55  	if actions[0].Action == "install" {
    56  		installs := make([]store.SnapActionResult, 0, len(actions))
    57  		for _, a := range actions {
    58  			snapName, instanceKey := snap.SplitInstanceName(a.InstanceName)
    59  			if instanceKey != "" {
    60  				panic(fmt.Sprintf("unexpected instance name %q in snap install action", a.InstanceName))
    61  			}
    62  
    63  			installs = append(installs, store.SnapActionResult{Info: &snap.Info{
    64  				DownloadInfo: snap.DownloadInfo{
    65  					Size: 1,
    66  				},
    67  				SideInfo: snap.SideInfo{
    68  					RealName: snapName,
    69  					Revision: snap.R(2),
    70  				},
    71  				Architectures: []string{"all"},
    72  			}})
    73  		}
    74  
    75  		return installs, nil, nil
    76  	}
    77  
    78  	snaps := []store.SnapActionResult{{Info: &snap.Info{
    79  		SideInfo: snap.SideInfo{
    80  			RealName: "test-snap",
    81  			Revision: snap.R(2),
    82  			SnapID:   "test-snap-id",
    83  		},
    84  		Architectures: []string{"all"},
    85  	}}, {Info: &snap.Info{
    86  		SideInfo: snap.SideInfo{
    87  			RealName: "other-snap",
    88  			Revision: snap.R(2),
    89  			SnapID:   "other-snap-id",
    90  		},
    91  		Architectures: []string{"all"},
    92  	}}}
    93  	return snaps, nil, nil
    94  }
    95  
    96  type servicectlSuite struct {
    97  	testutil.BaseTest
    98  	st          *state.State
    99  	fakeStore   fakeStore
   100  	mockContext *hookstate.Context
   101  	mockHandler *hooktest.MockHandler
   102  }
   103  
   104  var _ = Suite(&servicectlSuite{})
   105  
   106  const testSnapYaml = `name: test-snap
   107  version: 1.0
   108  summary: test-snap
   109  apps:
   110   normal-app:
   111    command: bin/dummy
   112   test-service:
   113    command: bin/service
   114    daemon: simple
   115    reload-command: bin/reload
   116   another-service:
   117    command: bin/service
   118    daemon: simple
   119    reload-command: bin/reload
   120  `
   121  
   122  const otherSnapYaml = `name: other-snap
   123  version: 1.0
   124  summary: other-snap
   125  apps:
   126   test-service:
   127    command: bin/service
   128    daemon: simple
   129    reload-command: bin/reload
   130  `
   131  
   132  func mockServiceChangeFunc(testServiceControlInputs func(appInfos []*snap.AppInfo, inst *servicestate.Instruction)) func() {
   133  	return ctlcmd.MockServicestateControlFunc(func(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, context *hookstate.Context) ([]*state.TaskSet, error) {
   134  		testServiceControlInputs(appInfos, inst)
   135  		return nil, fmt.Errorf("forced error")
   136  	})
   137  }
   138  
   139  func (s *servicectlSuite) SetUpTest(c *C) {
   140  	s.BaseTest.SetUpTest(c)
   141  	oldRoot := dirs.GlobalRootDir
   142  	dirs.SetRootDir(c.MkDir())
   143  
   144  	testutil.MockCommand(c, "systemctl", "")
   145  
   146  	s.BaseTest.AddCleanup(func() {
   147  		dirs.SetRootDir(oldRoot)
   148  	})
   149  	s.BaseTest.AddCleanup(snap.MockSanitizePlugsSlots(func(snapInfo *snap.Info) {}))
   150  
   151  	s.mockHandler = hooktest.NewMockHandler()
   152  
   153  	s.st = state.New(nil)
   154  	s.st.Lock()
   155  	defer s.st.Unlock()
   156  
   157  	snapstate.ReplaceStore(s.st, &s.fakeStore)
   158  
   159  	// mock installed snaps
   160  	info1 := snaptest.MockSnapCurrent(c, string(testSnapYaml), &snap.SideInfo{
   161  		Revision: snap.R(1),
   162  	})
   163  	info2 := snaptest.MockSnapCurrent(c, string(otherSnapYaml), &snap.SideInfo{
   164  		Revision: snap.R(1),
   165  	})
   166  	snapstate.Set(s.st, info1.InstanceName(), &snapstate.SnapState{
   167  		Active: true,
   168  		Sequence: []*snap.SideInfo{
   169  			{
   170  				RealName: info1.SnapName(),
   171  				Revision: info1.Revision,
   172  				SnapID:   "test-snap-id",
   173  			},
   174  		},
   175  		Current: info1.Revision,
   176  	})
   177  	snapstate.Set(s.st, info2.InstanceName(), &snapstate.SnapState{
   178  		Active: true,
   179  		Sequence: []*snap.SideInfo{
   180  			{
   181  				RealName: info2.SnapName(),
   182  				Revision: info2.Revision,
   183  				SnapID:   "other-snap-id",
   184  			},
   185  		},
   186  		Current: info2.Revision,
   187  	})
   188  
   189  	task := s.st.NewTask("test-task", "my test task")
   190  	setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "test-hook"}
   191  
   192  	var err error
   193  	s.mockContext, err = hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
   194  	c.Assert(err, IsNil)
   195  
   196  	s.st.Set("seeded", true)
   197  	s.st.Set("refresh-privacy-key", "privacy-key")
   198  	s.AddCleanup(snapstatetest.UseFallbackDeviceModel())
   199  }
   200  
   201  func (s *servicectlSuite) TestStopCommand(c *C) {
   202  	var serviceChangeFuncCalled bool
   203  	restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
   204  		serviceChangeFuncCalled = true
   205  		c.Assert(appInfos, HasLen, 1)
   206  		c.Assert(appInfos[0].Name, Equals, "test-service")
   207  		c.Assert(inst, DeepEquals, &servicestate.Instruction{
   208  			Action: "stop",
   209  			Names:  []string{"test-snap.test-service"},
   210  			StopOptions: client.StopOptions{
   211  				Disable: false,
   212  			},
   213  		},
   214  		)
   215  	})
   216  	defer restore()
   217  	_, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "test-snap.test-service"}, 0)
   218  	c.Assert(err, NotNil)
   219  	c.Check(err, ErrorMatches, "forced error")
   220  	c.Assert(serviceChangeFuncCalled, Equals, true)
   221  }
   222  
   223  func (s *servicectlSuite) TestStopCommandUnknownService(c *C) {
   224  	var serviceChangeFuncCalled bool
   225  	restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
   226  		serviceChangeFuncCalled = true
   227  	})
   228  	defer restore()
   229  	_, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "test-snap.fooservice"}, 0)
   230  	c.Assert(err, NotNil)
   231  	c.Assert(err, ErrorMatches, `unknown service: "test-snap.fooservice"`)
   232  	c.Assert(serviceChangeFuncCalled, Equals, false)
   233  }
   234  
   235  func (s *servicectlSuite) TestStopCommandFailsOnOtherSnap(c *C) {
   236  	var serviceChangeFuncCalled bool
   237  	restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
   238  		serviceChangeFuncCalled = true
   239  	})
   240  	defer restore()
   241  	// verify that snapctl is not allowed to control services of other snaps (only the one of its hook)
   242  	_, _, err := ctlcmd.Run(s.mockContext, []string{"stop", "other-snap.test-service"}, 0)
   243  	c.Check(err, NotNil)
   244  	c.Assert(err, ErrorMatches, `unknown service: "other-snap.test-service"`)
   245  	c.Assert(serviceChangeFuncCalled, Equals, false)
   246  }
   247  
   248  func (s *servicectlSuite) TestStartCommand(c *C) {
   249  	var serviceChangeFuncCalled bool
   250  	restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
   251  		serviceChangeFuncCalled = true
   252  		c.Assert(appInfos, HasLen, 1)
   253  		c.Assert(appInfos[0].Name, Equals, "test-service")
   254  		c.Assert(inst, DeepEquals, &servicestate.Instruction{
   255  			Action: "start",
   256  			Names:  []string{"test-snap.test-service"},
   257  			StartOptions: client.StartOptions{
   258  				Enable: false,
   259  			},
   260  		},
   261  		)
   262  	})
   263  	defer restore()
   264  	_, _, err := ctlcmd.Run(s.mockContext, []string{"start", "test-snap.test-service"}, 0)
   265  	c.Check(err, NotNil)
   266  	c.Check(err, ErrorMatches, "forced error")
   267  	c.Assert(serviceChangeFuncCalled, Equals, true)
   268  }
   269  
   270  func (s *servicectlSuite) TestRestartCommand(c *C) {
   271  	var serviceChangeFuncCalled bool
   272  	restore := mockServiceChangeFunc(func(appInfos []*snap.AppInfo, inst *servicestate.Instruction) {
   273  		serviceChangeFuncCalled = true
   274  		c.Assert(appInfos, HasLen, 1)
   275  		c.Assert(appInfos[0].Name, Equals, "test-service")
   276  		c.Assert(inst, DeepEquals, &servicestate.Instruction{
   277  			Action: "restart",
   278  			Names:  []string{"test-snap.test-service"},
   279  			RestartOptions: client.RestartOptions{
   280  				Reload: false,
   281  			},
   282  		},
   283  		)
   284  	})
   285  	defer restore()
   286  	_, _, err := ctlcmd.Run(s.mockContext, []string{"restart", "test-snap.test-service"}, 0)
   287  	c.Check(err, NotNil)
   288  	c.Check(err, ErrorMatches, "forced error")
   289  	c.Assert(serviceChangeFuncCalled, Equals, true)
   290  }
   291  
   292  func (s *servicectlSuite) TestConflictingChange(c *C) {
   293  	s.st.Lock()
   294  	task := s.st.NewTask("link-snap", "conflicting task")
   295  	snapsup := snapstate.SnapSetup{
   296  		SideInfo: &snap.SideInfo{
   297  			RealName: "test-snap",
   298  			SnapID:   "test-snap-id-1",
   299  			Revision: snap.R(1),
   300  		},
   301  	}
   302  	task.Set("snap-setup", snapsup)
   303  	chg := s.st.NewChange("conflicting change", "install change")
   304  	chg.AddTask(task)
   305  	s.st.Unlock()
   306  
   307  	_, _, err := ctlcmd.Run(s.mockContext, []string{"start", "test-snap.test-service"}, 0)
   308  	c.Check(err, NotNil)
   309  	c.Check(err, ErrorMatches, `snap "test-snap" has "conflicting change" change in progress`)
   310  }
   311  
   312  var (
   313  	installTaskKinds = []string{
   314  		"prerequisites",
   315  		"download-snap",
   316  		"validate-snap",
   317  		"mount-snap",
   318  		"copy-snap-data",
   319  		"setup-profiles",
   320  		"link-snap",
   321  		"auto-connect",
   322  		"set-auto-aliases",
   323  		"setup-aliases",
   324  		"run-hook[install]",
   325  		"start-snap-services",
   326  		"run-hook[configure]",
   327  		"run-hook[check-health]",
   328  	}
   329  
   330  	refreshTaskKinds = []string{
   331  		"prerequisites",
   332  		"download-snap",
   333  		"validate-snap",
   334  		"mount-snap",
   335  		"run-hook[pre-refresh]",
   336  		"stop-snap-services",
   337  		"remove-aliases",
   338  		"unlink-current-snap",
   339  		"copy-snap-data",
   340  		"setup-profiles",
   341  		"link-snap",
   342  		"auto-connect",
   343  		"set-auto-aliases",
   344  		"setup-aliases",
   345  		"run-hook[post-refresh]",
   346  		"start-snap-services",
   347  		"cleanup",
   348  		"run-hook[configure]",
   349  		"run-hook[check-health]",
   350  	}
   351  )
   352  
   353  func (s *servicectlSuite) TestQueuedCommands(c *C) {
   354  	s.st.Lock()
   355  
   356  	chg := s.st.NewChange("install change", "install change")
   357  	installed, tts, err := snapstate.InstallMany(s.st, []string{"one", "two"}, 0)
   358  	c.Assert(err, IsNil)
   359  	c.Check(installed, DeepEquals, []string{"one", "two"})
   360  	c.Assert(tts, HasLen, 2)
   361  	c.Assert(taskKinds(tts[0].Tasks()), DeepEquals, installTaskKinds)
   362  	c.Assert(taskKinds(tts[1].Tasks()), DeepEquals, installTaskKinds)
   363  	chg.AddAll(tts[0])
   364  	chg.AddAll(tts[1])
   365  
   366  	s.st.Unlock()
   367  
   368  	for _, ts := range tts {
   369  		tsTasks := ts.Tasks()
   370  		// assumes configure task is last
   371  		task := tsTasks[len(tsTasks)-1]
   372  		c.Assert(task.Kind(), Equals, "run-hook")
   373  		setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"}
   374  		context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
   375  		c.Assert(err, IsNil)
   376  
   377  		_, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0)
   378  		c.Check(err, IsNil)
   379  		_, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0)
   380  		c.Check(err, IsNil)
   381  		_, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}, 0)
   382  		c.Check(err, IsNil)
   383  	}
   384  
   385  	s.st.Lock()
   386  	defer s.st.Unlock()
   387  
   388  	expectedTaskKinds := append(installTaskKinds, "exec-command", "exec-command", "exec-command")
   389  	for i := 1; i <= 2; i++ {
   390  		laneTasks := chg.LaneTasks(i)
   391  		c.Assert(taskKinds(laneTasks), DeepEquals, expectedTaskKinds)
   392  		c.Check(laneTasks[12].Summary(), Matches, `Run configure hook of .* snap if present`)
   393  		c.Check(laneTasks[14].Summary(), Equals, "stop of [test-snap.test-service]")
   394  		c.Check(laneTasks[15].Summary(), Equals, "start of [test-snap.test-service]")
   395  		c.Check(laneTasks[16].Summary(), Equals, "restart of [test-snap.test-service]")
   396  	}
   397  }
   398  
   399  func (s *servicectlSuite) testQueueCommandsOrdering(c *C, finalTaskKind string) {
   400  	s.st.Lock()
   401  
   402  	chg := s.st.NewChange("seeding change", "seeding change")
   403  	finalTask := s.st.NewTask(finalTaskKind, "")
   404  	chg.AddTask(finalTask)
   405  	configure := s.st.NewTask("run-hook", "")
   406  	chg.AddTask(configure)
   407  
   408  	s.st.Unlock()
   409  
   410  	setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"}
   411  	context, err := hookstate.NewContext(configure, configure.State(), setup, s.mockHandler, "")
   412  	c.Assert(err, IsNil)
   413  
   414  	_, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0)
   415  	c.Check(err, IsNil)
   416  	_, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0)
   417  	c.Check(err, IsNil)
   418  
   419  	s.st.Lock()
   420  	defer s.st.Unlock()
   421  
   422  	finalTaskWt := finalTask.WaitTasks()
   423  	c.Assert(finalTaskWt, HasLen, 2)
   424  
   425  	for _, t := range finalTaskWt {
   426  		// mark-seeded tasks should wait for both exec-command tasks
   427  		c.Check(t.Kind(), Equals, "exec-command")
   428  		var argv []string
   429  		c.Assert(t.Get("argv", &argv), IsNil)
   430  		c.Check(argv, HasLen, 3)
   431  
   432  		commandWt := make(map[string]bool)
   433  		for _, wt := range t.WaitTasks() {
   434  			commandWt[wt.Kind()] = true
   435  		}
   436  		// exec-command for "stop" should wait for configure hook task, "start" should wait for "stop" and "configure" hook task.
   437  		switch argv[1] {
   438  		case "stop":
   439  			c.Check(commandWt, DeepEquals, map[string]bool{"run-hook": true})
   440  		case "start":
   441  			c.Check(commandWt, DeepEquals, map[string]bool{"run-hook": true, "exec-command": true})
   442  		default:
   443  			c.Fatalf("unexpected command: %q", argv[1])
   444  		}
   445  	}
   446  	c.Check(finalTask.HaltTasks(), HasLen, 0)
   447  }
   448  
   449  func (s *servicectlSuite) TestQueuedCommandsRunBeforeMarkSeeded(c *C) {
   450  	s.testQueueCommandsOrdering(c, "mark-seeded")
   451  }
   452  
   453  func (s *servicectlSuite) TestQueuedCommandsRunBeforeSetModel(c *C) {
   454  	s.testQueueCommandsOrdering(c, "set-model")
   455  }
   456  
   457  func (s *servicectlSuite) TestQueuedCommandsUpdateMany(c *C) {
   458  	oldAutoAliases := snapstate.AutoAliases
   459  	snapstate.AutoAliases = func(*state.State, *snap.Info) (map[string]string, error) {
   460  		return nil, nil
   461  	}
   462  	defer func() { snapstate.AutoAliases = oldAutoAliases }()
   463  
   464  	s.st.Lock()
   465  
   466  	chg := s.st.NewChange("update many change", "update change")
   467  	installed, tts, err := snapstate.UpdateMany(context.Background(), s.st, []string{"test-snap", "other-snap"}, 0, nil)
   468  	c.Assert(err, IsNil)
   469  	sort.Strings(installed)
   470  	c.Check(installed, DeepEquals, []string{"other-snap", "test-snap"})
   471  	c.Assert(tts, HasLen, 3)
   472  	c.Assert(taskKinds(tts[0].Tasks()), DeepEquals, refreshTaskKinds)
   473  	c.Assert(taskKinds(tts[1].Tasks()), DeepEquals, refreshTaskKinds)
   474  	c.Assert(taskKinds(tts[2].Tasks()), DeepEquals, []string{"check-rerefresh"})
   475  	c.Assert(tts[2].Tasks()[0].Kind(), Equals, "check-rerefresh")
   476  	chg.AddAll(tts[0])
   477  	chg.AddAll(tts[1])
   478  
   479  	s.st.Unlock()
   480  
   481  	for _, ts := range tts[:2] {
   482  		tsTasks := ts.Tasks()
   483  		// assumes configure task is last
   484  		task := tsTasks[len(tsTasks)-1]
   485  		c.Assert(task.Kind(), Equals, "run-hook")
   486  		setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"}
   487  		context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
   488  		c.Assert(err, IsNil)
   489  
   490  		_, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0)
   491  		c.Check(err, IsNil)
   492  		_, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0)
   493  		c.Check(err, IsNil)
   494  		_, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}, 0)
   495  		c.Check(err, IsNil)
   496  	}
   497  
   498  	s.st.Lock()
   499  	defer s.st.Unlock()
   500  
   501  	expectedTaskKinds := append(refreshTaskKinds, "exec-command", "exec-command", "exec-command")
   502  	for i := 1; i <= 2; i++ {
   503  		laneTasks := chg.LaneTasks(i)
   504  		c.Assert(taskKinds(laneTasks), DeepEquals, expectedTaskKinds)
   505  		c.Check(laneTasks[17].Summary(), Matches, `Run configure hook of .* snap if present`)
   506  		c.Check(laneTasks[19].Summary(), Equals, "stop of [test-snap.test-service]")
   507  		c.Check(laneTasks[20].Summary(), Equals, "start of [test-snap.test-service]")
   508  		c.Check(laneTasks[21].Summary(), Equals, "restart of [test-snap.test-service]")
   509  	}
   510  }
   511  
   512  func (s *servicectlSuite) TestQueuedCommandsSingleLane(c *C) {
   513  	s.st.Lock()
   514  
   515  	chg := s.st.NewChange("install change", "install change")
   516  	ts, err := snapstate.Install(context.Background(), s.st, "one", &snapstate.RevisionOptions{Revision: snap.R(1)}, 0, snapstate.Flags{})
   517  	c.Assert(err, IsNil)
   518  	c.Assert(taskKinds(ts.Tasks()), DeepEquals, installTaskKinds)
   519  	chg.AddAll(ts)
   520  
   521  	s.st.Unlock()
   522  
   523  	tsTasks := ts.Tasks()
   524  	// assumes configure task is last
   525  	task := tsTasks[len(tsTasks)-1]
   526  	c.Assert(task.Kind(), Equals, "run-hook")
   527  	setup := &hookstate.HookSetup{Snap: "test-snap", Revision: snap.R(1), Hook: "configure"}
   528  	context, err := hookstate.NewContext(task, task.State(), setup, s.mockHandler, "")
   529  	c.Assert(err, IsNil)
   530  
   531  	_, _, err = ctlcmd.Run(context, []string{"stop", "test-snap.test-service"}, 0)
   532  	c.Check(err, IsNil)
   533  	_, _, err = ctlcmd.Run(context, []string{"start", "test-snap.test-service"}, 0)
   534  	c.Check(err, IsNil)
   535  	_, _, err = ctlcmd.Run(context, []string{"restart", "test-snap.test-service"}, 0)
   536  	c.Check(err, IsNil)
   537  
   538  	s.st.Lock()
   539  	defer s.st.Unlock()
   540  
   541  	laneTasks := chg.LaneTasks(0)
   542  	c.Assert(taskKinds(laneTasks), DeepEquals, append(installTaskKinds, "exec-command", "exec-command", "exec-command"))
   543  	c.Check(laneTasks[12].Summary(), Matches, `Run configure hook of .* snap if present`)
   544  	c.Check(laneTasks[14].Summary(), Equals, "stop of [test-snap.test-service]")
   545  	c.Check(laneTasks[15].Summary(), Equals, "start of [test-snap.test-service]")
   546  	c.Check(laneTasks[16].Summary(), Equals, "restart of [test-snap.test-service]")
   547  }
   548  
   549  func (s *servicectlSuite) TestTwoServices(c *C) {
   550  	restore := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) {
   551  		c.Assert(args[0], Equals, "show")
   552  		c.Check(args[2], Matches, `snap\.test-snap\.\w+-service\.service`)
   553  		return []byte(fmt.Sprintf(`Id=%s
   554  Type=simple
   555  ActiveState=active
   556  UnitFileState=enabled
   557  `, args[2])), nil
   558  	})
   559  	defer restore()
   560  
   561  	stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"services"}, 0)
   562  	c.Assert(err, IsNil)
   563  	c.Check(string(stdout), Equals, `
   564  Service                    Startup  Current  Notes
   565  test-snap.another-service  enabled  active   -
   566  test-snap.test-service     enabled  active   -
   567  `[1:])
   568  	c.Check(string(stderr), Equals, "")
   569  }
   570  
   571  func (s *servicectlSuite) TestServices(c *C) {
   572  	restore := systemd.MockSystemctl(func(args ...string) (buf []byte, err error) {
   573  		c.Assert(args[0], Equals, "show")
   574  		c.Check(args[2], Equals, "snap.test-snap.test-service.service")
   575  		return []byte(`Id=snap.test-snap.test-service.service
   576  Type=simple
   577  ActiveState=active
   578  UnitFileState=enabled
   579  `), nil
   580  	})
   581  	defer restore()
   582  
   583  	stdout, stderr, err := ctlcmd.Run(s.mockContext, []string{"services", "test-snap.test-service"}, 0)
   584  	c.Assert(err, IsNil)
   585  	c.Check(string(stdout), Equals, `
   586  Service                 Startup  Current  Notes
   587  test-snap.test-service  enabled  active   -
   588  `[1:])
   589  	c.Check(string(stderr), Equals, "")
   590  }