github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/daemon/api_apps_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-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 daemon_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"io/ioutil"
    29  	"math"
    30  	"net/http"
    31  	"net/http/httptest"
    32  	"sort"
    33  	"strconv"
    34  	"strings"
    35  
    36  	"gopkg.in/check.v1"
    37  
    38  	"github.com/snapcore/snapd/client"
    39  	"github.com/snapcore/snapd/daemon"
    40  	"github.com/snapcore/snapd/overlord/hookstate"
    41  	"github.com/snapcore/snapd/overlord/servicestate"
    42  	"github.com/snapcore/snapd/overlord/snapstate"
    43  	"github.com/snapcore/snapd/overlord/state"
    44  	"github.com/snapcore/snapd/snap"
    45  	"github.com/snapcore/snapd/systemd"
    46  	"github.com/snapcore/snapd/testutil"
    47  )
    48  
    49  var _ = check.Suite(&appsSuite{})
    50  
    51  type appsSuite struct {
    52  	apiBaseSuite
    53  
    54  	journalctlRestorer func()
    55  	jctlSvcses         [][]string
    56  	jctlNs             []int
    57  	jctlFollows        []bool
    58  	jctlRCs            []io.ReadCloser
    59  	jctlErrs           []error
    60  
    61  	serviceControlError error
    62  	serviceControlCalls []serviceControlArgs
    63  
    64  	infoA, infoB, infoC, infoD, infoE *snap.Info
    65  }
    66  
    67  func (s *appsSuite) journalctl(svcs []string, n int, follow bool) (rc io.ReadCloser, err error) {
    68  	s.jctlSvcses = append(s.jctlSvcses, svcs)
    69  	s.jctlNs = append(s.jctlNs, n)
    70  	s.jctlFollows = append(s.jctlFollows, follow)
    71  
    72  	if len(s.jctlErrs) > 0 {
    73  		err, s.jctlErrs = s.jctlErrs[0], s.jctlErrs[1:]
    74  	}
    75  	if len(s.jctlRCs) > 0 {
    76  		rc, s.jctlRCs = s.jctlRCs[0], s.jctlRCs[1:]
    77  	}
    78  
    79  	return rc, err
    80  }
    81  
    82  type serviceControlArgs struct {
    83  	action  string
    84  	options string
    85  	names   []string
    86  }
    87  
    88  func (s *appsSuite) fakeServiceControl(st *state.State, appInfos []*snap.AppInfo, inst *servicestate.Instruction, flags *servicestate.Flags, context *hookstate.Context) ([]*state.TaskSet, error) {
    89  	if flags != nil {
    90  		panic("flags are not expected")
    91  	}
    92  
    93  	if s.serviceControlError != nil {
    94  		return nil, s.serviceControlError
    95  	}
    96  
    97  	serviceCommand := serviceControlArgs{action: inst.Action}
    98  	if inst.RestartOptions.Reload {
    99  		serviceCommand.options = "reload"
   100  	}
   101  	// only one flag should ever be set (depending on Action), but appending
   102  	// them below acts as an extra sanity check.
   103  	if inst.StartOptions.Enable {
   104  		serviceCommand.options += "enable"
   105  	}
   106  	if inst.StopOptions.Disable {
   107  		serviceCommand.options += "disable"
   108  	}
   109  	for _, app := range appInfos {
   110  		serviceCommand.names = append(serviceCommand.names, fmt.Sprintf("%s.%s", app.Snap.InstanceName(), app.Name))
   111  	}
   112  	s.serviceControlCalls = append(s.serviceControlCalls, serviceCommand)
   113  
   114  	t := st.NewTask("dummy", "")
   115  	ts := state.NewTaskSet(t)
   116  	return []*state.TaskSet{ts}, nil
   117  }
   118  
   119  func (s *appsSuite) SetUpSuite(c *check.C) {
   120  	s.apiBaseSuite.SetUpSuite(c)
   121  	s.journalctlRestorer = systemd.MockJournalctl(s.journalctl)
   122  }
   123  
   124  func (s *appsSuite) TearDownSuite(c *check.C) {
   125  	s.journalctlRestorer()
   126  	s.apiBaseSuite.TearDownSuite(c)
   127  }
   128  
   129  func (s *appsSuite) SetUpTest(c *check.C) {
   130  	s.apiBaseSuite.SetUpTest(c)
   131  
   132  	s.jctlSvcses = nil
   133  	s.jctlNs = nil
   134  	s.jctlFollows = nil
   135  	s.jctlRCs = nil
   136  	s.jctlErrs = nil
   137  
   138  	d := s.daemon(c)
   139  
   140  	s.serviceControlCalls = nil
   141  	s.serviceControlError = nil
   142  	restoreServicestateCtrl := daemon.MockServicestateControl(s.fakeServiceControl)
   143  	s.AddCleanup(restoreServicestateCtrl)
   144  
   145  	// turn off ensuring snap services which will call systemctl automatically
   146  	r := servicestate.MockEnsuredSnapServices(s.d.Overlord().ServiceManager(), true)
   147  	s.AddCleanup(r)
   148  
   149  	s.infoA = s.mkInstalledInState(c, s.d, "snap-a", "dev", "v1", snap.R(1), true, "apps: {svc1: {daemon: simple}, svc2: {daemon: simple, reload-command: x}}")
   150  	s.infoB = s.mkInstalledInState(c, s.d, "snap-b", "dev", "v1", snap.R(1), false, "apps: {svc3: {daemon: simple}, cmd1: {}}")
   151  	s.infoC = s.mkInstalledInState(c, s.d, "snap-c", "dev", "v1", snap.R(1), true, "")
   152  	s.infoD = s.mkInstalledInState(c, s.d, "snap-d", "dev", "v1", snap.R(1), true, "apps: {cmd2: {}, cmd3: {}}")
   153  	s.infoE = s.mkInstalledInState(c, s.d, "snap-e", "dev", "v1", snap.R(1), true, "apps: {svc4: {daemon: simple, daemon-scope: user}}")
   154  
   155  	d.Overlord().Loop()
   156  	s.AddCleanup(func() { d.Overlord().Stop() })
   157  }
   158  
   159  func (s *appsSuite) TestSplitAppName(c *check.C) {
   160  	type T struct {
   161  		name string
   162  		snap string
   163  		app  string
   164  	}
   165  
   166  	for _, x := range []T{
   167  		{name: "foo.bar", snap: "foo", app: "bar"},
   168  		{name: "foo", snap: "foo", app: ""},
   169  		{name: "foo.bar.baz", snap: "foo", app: "bar.baz"},
   170  		{name: ".", snap: "", app: ""}, // SISO
   171  	} {
   172  		snap, app := daemon.SplitAppName(x.name)
   173  		c.Check(x.snap, check.Equals, snap, check.Commentf(x.name))
   174  		c.Check(x.app, check.Equals, app, check.Commentf(x.name))
   175  	}
   176  }
   177  
   178  func (s *appsSuite) TestGetAppsInfo(c *check.C) {
   179  	// System services from active snaps
   180  	svcNames := []string{"snap-a.svc1", "snap-a.svc2"}
   181  	for _, name := range svcNames {
   182  		s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(`
   183  Id=snap.%s.service
   184  Type=simple
   185  ActiveState=active
   186  UnitFileState=enabled
   187  `[1:], name)))
   188  	}
   189  	// System services from inactive snaps
   190  	svcNames = append(svcNames, "snap-b.svc3")
   191  	// User services from active snaps
   192  	svcNames = append(svcNames, "snap-e.svc4")
   193  	s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n"))
   194  
   195  	req, err := http.NewRequest("GET", "/v2/apps", nil)
   196  	c.Assert(err, check.IsNil)
   197  
   198  	rsp := s.syncReq(c, req, nil)
   199  	c.Assert(rsp.Status, check.Equals, 200)
   200  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
   201  	apps := rsp.Result.([]client.AppInfo)
   202  	c.Assert(apps, check.HasLen, 7)
   203  
   204  	for _, name := range svcNames {
   205  		snapName, app := daemon.SplitAppName(name)
   206  		needle := client.AppInfo{
   207  			Snap:        snapName,
   208  			Name:        app,
   209  			Daemon:      "simple",
   210  			DaemonScope: snap.SystemDaemon,
   211  		}
   212  		if snapName != "snap-b" {
   213  			// snap-b is not active (all the others are)
   214  			needle.Active = true
   215  			needle.Enabled = true
   216  		}
   217  		if snapName == "snap-e" {
   218  			// snap-e contains user services
   219  			needle.DaemonScope = snap.UserDaemon
   220  			needle.Active = false
   221  		}
   222  		c.Check(apps, testutil.DeepContains, needle)
   223  	}
   224  
   225  	for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} {
   226  		snap, app := daemon.SplitAppName(name)
   227  		c.Check(apps, testutil.DeepContains, client.AppInfo{
   228  			Snap: snap,
   229  			Name: app,
   230  		})
   231  	}
   232  
   233  	appNames := make([]string, len(apps))
   234  	for i, app := range apps {
   235  		appNames[i] = app.Snap + "." + app.Name
   236  	}
   237  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
   238  }
   239  
   240  func (s *appsSuite) TestGetAppsInfoNames(c *check.C) {
   241  
   242  	req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil)
   243  	c.Assert(err, check.IsNil)
   244  
   245  	rsp := s.syncReq(c, req, nil)
   246  	c.Assert(rsp.Status, check.Equals, 200)
   247  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
   248  	apps := rsp.Result.([]client.AppInfo)
   249  	c.Assert(apps, check.HasLen, 2)
   250  
   251  	for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} {
   252  		snap, app := daemon.SplitAppName(name)
   253  		c.Check(apps, testutil.DeepContains, client.AppInfo{
   254  			Snap: snap,
   255  			Name: app,
   256  		})
   257  	}
   258  
   259  	appNames := make([]string, len(apps))
   260  	for i, app := range apps {
   261  		appNames[i] = app.Snap + "." + app.Name
   262  	}
   263  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
   264  }
   265  
   266  func (s *appsSuite) TestGetAppsInfoServices(c *check.C) {
   267  	// System services from active snaps
   268  	svcNames := []string{"snap-a.svc1", "snap-a.svc2"}
   269  	for _, name := range svcNames {
   270  		s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(`
   271  Id=snap.%s.service
   272  Type=simple
   273  ActiveState=active
   274  UnitFileState=enabled
   275  `[1:], name)))
   276  	}
   277  	// System services from inactive snaps
   278  	svcNames = append(svcNames, "snap-b.svc3")
   279  	// User services from active snaps
   280  	svcNames = append(svcNames, "snap-e.svc4")
   281  	s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n"))
   282  
   283  	req, err := http.NewRequest("GET", "/v2/apps?select=service", nil)
   284  	c.Assert(err, check.IsNil)
   285  
   286  	rsp := s.syncReq(c, req, nil)
   287  	c.Assert(rsp.Status, check.Equals, 200)
   288  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
   289  	svcs := rsp.Result.([]client.AppInfo)
   290  	c.Assert(svcs, check.HasLen, 4)
   291  
   292  	for _, name := range svcNames {
   293  		snapName, app := daemon.SplitAppName(name)
   294  		needle := client.AppInfo{
   295  			Snap:        snapName,
   296  			Name:        app,
   297  			Daemon:      "simple",
   298  			DaemonScope: snap.SystemDaemon,
   299  		}
   300  		if snapName != "snap-b" {
   301  			// snap-b is not active (all the others are)
   302  			needle.Active = true
   303  			needle.Enabled = true
   304  		}
   305  		if snapName == "snap-e" {
   306  			// snap-e contains user services
   307  			needle.DaemonScope = snap.UserDaemon
   308  			needle.Active = false
   309  		}
   310  		c.Check(svcs, testutil.DeepContains, needle)
   311  	}
   312  
   313  	appNames := make([]string, len(svcs))
   314  	for i, svc := range svcs {
   315  		appNames[i] = svc.Snap + "." + svc.Name
   316  	}
   317  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
   318  }
   319  
   320  func (s *appsSuite) TestGetAppsInfoBadSelect(c *check.C) {
   321  	req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil)
   322  	c.Assert(err, check.IsNil)
   323  
   324  	rspe := s.errorReq(c, req, nil)
   325  	c.Assert(rspe.Status, check.Equals, 400)
   326  }
   327  
   328  func (s *appsSuite) TestGetAppsInfoBadName(c *check.C) {
   329  	req, err := http.NewRequest("GET", "/v2/apps?names=potato", nil)
   330  	c.Assert(err, check.IsNil)
   331  
   332  	rspe := s.errorReq(c, req, nil)
   333  	c.Assert(rspe.Status, check.Equals, 404)
   334  }
   335  
   336  func (s *appsSuite) TestAppInfosForOne(c *check.C) {
   337  	st := s.d.Overlord().State()
   338  	appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-a.svc1"}, daemon.AppInfoServiceTrue)
   339  	c.Assert(rspe, check.IsNil)
   340  	c.Assert(appInfos, check.HasLen, 1)
   341  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   342  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   343  }
   344  
   345  func (s *appsSuite) TestAppInfosForAll(c *check.C) {
   346  	type T struct {
   347  		opts  daemon.AppInfoOptions
   348  		snaps []*snap.Info
   349  		names []string
   350  	}
   351  
   352  	for _, t := range []T{
   353  		{
   354  			opts:  daemon.AppInfoServiceTrue,
   355  			names: []string{"svc1", "svc2", "svc3", "svc4"},
   356  			snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoE},
   357  		},
   358  		{
   359  			opts:  daemon.AppInfoServiceFalse,
   360  			names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3", "svc4"},
   361  			snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD, s.infoE},
   362  		},
   363  	} {
   364  		c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts))
   365  
   366  		st := s.d.Overlord().State()
   367  		appInfos, rspe := daemon.AppInfosFor(st, nil, t.opts)
   368  		c.Assert(rspe, check.IsNil, check.Commentf("%s", t.opts))
   369  		names := make([]string, len(appInfos))
   370  		for i, appInfo := range appInfos {
   371  			names[i] = appInfo.Name
   372  		}
   373  		c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts))
   374  
   375  		for i := range appInfos {
   376  			c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i]))
   377  		}
   378  	}
   379  }
   380  
   381  func (s *appsSuite) TestAppInfosForOneSnap(c *check.C) {
   382  	st := s.d.Overlord().State()
   383  	appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-a"}, daemon.AppInfoServiceTrue)
   384  	c.Assert(rspe, check.IsNil)
   385  	c.Assert(appInfos, check.HasLen, 2)
   386  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   387  
   388  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   389  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   390  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
   391  	c.Check(appInfos[1].Name, check.Equals, "svc2")
   392  }
   393  
   394  func (s *appsSuite) TestAppInfosForMixedArgs(c *check.C) {
   395  	st := s.d.Overlord().State()
   396  	appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-a", "snap-a.svc1"}, daemon.AppInfoServiceTrue)
   397  	c.Assert(rspe, check.IsNil)
   398  	c.Assert(appInfos, check.HasLen, 2)
   399  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   400  
   401  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   402  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   403  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
   404  	c.Check(appInfos[1].Name, check.Equals, "svc2")
   405  }
   406  
   407  func (s *appsSuite) TestAppInfosCleanupAndSorted(c *check.C) {
   408  	st := s.d.Overlord().State()
   409  	appInfos, rspe := daemon.AppInfosFor(st, []string{
   410  		"snap-b.svc3",
   411  		"snap-a.svc2",
   412  		"snap-a.svc1",
   413  		"snap-a.svc2",
   414  		"snap-b.svc3",
   415  		"snap-a.svc1",
   416  		"snap-b",
   417  		"snap-a",
   418  	}, daemon.AppInfoServiceTrue)
   419  	c.Assert(rspe, check.IsNil)
   420  	c.Assert(appInfos, check.HasLen, 3)
   421  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   422  
   423  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   424  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   425  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
   426  	c.Check(appInfos[1].Name, check.Equals, "svc2")
   427  	c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB)
   428  	c.Check(appInfos[2].Name, check.Equals, "svc3")
   429  }
   430  
   431  func (s *appsSuite) TestAppInfosForAppless(c *check.C) {
   432  	st := s.d.Overlord().State()
   433  	appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-c"}, daemon.AppInfoServiceTrue)
   434  	c.Assert(rspe, check.NotNil)
   435  	c.Check(rspe.Status, check.Equals, 404)
   436  	c.Check(rspe.Kind, check.Equals, client.ErrorKindAppNotFound)
   437  	c.Assert(appInfos, check.IsNil)
   438  }
   439  
   440  func (s *appsSuite) TestAppInfosForMissingApp(c *check.C) {
   441  	st := s.d.Overlord().State()
   442  	appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-c.whatever"}, daemon.AppInfoServiceTrue)
   443  	c.Assert(rspe, check.NotNil)
   444  	c.Check(rspe.Status, check.Equals, 404)
   445  	c.Check(rspe.Kind, check.Equals, client.ErrorKindAppNotFound)
   446  	c.Assert(appInfos, check.IsNil)
   447  }
   448  
   449  func (s *appsSuite) TestAppInfosForMissingSnap(c *check.C) {
   450  	st := s.d.Overlord().State()
   451  	appInfos, rspe := daemon.AppInfosFor(st, []string{"snap-x"}, daemon.AppInfoServiceTrue)
   452  	c.Assert(rspe, check.NotNil)
   453  	c.Check(rspe.Status, check.Equals, 404)
   454  	c.Check(rspe.Kind, check.Equals, client.ErrorKindSnapNotFound)
   455  	c.Assert(appInfos, check.IsNil)
   456  }
   457  
   458  func (s *appsSuite) testPostApps(c *check.C, inst servicestate.Instruction, servicecmds []serviceControlArgs) *state.Change {
   459  	postBody, err := json.Marshal(inst)
   460  	c.Assert(err, check.IsNil)
   461  
   462  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody))
   463  	c.Assert(err, check.IsNil)
   464  
   465  	rsp := s.asyncReq(c, req, nil)
   466  	c.Assert(rsp.Status, check.Equals, 202)
   467  	c.Check(rsp.Change, check.Matches, `[0-9]+`)
   468  
   469  	st := s.d.Overlord().State()
   470  	st.Lock()
   471  	defer st.Unlock()
   472  	chg := st.Change(rsp.Change)
   473  	c.Assert(chg, check.NotNil)
   474  	c.Check(chg.Tasks(), check.HasLen, len(servicecmds))
   475  
   476  	st.Unlock()
   477  	<-chg.Ready()
   478  	st.Lock()
   479  
   480  	c.Check(s.serviceControlCalls, check.DeepEquals, servicecmds)
   481  	return chg
   482  }
   483  
   484  func (s *appsSuite) TestPostAppsStartOne(c *check.C) {
   485  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}}
   486  	expected := []serviceControlArgs{
   487  		{action: "start", names: []string{"snap-a.svc2"}},
   488  	}
   489  	chg := s.testPostApps(c, inst, expected)
   490  	chg.State().Lock()
   491  	defer chg.State().Unlock()
   492  
   493  	var names []string
   494  	err := chg.Get("snap-names", &names)
   495  	c.Assert(err, check.IsNil)
   496  	c.Assert(names, check.DeepEquals, []string{"snap-a"})
   497  }
   498  
   499  func (s *appsSuite) TestPostAppsStartTwo(c *check.C) {
   500  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a"}}
   501  	expected := []serviceControlArgs{
   502  		{action: "start", names: []string{"snap-a.svc1", "snap-a.svc2"}},
   503  	}
   504  	chg := s.testPostApps(c, inst, expected)
   505  
   506  	chg.State().Lock()
   507  	defer chg.State().Unlock()
   508  
   509  	var names []string
   510  	err := chg.Get("snap-names", &names)
   511  	c.Assert(err, check.IsNil)
   512  	c.Assert(names, check.DeepEquals, []string{"snap-a"})
   513  }
   514  
   515  func (s *appsSuite) TestPostAppsStartThree(c *check.C) {
   516  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a", "snap-b"}}
   517  	expected := []serviceControlArgs{
   518  		{action: "start", names: []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}},
   519  	}
   520  	chg := s.testPostApps(c, inst, expected)
   521  	// check the summary expands the snap into actual apps
   522  	c.Check(chg.Summary(), check.Equals, "Running service command")
   523  	chg.State().Lock()
   524  	defer chg.State().Unlock()
   525  
   526  	var names []string
   527  	err := chg.Get("snap-names", &names)
   528  	c.Assert(err, check.IsNil)
   529  	c.Assert(names, check.DeepEquals, []string{"snap-a", "snap-b"})
   530  }
   531  
   532  func (s *appsSuite) TestPostAppsStop(c *check.C) {
   533  	inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}}
   534  	expected := []serviceControlArgs{
   535  		{action: "stop", names: []string{"snap-a.svc2"}},
   536  	}
   537  	s.testPostApps(c, inst, expected)
   538  }
   539  
   540  func (s *appsSuite) TestPostAppsRestart(c *check.C) {
   541  	inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}}
   542  	expected := []serviceControlArgs{
   543  		{action: "restart", names: []string{"snap-a.svc2"}},
   544  	}
   545  	s.testPostApps(c, inst, expected)
   546  }
   547  
   548  func (s *appsSuite) TestPostAppsReload(c *check.C) {
   549  	inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}}
   550  	inst.Reload = true
   551  	expected := []serviceControlArgs{
   552  		{action: "restart", options: "reload", names: []string{"snap-a.svc2"}},
   553  	}
   554  	s.testPostApps(c, inst, expected)
   555  }
   556  
   557  func (s *appsSuite) TestPostAppsEnableNow(c *check.C) {
   558  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}}
   559  	inst.Enable = true
   560  	expected := []serviceControlArgs{
   561  		{action: "start", options: "enable", names: []string{"snap-a.svc2"}},
   562  	}
   563  	s.testPostApps(c, inst, expected)
   564  }
   565  
   566  func (s *appsSuite) TestPostAppsDisableNow(c *check.C) {
   567  	inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}}
   568  	inst.Disable = true
   569  	expected := []serviceControlArgs{
   570  		{action: "stop", options: "disable", names: []string{"snap-a.svc2"}},
   571  	}
   572  	s.testPostApps(c, inst, expected)
   573  }
   574  
   575  func (s *appsSuite) TestPostAppsBadJSON(c *check.C) {
   576  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`))
   577  	c.Assert(err, check.IsNil)
   578  	rspe := s.errorReq(c, req, nil)
   579  	c.Check(rspe.Status, check.Equals, 400)
   580  	c.Check(rspe.Message, check.Matches, ".*cannot decode request body.*")
   581  }
   582  
   583  func (s *appsSuite) TestPostAppsBadOp(c *check.C) {
   584  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`))
   585  	c.Assert(err, check.IsNil)
   586  	rspe := s.errorReq(c, req, nil)
   587  	c.Check(rspe.Status, check.Equals, 400)
   588  	c.Check(rspe.Message, check.Matches, ".*cannot perform operation on services without a list of services.*")
   589  }
   590  
   591  func (s *appsSuite) TestPostAppsBadSnap(c *check.C) {
   592  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`))
   593  	c.Assert(err, check.IsNil)
   594  	rspe := s.errorReq(c, req, nil)
   595  	c.Check(rspe.Status, check.Equals, 404)
   596  	c.Check(rspe.Message, check.Equals, `snap "snap-c" has no services`)
   597  }
   598  
   599  func (s *appsSuite) TestPostAppsBadApp(c *check.C) {
   600  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`))
   601  	c.Assert(err, check.IsNil)
   602  	rspe := s.errorReq(c, req, nil)
   603  	c.Check(rspe.Status, check.Equals, 404)
   604  	c.Check(rspe.Message, check.Equals, `snap "snap-a" has no service "what"`)
   605  }
   606  
   607  func (s *appsSuite) TestPostAppsServiceControlError(c *check.C) {
   608  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`))
   609  	c.Assert(err, check.IsNil)
   610  	s.serviceControlError = fmt.Errorf("total failure")
   611  	rspe := s.errorReq(c, req, nil)
   612  	c.Check(rspe.Status, check.Equals, 400)
   613  	c.Check(rspe.Message, check.Equals, `total failure`)
   614  }
   615  
   616  func (s *appsSuite) TestPostAppsConflict(c *check.C) {
   617  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`))
   618  	c.Assert(err, check.IsNil)
   619  	s.serviceControlError = &snapstate.ChangeConflictError{Snap: "snap-a", ChangeKind: "enable"}
   620  	rspe := s.errorReq(c, req, nil)
   621  	c.Check(rspe.Status, check.Equals, 400)
   622  	c.Check(rspe.Message, check.Equals, `snap "snap-a" has "enable" change in progress`)
   623  }
   624  
   625  func (s *appsSuite) expectLogsAccess() {
   626  	s.expectReadAccess(daemon.AuthenticatedAccess{Polkit: "io.snapcraft.snapd.manage"})
   627  }
   628  
   629  func (s *appsSuite) TestLogs(c *check.C) {
   630  	s.expectLogsAccess()
   631  
   632  	s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(`
   633  {"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"}
   634  {"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"}
   635  {"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"}
   636  {"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"}
   637  {"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"}
   638  	`))}
   639  
   640  	req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil)
   641  	c.Assert(err, check.IsNil)
   642  
   643  	rec := httptest.NewRecorder()
   644  	s.req(c, req, nil).ServeHTTP(rec, req)
   645  
   646  	c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}})
   647  	c.Check(s.jctlNs, check.DeepEquals, []int{42})
   648  	c.Check(s.jctlFollows, check.DeepEquals, []bool{false})
   649  
   650  	c.Check(rec.Code, check.Equals, 200)
   651  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq")
   652  	c.Check(rec.Body.String(), check.Equals, `
   653  {"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"}
   654  {"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"}
   655  {"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"}
   656  {"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"}
   657  {"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"}
   658  `[1:])
   659  }
   660  
   661  func (s *appsSuite) TestLogsN(c *check.C) {
   662  	s.expectLogsAccess()
   663  
   664  	type T struct {
   665  		in  string
   666  		out int
   667  	}
   668  
   669  	for _, t := range []T{
   670  		{in: "", out: 10},
   671  		{in: "0", out: 0},
   672  		{in: "-1", out: -1},
   673  		{in: strconv.Itoa(math.MinInt32), out: math.MinInt32},
   674  		{in: strconv.Itoa(math.MaxInt32), out: math.MaxInt32},
   675  	} {
   676  
   677  		s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(""))}
   678  		s.jctlNs = nil
   679  
   680  		req, err := http.NewRequest("GET", "/v2/logs?n="+t.in, nil)
   681  		c.Assert(err, check.IsNil)
   682  
   683  		rec := httptest.NewRecorder()
   684  		s.req(c, req, nil).ServeHTTP(rec, req)
   685  
   686  		c.Check(s.jctlNs, check.DeepEquals, []int{t.out})
   687  	}
   688  }
   689  
   690  func (s *appsSuite) TestLogsBadN(c *check.C) {
   691  	s.expectLogsAccess()
   692  
   693  	req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil)
   694  	c.Assert(err, check.IsNil)
   695  
   696  	rspe := s.errorReq(c, req, nil)
   697  	c.Assert(rspe.Status, check.Equals, 400)
   698  }
   699  
   700  func (s *appsSuite) TestLogsFollow(c *check.C) {
   701  	s.expectLogsAccess()
   702  
   703  	s.jctlRCs = []io.ReadCloser{
   704  		ioutil.NopCloser(strings.NewReader("")),
   705  		ioutil.NopCloser(strings.NewReader("")),
   706  		ioutil.NopCloser(strings.NewReader("")),
   707  	}
   708  
   709  	reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil)
   710  	c.Assert(err, check.IsNil)
   711  	reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil)
   712  	c.Assert(err, check.IsNil)
   713  	reqN, err := http.NewRequest("GET", "/v2/logs", nil)
   714  	c.Assert(err, check.IsNil)
   715  
   716  	rec := httptest.NewRecorder()
   717  	s.req(c, reqT, nil).ServeHTTP(rec, reqT)
   718  	s.req(c, reqF, nil).ServeHTTP(rec, reqF)
   719  	s.req(c, reqN, nil).ServeHTTP(rec, reqN)
   720  
   721  	c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false})
   722  }
   723  
   724  func (s *appsSuite) TestLogsBadFollow(c *check.C) {
   725  	s.expectLogsAccess()
   726  
   727  	req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil)
   728  	c.Assert(err, check.IsNil)
   729  
   730  	rspe := s.errorReq(c, req, nil)
   731  	c.Assert(rspe.Status, check.Equals, 400)
   732  }
   733  
   734  func (s *appsSuite) TestLogsBadName(c *check.C) {
   735  	s.expectLogsAccess()
   736  
   737  	req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil)
   738  	c.Assert(err, check.IsNil)
   739  
   740  	rspe := s.errorReq(c, req, nil)
   741  	c.Assert(rspe.Status, check.Equals, 404)
   742  }
   743  
   744  func (s *appsSuite) TestLogsSad(c *check.C) {
   745  	s.expectLogsAccess()
   746  
   747  	s.jctlErrs = []error{errors.New("potato")}
   748  	req, err := http.NewRequest("GET", "/v2/logs", nil)
   749  	c.Assert(err, check.IsNil)
   750  
   751  	rspe := s.errorReq(c, req, nil)
   752  	c.Assert(rspe.Status, check.Equals, 500)
   753  }
   754  
   755  func (s *appsSuite) TestLogsNoServices(c *check.C) {
   756  	s.expectLogsAccess()
   757  
   758  	// no installed snaps with services
   759  	st := s.d.Overlord().State()
   760  	st.Lock()
   761  	st.Set("snaps", nil)
   762  	st.Unlock()
   763  
   764  	req, err := http.NewRequest("GET", "/v2/logs", nil)
   765  	c.Assert(err, check.IsNil)
   766  
   767  	rspe := s.errorReq(c, req, nil)
   768  	c.Assert(rspe.Status, check.Equals, 404)
   769  }