github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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  	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}}")
   146  	s.infoB = s.mkInstalledInState(c, s.d, "snap-b", "dev", "v1", snap.R(1), false, "apps: {svc3: {daemon: simple}, cmd1: {}}")
   147  	s.infoC = s.mkInstalledInState(c, s.d, "snap-c", "dev", "v1", snap.R(1), true, "")
   148  	s.infoD = s.mkInstalledInState(c, s.d, "snap-d", "dev", "v1", snap.R(1), true, "apps: {cmd2: {}, cmd3: {}}")
   149  	s.infoE = s.mkInstalledInState(c, s.d, "snap-e", "dev", "v1", snap.R(1), true, "apps: {svc4: {daemon: simple, daemon-scope: user}}")
   150  
   151  	d.Overlord().Loop()
   152  	s.AddCleanup(func() { d.Overlord().Stop() })
   153  }
   154  
   155  func (s *appsSuite) TestSplitAppName(c *check.C) {
   156  	type T struct {
   157  		name string
   158  		snap string
   159  		app  string
   160  	}
   161  
   162  	for _, x := range []T{
   163  		{name: "foo.bar", snap: "foo", app: "bar"},
   164  		{name: "foo", snap: "foo", app: ""},
   165  		{name: "foo.bar.baz", snap: "foo", app: "bar.baz"},
   166  		{name: ".", snap: "", app: ""}, // SISO
   167  	} {
   168  		snap, app := daemon.SplitAppName(x.name)
   169  		c.Check(x.snap, check.Equals, snap, check.Commentf(x.name))
   170  		c.Check(x.app, check.Equals, app, check.Commentf(x.name))
   171  	}
   172  }
   173  
   174  func (s *appsSuite) TestGetAppsInfo(c *check.C) {
   175  	// System services from active snaps
   176  	svcNames := []string{"snap-a.svc1", "snap-a.svc2"}
   177  	for _, name := range svcNames {
   178  		s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(`
   179  Id=snap.%s.service
   180  Type=simple
   181  ActiveState=active
   182  UnitFileState=enabled
   183  `[1:], name)))
   184  	}
   185  	// System services from inactive snaps
   186  	svcNames = append(svcNames, "snap-b.svc3")
   187  	// User services from active snaps
   188  	svcNames = append(svcNames, "snap-e.svc4")
   189  	s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n"))
   190  
   191  	req, err := http.NewRequest("GET", "/v2/apps", nil)
   192  	c.Assert(err, check.IsNil)
   193  
   194  	rsp := s.req(c, req, nil).(*daemon.Resp)
   195  	c.Assert(rsp.Status, check.Equals, 200)
   196  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   197  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
   198  	apps := rsp.Result.([]client.AppInfo)
   199  	c.Assert(apps, check.HasLen, 7)
   200  
   201  	for _, name := range svcNames {
   202  		snapName, app := daemon.SplitAppName(name)
   203  		needle := client.AppInfo{
   204  			Snap:        snapName,
   205  			Name:        app,
   206  			Daemon:      "simple",
   207  			DaemonScope: snap.SystemDaemon,
   208  		}
   209  		if snapName != "snap-b" {
   210  			// snap-b is not active (all the others are)
   211  			needle.Active = true
   212  			needle.Enabled = true
   213  		}
   214  		if snapName == "snap-e" {
   215  			// snap-e contains user services
   216  			needle.DaemonScope = snap.UserDaemon
   217  			needle.Active = false
   218  		}
   219  		c.Check(apps, testutil.DeepContains, needle)
   220  	}
   221  
   222  	for _, name := range []string{"snap-b.cmd1", "snap-d.cmd2", "snap-d.cmd3"} {
   223  		snap, app := daemon.SplitAppName(name)
   224  		c.Check(apps, testutil.DeepContains, client.AppInfo{
   225  			Snap: snap,
   226  			Name: app,
   227  		})
   228  	}
   229  
   230  	appNames := make([]string, len(apps))
   231  	for i, app := range apps {
   232  		appNames[i] = app.Snap + "." + app.Name
   233  	}
   234  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
   235  }
   236  
   237  func (s *appsSuite) TestGetAppsInfoNames(c *check.C) {
   238  
   239  	req, err := http.NewRequest("GET", "/v2/apps?names=snap-d", nil)
   240  	c.Assert(err, check.IsNil)
   241  
   242  	rsp := s.req(c, req, nil).(*daemon.Resp)
   243  	c.Assert(rsp.Status, check.Equals, 200)
   244  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   245  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
   246  	apps := rsp.Result.([]client.AppInfo)
   247  	c.Assert(apps, check.HasLen, 2)
   248  
   249  	for _, name := range []string{"snap-d.cmd2", "snap-d.cmd3"} {
   250  		snap, app := daemon.SplitAppName(name)
   251  		c.Check(apps, testutil.DeepContains, client.AppInfo{
   252  			Snap: snap,
   253  			Name: app,
   254  		})
   255  	}
   256  
   257  	appNames := make([]string, len(apps))
   258  	for i, app := range apps {
   259  		appNames[i] = app.Snap + "." + app.Name
   260  	}
   261  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
   262  }
   263  
   264  func (s *appsSuite) TestGetAppsInfoServices(c *check.C) {
   265  	// System services from active snaps
   266  	svcNames := []string{"snap-a.svc1", "snap-a.svc2"}
   267  	for _, name := range svcNames {
   268  		s.SysctlBufs = append(s.SysctlBufs, []byte(fmt.Sprintf(`
   269  Id=snap.%s.service
   270  Type=simple
   271  ActiveState=active
   272  UnitFileState=enabled
   273  `[1:], name)))
   274  	}
   275  	// System services from inactive snaps
   276  	svcNames = append(svcNames, "snap-b.svc3")
   277  	// User services from active snaps
   278  	svcNames = append(svcNames, "snap-e.svc4")
   279  	s.SysctlBufs = append(s.SysctlBufs, []byte("enabled\n"))
   280  
   281  	req, err := http.NewRequest("GET", "/v2/apps?select=service", nil)
   282  	c.Assert(err, check.IsNil)
   283  
   284  	rsp := s.req(c, req, nil).(*daemon.Resp)
   285  	c.Assert(rsp.Status, check.Equals, 200)
   286  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   287  	c.Assert(rsp.Result, check.FitsTypeOf, []client.AppInfo{})
   288  	svcs := rsp.Result.([]client.AppInfo)
   289  	c.Assert(svcs, check.HasLen, 4)
   290  
   291  	for _, name := range svcNames {
   292  		snapName, app := daemon.SplitAppName(name)
   293  		needle := client.AppInfo{
   294  			Snap:        snapName,
   295  			Name:        app,
   296  			Daemon:      "simple",
   297  			DaemonScope: snap.SystemDaemon,
   298  		}
   299  		if snapName != "snap-b" {
   300  			// snap-b is not active (all the others are)
   301  			needle.Active = true
   302  			needle.Enabled = true
   303  		}
   304  		if snapName == "snap-e" {
   305  			// snap-e contains user services
   306  			needle.DaemonScope = snap.UserDaemon
   307  			needle.Active = false
   308  		}
   309  		c.Check(svcs, testutil.DeepContains, needle)
   310  	}
   311  
   312  	appNames := make([]string, len(svcs))
   313  	for i, svc := range svcs {
   314  		appNames[i] = svc.Snap + "." + svc.Name
   315  	}
   316  	c.Check(sort.StringsAreSorted(appNames), check.Equals, true)
   317  }
   318  
   319  func (s *appsSuite) TestGetAppsInfoBadSelect(c *check.C) {
   320  	req, err := http.NewRequest("GET", "/v2/apps?select=potato", nil)
   321  	c.Assert(err, check.IsNil)
   322  
   323  	rsp := s.req(c, req, nil).(*daemon.Resp)
   324  	c.Assert(rsp.Status, check.Equals, 400)
   325  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   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  	rsp := s.req(c, req, nil).(*daemon.Resp)
   333  	c.Assert(rsp.Status, check.Equals, 404)
   334  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   335  }
   336  
   337  func (s *appsSuite) TestAppInfosForOne(c *check.C) {
   338  	st := s.d.Overlord().State()
   339  	appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a.svc1"}, daemon.AppInfoServiceTrue)
   340  	c.Assert(rsp, check.IsNil)
   341  	c.Assert(appInfos, check.HasLen, 1)
   342  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   343  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   344  }
   345  
   346  func (s *appsSuite) TestAppInfosForAll(c *check.C) {
   347  	type T struct {
   348  		opts  daemon.AppInfoOptions
   349  		snaps []*snap.Info
   350  		names []string
   351  	}
   352  
   353  	for _, t := range []T{
   354  		{
   355  			opts:  daemon.AppInfoServiceTrue,
   356  			names: []string{"svc1", "svc2", "svc3", "svc4"},
   357  			snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoE},
   358  		},
   359  		{
   360  			opts:  daemon.AppInfoServiceFalse,
   361  			names: []string{"svc1", "svc2", "cmd1", "svc3", "cmd2", "cmd3", "svc4"},
   362  			snaps: []*snap.Info{s.infoA, s.infoA, s.infoB, s.infoB, s.infoD, s.infoD, s.infoE},
   363  		},
   364  	} {
   365  		c.Assert(len(t.names), check.Equals, len(t.snaps), check.Commentf("%s", t.opts))
   366  
   367  		st := s.d.Overlord().State()
   368  		appInfos, rsp := daemon.AppInfosFor(st, nil, t.opts)
   369  		c.Assert(rsp, check.IsNil, check.Commentf("%s", t.opts))
   370  		names := make([]string, len(appInfos))
   371  		for i, appInfo := range appInfos {
   372  			names[i] = appInfo.Name
   373  		}
   374  		c.Assert(names, check.DeepEquals, t.names, check.Commentf("%s", t.opts))
   375  
   376  		for i := range appInfos {
   377  			c.Check(appInfos[i].Snap, check.DeepEquals, t.snaps[i], check.Commentf("%s: %s", t.opts, t.names[i]))
   378  		}
   379  	}
   380  }
   381  
   382  func (s *appsSuite) TestAppInfosForOneSnap(c *check.C) {
   383  	st := s.d.Overlord().State()
   384  	appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a"}, daemon.AppInfoServiceTrue)
   385  	c.Assert(rsp, check.IsNil)
   386  	c.Assert(appInfos, check.HasLen, 2)
   387  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   388  
   389  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   390  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   391  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
   392  	c.Check(appInfos[1].Name, check.Equals, "svc2")
   393  }
   394  
   395  func (s *appsSuite) TestAppInfosForMixedArgs(c *check.C) {
   396  	st := s.d.Overlord().State()
   397  	appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-a", "snap-a.svc1"}, daemon.AppInfoServiceTrue)
   398  	c.Assert(rsp, check.IsNil)
   399  	c.Assert(appInfos, check.HasLen, 2)
   400  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   401  
   402  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   403  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   404  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
   405  	c.Check(appInfos[1].Name, check.Equals, "svc2")
   406  }
   407  
   408  func (s *appsSuite) TestAppInfosCleanupAndSorted(c *check.C) {
   409  	st := s.d.Overlord().State()
   410  	appInfos, rsp := daemon.AppInfosFor(st, []string{
   411  		"snap-b.svc3",
   412  		"snap-a.svc2",
   413  		"snap-a.svc1",
   414  		"snap-a.svc2",
   415  		"snap-b.svc3",
   416  		"snap-a.svc1",
   417  		"snap-b",
   418  		"snap-a",
   419  	}, daemon.AppInfoServiceTrue)
   420  	c.Assert(rsp, check.IsNil)
   421  	c.Assert(appInfos, check.HasLen, 3)
   422  	sort.Sort(snap.AppInfoBySnapApp(appInfos))
   423  
   424  	c.Check(appInfos[0].Snap, check.DeepEquals, s.infoA)
   425  	c.Check(appInfos[0].Name, check.Equals, "svc1")
   426  	c.Check(appInfos[1].Snap, check.DeepEquals, s.infoA)
   427  	c.Check(appInfos[1].Name, check.Equals, "svc2")
   428  	c.Check(appInfos[2].Snap, check.DeepEquals, s.infoB)
   429  	c.Check(appInfos[2].Name, check.Equals, "svc3")
   430  }
   431  
   432  func (s *appsSuite) TestAppInfosForAppless(c *check.C) {
   433  	st := s.d.Overlord().State()
   434  	appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-c"}, daemon.AppInfoServiceTrue)
   435  	c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{})
   436  	c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404)
   437  	c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindAppNotFound)
   438  	c.Assert(appInfos, check.IsNil)
   439  }
   440  
   441  func (s *appsSuite) TestAppInfosForMissingApp(c *check.C) {
   442  	st := s.d.Overlord().State()
   443  	appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-c.whatever"}, daemon.AppInfoServiceTrue)
   444  	c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{})
   445  	c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404)
   446  	c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindAppNotFound)
   447  	c.Assert(appInfos, check.IsNil)
   448  }
   449  
   450  func (s *appsSuite) TestAppInfosForMissingSnap(c *check.C) {
   451  	st := s.d.Overlord().State()
   452  	appInfos, rsp := daemon.AppInfosFor(st, []string{"snap-x"}, daemon.AppInfoServiceTrue)
   453  	c.Assert(rsp, check.FitsTypeOf, &daemon.Resp{})
   454  	c.Check(rsp.(*daemon.Resp).Status, check.Equals, 404)
   455  	c.Check(rsp.(*daemon.Resp).Result.(*daemon.ErrorResult).Kind, check.Equals, client.ErrorKindSnapNotFound)
   456  	c.Assert(appInfos, check.IsNil)
   457  }
   458  
   459  func (s *appsSuite) testPostApps(c *check.C, inst servicestate.Instruction, servicecmds []serviceControlArgs) *state.Change {
   460  	postBody, err := json.Marshal(inst)
   461  	c.Assert(err, check.IsNil)
   462  
   463  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBuffer(postBody))
   464  	c.Assert(err, check.IsNil)
   465  
   466  	rsp := s.req(c, req, nil).(*daemon.Resp)
   467  	c.Assert(rsp.Status, check.Equals, 202)
   468  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeAsync)
   469  	c.Check(rsp.Change, check.Matches, `[0-9]+`)
   470  
   471  	st := s.d.Overlord().State()
   472  	st.Lock()
   473  	defer st.Unlock()
   474  	chg := st.Change(rsp.Change)
   475  	c.Assert(chg, check.NotNil)
   476  	c.Check(chg.Tasks(), check.HasLen, len(servicecmds))
   477  
   478  	st.Unlock()
   479  	<-chg.Ready()
   480  	st.Lock()
   481  
   482  	c.Check(s.serviceControlCalls, check.DeepEquals, servicecmds)
   483  	return chg
   484  }
   485  
   486  func (s *appsSuite) TestPostAppsStartOne(c *check.C) {
   487  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}}
   488  	expected := []serviceControlArgs{
   489  		{action: "start", names: []string{"snap-a.svc2"}},
   490  	}
   491  	chg := s.testPostApps(c, inst, expected)
   492  	chg.State().Lock()
   493  	defer chg.State().Unlock()
   494  
   495  	var names []string
   496  	err := chg.Get("snap-names", &names)
   497  	c.Assert(err, check.IsNil)
   498  	c.Assert(names, check.DeepEquals, []string{"snap-a"})
   499  }
   500  
   501  func (s *appsSuite) TestPostAppsStartTwo(c *check.C) {
   502  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a"}}
   503  	expected := []serviceControlArgs{
   504  		{action: "start", names: []string{"snap-a.svc1", "snap-a.svc2"}},
   505  	}
   506  	chg := s.testPostApps(c, inst, expected)
   507  
   508  	chg.State().Lock()
   509  	defer chg.State().Unlock()
   510  
   511  	var names []string
   512  	err := chg.Get("snap-names", &names)
   513  	c.Assert(err, check.IsNil)
   514  	c.Assert(names, check.DeepEquals, []string{"snap-a"})
   515  }
   516  
   517  func (s *appsSuite) TestPostAppsStartThree(c *check.C) {
   518  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a", "snap-b"}}
   519  	expected := []serviceControlArgs{
   520  		{action: "start", names: []string{"snap-a.svc1", "snap-a.svc2", "snap-b.svc3"}},
   521  	}
   522  	chg := s.testPostApps(c, inst, expected)
   523  	// check the summary expands the snap into actual apps
   524  	c.Check(chg.Summary(), check.Equals, "Running service command")
   525  	chg.State().Lock()
   526  	defer chg.State().Unlock()
   527  
   528  	var names []string
   529  	err := chg.Get("snap-names", &names)
   530  	c.Assert(err, check.IsNil)
   531  	c.Assert(names, check.DeepEquals, []string{"snap-a", "snap-b"})
   532  }
   533  
   534  func (s *appsSuite) TestPostAppsStop(c *check.C) {
   535  	inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}}
   536  	expected := []serviceControlArgs{
   537  		{action: "stop", names: []string{"snap-a.svc2"}},
   538  	}
   539  	s.testPostApps(c, inst, expected)
   540  }
   541  
   542  func (s *appsSuite) TestPostAppsRestart(c *check.C) {
   543  	inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}}
   544  	expected := []serviceControlArgs{
   545  		{action: "restart", names: []string{"snap-a.svc2"}},
   546  	}
   547  	s.testPostApps(c, inst, expected)
   548  }
   549  
   550  func (s *appsSuite) TestPostAppsReload(c *check.C) {
   551  	inst := servicestate.Instruction{Action: "restart", Names: []string{"snap-a.svc2"}}
   552  	inst.Reload = true
   553  	expected := []serviceControlArgs{
   554  		{action: "restart", options: "reload", names: []string{"snap-a.svc2"}},
   555  	}
   556  	s.testPostApps(c, inst, expected)
   557  }
   558  
   559  func (s *appsSuite) TestPostAppsEnableNow(c *check.C) {
   560  	inst := servicestate.Instruction{Action: "start", Names: []string{"snap-a.svc2"}}
   561  	inst.Enable = true
   562  	expected := []serviceControlArgs{
   563  		{action: "start", options: "enable", names: []string{"snap-a.svc2"}},
   564  	}
   565  	s.testPostApps(c, inst, expected)
   566  }
   567  
   568  func (s *appsSuite) TestPostAppsDisableNow(c *check.C) {
   569  	inst := servicestate.Instruction{Action: "stop", Names: []string{"snap-a.svc2"}}
   570  	inst.Disable = true
   571  	expected := []serviceControlArgs{
   572  		{action: "stop", options: "disable", names: []string{"snap-a.svc2"}},
   573  	}
   574  	s.testPostApps(c, inst, expected)
   575  }
   576  
   577  func (s *appsSuite) TestPostAppsBadJSON(c *check.C) {
   578  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`'junk`))
   579  	c.Assert(err, check.IsNil)
   580  	rsp := s.req(c, req, nil).(*daemon.Resp)
   581  	c.Check(rsp.Status, check.Equals, 400)
   582  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   583  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, ".*cannot decode request body.*")
   584  }
   585  
   586  func (s *appsSuite) TestPostAppsBadOp(c *check.C) {
   587  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"random": "json"}`))
   588  	c.Assert(err, check.IsNil)
   589  	rsp := s.req(c, req, nil).(*daemon.Resp)
   590  	c.Check(rsp.Status, check.Equals, 400)
   591  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   592  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Matches, ".*cannot perform operation on services without a list of services.*")
   593  }
   594  
   595  func (s *appsSuite) TestPostAppsBadSnap(c *check.C) {
   596  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-c"]}`))
   597  	c.Assert(err, check.IsNil)
   598  	rsp := s.req(c, req, nil).(*daemon.Resp)
   599  	c.Check(rsp.Status, check.Equals, 404)
   600  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   601  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-c" has no services`)
   602  }
   603  
   604  func (s *appsSuite) TestPostAppsBadApp(c *check.C) {
   605  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "stop", "names": ["snap-a.what"]}`))
   606  	c.Assert(err, check.IsNil)
   607  	rsp := s.req(c, req, nil).(*daemon.Resp)
   608  	c.Check(rsp.Status, check.Equals, 404)
   609  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   610  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-a" has no service "what"`)
   611  }
   612  
   613  func (s *appsSuite) TestPostAppsServiceControlError(c *check.C) {
   614  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`))
   615  	c.Assert(err, check.IsNil)
   616  	s.serviceControlError = fmt.Errorf("total failure")
   617  	rsp := s.req(c, req, nil).(*daemon.Resp)
   618  	c.Check(rsp.Status, check.Equals, 400)
   619  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   620  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `total failure`)
   621  }
   622  
   623  func (s *appsSuite) TestPostAppsConflict(c *check.C) {
   624  	req, err := http.NewRequest("POST", "/v2/apps", bytes.NewBufferString(`{"action": "start", "names": ["snap-a.svc1"]}`))
   625  	c.Assert(err, check.IsNil)
   626  	s.serviceControlError = &snapstate.ChangeConflictError{Snap: "snap-a", ChangeKind: "enable"}
   627  	rsp := s.req(c, req, nil).(*daemon.Resp)
   628  	c.Check(rsp.Status, check.Equals, 400)
   629  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   630  	c.Check(rsp.Result.(*daemon.ErrorResult).Message, check.Equals, `snap "snap-a" has "enable" change in progress`)
   631  }
   632  
   633  func (s *appsSuite) TestLogs(c *check.C) {
   634  	s.jctlRCs = []io.ReadCloser{ioutil.NopCloser(strings.NewReader(`
   635  {"MESSAGE": "hello1", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "42"}
   636  {"MESSAGE": "hello2", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "44"}
   637  {"MESSAGE": "hello3", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "46"}
   638  {"MESSAGE": "hello4", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "48"}
   639  {"MESSAGE": "hello5", "SYSLOG_IDENTIFIER": "xyzzy", "_PID": "42", "__REALTIME_TIMESTAMP": "50"}
   640  	`))}
   641  
   642  	req, err := http.NewRequest("GET", "/v2/logs?names=snap-a.svc2&n=42&follow=false", nil)
   643  	c.Assert(err, check.IsNil)
   644  
   645  	rec := httptest.NewRecorder()
   646  	s.req(c, req, nil).ServeHTTP(rec, req)
   647  
   648  	c.Check(s.jctlSvcses, check.DeepEquals, [][]string{{"snap.snap-a.svc2.service"}})
   649  	c.Check(s.jctlNs, check.DeepEquals, []int{42})
   650  	c.Check(s.jctlFollows, check.DeepEquals, []bool{false})
   651  
   652  	c.Check(rec.Code, check.Equals, 200)
   653  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json-seq")
   654  	c.Check(rec.Body.String(), check.Equals, `
   655  {"timestamp":"1970-01-01T00:00:00.000042Z","message":"hello1","sid":"xyzzy","pid":"42"}
   656  {"timestamp":"1970-01-01T00:00:00.000044Z","message":"hello2","sid":"xyzzy","pid":"42"}
   657  {"timestamp":"1970-01-01T00:00:00.000046Z","message":"hello3","sid":"xyzzy","pid":"42"}
   658  {"timestamp":"1970-01-01T00:00:00.000048Z","message":"hello4","sid":"xyzzy","pid":"42"}
   659  {"timestamp":"1970-01-01T00:00:00.00005Z","message":"hello5","sid":"xyzzy","pid":"42"}
   660  `[1:])
   661  }
   662  
   663  func (s *appsSuite) TestLogsN(c *check.C) {
   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  	req, err := http.NewRequest("GET", "/v2/logs?n=hello", nil)
   692  	c.Assert(err, check.IsNil)
   693  
   694  	rsp := s.req(c, req, nil).(*daemon.Resp)
   695  	c.Assert(rsp.Status, check.Equals, 400)
   696  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   697  }
   698  
   699  func (s *appsSuite) TestLogsFollow(c *check.C) {
   700  	s.jctlRCs = []io.ReadCloser{
   701  		ioutil.NopCloser(strings.NewReader("")),
   702  		ioutil.NopCloser(strings.NewReader("")),
   703  		ioutil.NopCloser(strings.NewReader("")),
   704  	}
   705  
   706  	reqT, err := http.NewRequest("GET", "/v2/logs?follow=true", nil)
   707  	c.Assert(err, check.IsNil)
   708  	reqF, err := http.NewRequest("GET", "/v2/logs?follow=false", nil)
   709  	c.Assert(err, check.IsNil)
   710  	reqN, err := http.NewRequest("GET", "/v2/logs", nil)
   711  	c.Assert(err, check.IsNil)
   712  
   713  	rec := httptest.NewRecorder()
   714  	s.req(c, reqT, nil).ServeHTTP(rec, reqT)
   715  	s.req(c, reqF, nil).ServeHTTP(rec, reqF)
   716  	s.req(c, reqN, nil).ServeHTTP(rec, reqN)
   717  
   718  	c.Check(s.jctlFollows, check.DeepEquals, []bool{true, false, false})
   719  }
   720  
   721  func (s *appsSuite) TestLogsBadFollow(c *check.C) {
   722  	req, err := http.NewRequest("GET", "/v2/logs?follow=hello", nil)
   723  	c.Assert(err, check.IsNil)
   724  
   725  	rsp := s.req(c, req, nil).(*daemon.Resp)
   726  	c.Assert(rsp.Status, check.Equals, 400)
   727  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   728  }
   729  
   730  func (s *appsSuite) TestLogsBadName(c *check.C) {
   731  	req, err := http.NewRequest("GET", "/v2/logs?names=hello", nil)
   732  	c.Assert(err, check.IsNil)
   733  
   734  	rsp := s.req(c, req, nil).(*daemon.Resp)
   735  	c.Assert(rsp.Status, check.Equals, 404)
   736  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   737  }
   738  
   739  func (s *appsSuite) TestLogsSad(c *check.C) {
   740  	s.jctlErrs = []error{errors.New("potato")}
   741  	req, err := http.NewRequest("GET", "/v2/logs", nil)
   742  	c.Assert(err, check.IsNil)
   743  
   744  	rsp := s.req(c, req, nil).(*daemon.Resp)
   745  	c.Assert(rsp.Status, check.Equals, 500)
   746  	c.Assert(rsp.Type, check.Equals, daemon.ResponseTypeError)
   747  }