github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_general_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  	"fmt"
    26  	"io"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"net/url"
    30  	"time"
    31  
    32  	"gopkg.in/check.v1"
    33  
    34  	"github.com/snapcore/snapd/arch"
    35  	"github.com/snapcore/snapd/boot"
    36  	"github.com/snapcore/snapd/daemon"
    37  	"github.com/snapcore/snapd/dirs"
    38  	"github.com/snapcore/snapd/interfaces/ifacetest"
    39  	"github.com/snapcore/snapd/overlord/auth"
    40  	"github.com/snapcore/snapd/overlord/configstate/config"
    41  	"github.com/snapcore/snapd/overlord/state"
    42  	"github.com/snapcore/snapd/release"
    43  	"github.com/snapcore/snapd/sandbox"
    44  )
    45  
    46  var _ = check.Suite(&generalSuite{})
    47  
    48  type generalSuite struct {
    49  	apiBaseSuite
    50  }
    51  
    52  func (s *generalSuite) TestRoot(c *check.C) {
    53  	s.daemon(c)
    54  
    55  	req, err := http.NewRequest("GET", "/", nil)
    56  	c.Assert(err, check.IsNil)
    57  
    58  	// check it only does GET
    59  	s.checkGetOnly(c, req)
    60  
    61  	rec := httptest.NewRecorder()
    62  	s.req(c, req, nil).ServeHTTP(rec, nil)
    63  	c.Check(rec.Code, check.Equals, 200)
    64  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
    65  
    66  	expected := []interface{}{"TBD"}
    67  	var rsp daemon.Resp
    68  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
    69  	c.Check(rsp.Status, check.Equals, 200)
    70  	c.Check(rsp.Result, check.DeepEquals, expected)
    71  }
    72  
    73  func (s *generalSuite) TestSysInfo(c *check.C) {
    74  	req, err := http.NewRequest("GET", "/v2/system-info", nil)
    75  	c.Assert(err, check.IsNil)
    76  
    77  	d := s.daemon(c)
    78  	d.Version = "42b1"
    79  
    80  	// check it only does GET
    81  	s.checkGetOnly(c, req)
    82  
    83  	// set both legacy and new refresh schedules. new one takes priority
    84  	st := d.Overlord().State()
    85  	st.Lock()
    86  	tr := config.NewTransaction(st)
    87  	tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00")
    88  	tr.Set("core", "refresh.timer", "8:00~9:00/2")
    89  	tr.Commit()
    90  	st.Unlock()
    91  
    92  	restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"})
    93  	defer restore()
    94  	restore = release.MockOnClassic(true)
    95  	defer restore()
    96  	restore = sandbox.MockForceDevMode(true)
    97  	defer restore()
    98  	// reload dirs for release info to have effect
    99  	dirs.SetRootDir(dirs.GlobalRootDir)
   100  	restore = daemon.MockSystemdVirt("magic")
   101  	defer restore()
   102  
   103  	buildID := "this-is-my-build-id"
   104  	restore = daemon.MockBuildID(buildID)
   105  	defer restore()
   106  
   107  	rec := httptest.NewRecorder()
   108  	s.req(c, req, nil).ServeHTTP(rec, nil)
   109  	c.Check(rec.Code, check.Equals, 200)
   110  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
   111  
   112  	expected := map[string]interface{}{
   113  		"series":  "16",
   114  		"version": "42b1",
   115  		"os-release": map[string]interface{}{
   116  			"id":         "distro-id",
   117  			"version-id": "1.2",
   118  		},
   119  		"build-id":   buildID,
   120  		"on-classic": true,
   121  		"managed":    false,
   122  		"locations": map[string]interface{}{
   123  			"snap-mount-dir": dirs.SnapMountDir,
   124  			"snap-bin-dir":   dirs.SnapBinariesDir,
   125  		},
   126  		"refresh": map[string]interface{}{
   127  			// only the "timer" field
   128  			"timer": "8:00~9:00/2",
   129  		},
   130  		"confinement":      "partial",
   131  		"sandbox-features": map[string]interface{}{"confinement-options": []interface{}{"classic", "devmode"}},
   132  		"architecture":     arch.DpkgArchitecture(),
   133  		"virtualization":   "magic",
   134  		"system-mode":      "run",
   135  	}
   136  	var rsp daemon.Resp
   137  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
   138  	c.Check(rsp.Status, check.Equals, 200)
   139  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   140  	// Ensure that we had a kernel-verrsion but don't check the actual value.
   141  	const kernelVersionKey = "kernel-version"
   142  	c.Check(rsp.Result.(map[string]interface{})[kernelVersionKey], check.Not(check.Equals), "")
   143  	delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
   144  	c.Check(rsp.Result, check.DeepEquals, expected)
   145  }
   146  
   147  func (s *generalSuite) TestSysInfoLegacyRefresh(c *check.C) {
   148  	req, err := http.NewRequest("GET", "/v2/system-info", nil)
   149  	c.Assert(err, check.IsNil)
   150  
   151  	d := s.daemon(c)
   152  	d.Version = "42b1"
   153  
   154  	restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"})
   155  	defer restore()
   156  	restore = release.MockOnClassic(true)
   157  	defer restore()
   158  	restore = sandbox.MockForceDevMode(true)
   159  	defer restore()
   160  	restore = daemon.MockSystemdVirt("kvm")
   161  	defer restore()
   162  	// reload dirs for release info to have effect
   163  	dirs.SetRootDir(dirs.GlobalRootDir)
   164  
   165  	// set the legacy refresh schedule
   166  	st := d.Overlord().State()
   167  	st.Lock()
   168  	tr := config.NewTransaction(st)
   169  	tr.Set("core", "refresh.schedule", "00:00-9:00/12:00-13:00")
   170  	tr.Set("core", "refresh.timer", "")
   171  	tr.Commit()
   172  	st.Unlock()
   173  
   174  	// add a test security backend
   175  	err = d.Overlord().InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{
   176  		BackendName:             "apparmor",
   177  		SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} },
   178  	})
   179  	c.Assert(err, check.IsNil)
   180  
   181  	buildID := "this-is-my-build-id"
   182  	restore = daemon.MockBuildID(buildID)
   183  	defer restore()
   184  
   185  	rec := httptest.NewRecorder()
   186  	s.req(c, req, nil).ServeHTTP(rec, nil)
   187  	c.Check(rec.Code, check.Equals, 200)
   188  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
   189  
   190  	expected := map[string]interface{}{
   191  		"series":  "16",
   192  		"version": "42b1",
   193  		"os-release": map[string]interface{}{
   194  			"id":         "distro-id",
   195  			"version-id": "1.2",
   196  		},
   197  		"build-id":   buildID,
   198  		"on-classic": true,
   199  		"managed":    false,
   200  		"locations": map[string]interface{}{
   201  			"snap-mount-dir": dirs.SnapMountDir,
   202  			"snap-bin-dir":   dirs.SnapBinariesDir,
   203  		},
   204  		"refresh": map[string]interface{}{
   205  			// only the "schedule" field
   206  			"schedule": "00:00-9:00/12:00-13:00",
   207  		},
   208  		"confinement": "partial",
   209  		"sandbox-features": map[string]interface{}{
   210  			"apparmor":            []interface{}{"feature-1", "feature-2"},
   211  			"confinement-options": []interface{}{"classic", "devmode"}, // we know it's this because of the release.Mock... calls above
   212  		},
   213  		"architecture":   arch.DpkgArchitecture(),
   214  		"virtualization": "kvm",
   215  		"system-mode":    "run",
   216  	}
   217  	var rsp daemon.Resp
   218  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
   219  	c.Check(rsp.Status, check.Equals, 200)
   220  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   221  	const kernelVersionKey = "kernel-version"
   222  	delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
   223  	c.Check(rsp.Result, check.DeepEquals, expected)
   224  }
   225  
   226  func (s *generalSuite) testSysInfoSystemMode(c *check.C, mode string) {
   227  	req, err := http.NewRequest("GET", "/v2/system-info", nil)
   228  	c.Assert(err, check.IsNil)
   229  
   230  	c.Assert(mode != "", check.Equals, true, check.Commentf("mode is unset for the test"))
   231  
   232  	restore := release.MockReleaseInfo(&release.OS{ID: "distro-id", VersionID: "1.2"})
   233  	defer restore()
   234  	restore = release.MockOnClassic(false)
   235  	defer restore()
   236  	restore = sandbox.MockForceDevMode(false)
   237  	defer restore()
   238  	restore = daemon.MockSystemdVirt("")
   239  	defer restore()
   240  
   241  	// reload dirs for release info to have effect on paths
   242  	dirs.SetRootDir(dirs.GlobalRootDir)
   243  
   244  	// mock the modeenv file
   245  	m := boot.Modeenv{
   246  		Mode:           mode,
   247  		RecoverySystem: "20191127",
   248  	}
   249  	c.Assert(m.WriteTo(""), check.IsNil)
   250  
   251  	d := s.daemon(c)
   252  	d.Version = "42b1"
   253  
   254  	// add a test security backend
   255  	err = d.Overlord().InterfaceManager().Repository().AddBackend(&ifacetest.TestSecurityBackend{
   256  		BackendName:             "apparmor",
   257  		SandboxFeaturesCallback: func() []string { return []string{"feature-1", "feature-2"} },
   258  	})
   259  	c.Assert(err, check.IsNil)
   260  
   261  	buildID := "this-is-my-build-id"
   262  	restore = daemon.MockBuildID(buildID)
   263  	defer restore()
   264  
   265  	rec := httptest.NewRecorder()
   266  	s.req(c, req, nil).ServeHTTP(rec, nil)
   267  	c.Check(rec.Code, check.Equals, 200)
   268  	c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json")
   269  
   270  	expected := map[string]interface{}{
   271  		"series":  "16",
   272  		"version": "42b1",
   273  		"os-release": map[string]interface{}{
   274  			"id":         "distro-id",
   275  			"version-id": "1.2",
   276  		},
   277  		"build-id":   buildID,
   278  		"on-classic": false,
   279  		"managed":    false,
   280  		"locations": map[string]interface{}{
   281  			"snap-mount-dir": dirs.SnapMountDir,
   282  			"snap-bin-dir":   dirs.SnapBinariesDir,
   283  		},
   284  		"refresh": map[string]interface{}{
   285  			"timer": "00:00~24:00/4",
   286  		},
   287  		"confinement": "strict",
   288  		"sandbox-features": map[string]interface{}{
   289  			"apparmor":            []interface{}{"feature-1", "feature-2"},
   290  			"confinement-options": []interface{}{"devmode", "strict"}, // we know it's this because of the release.Mock... calls above
   291  		},
   292  		"architecture": arch.DpkgArchitecture(),
   293  		"system-mode":  mode,
   294  	}
   295  	var rsp daemon.Resp
   296  	c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil)
   297  	c.Check(rsp.Status, check.Equals, 200)
   298  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   299  	const kernelVersionKey = "kernel-version"
   300  	delete(rsp.Result.(map[string]interface{}), kernelVersionKey)
   301  	c.Check(rsp.Result, check.DeepEquals, expected)
   302  }
   303  
   304  func (s *generalSuite) TestSysInfoSystemModeRun(c *check.C) {
   305  	s.testSysInfoSystemMode(c, "run")
   306  }
   307  
   308  func (s *generalSuite) TestSysInfoSystemModeRecover(c *check.C) {
   309  	s.testSysInfoSystemMode(c, "recover")
   310  }
   311  
   312  func (s *generalSuite) TestSysInfoSystemModeInstall(c *check.C) {
   313  	s.testSysInfoSystemMode(c, "install")
   314  }
   315  func (s *generalSuite) TestSysInfoIsManaged(c *check.C) {
   316  	d := s.daemon(c)
   317  
   318  	st := d.Overlord().State()
   319  	st.Lock()
   320  	_, err := auth.NewUser(st, "someuser", "mymail@test.com", "macaroon", []string{"discharge"})
   321  	st.Unlock()
   322  	c.Assert(err, check.IsNil)
   323  
   324  	req, err := http.NewRequest("GET", "/v2/system-info", nil)
   325  	c.Assert(err, check.IsNil)
   326  
   327  	rsp := s.req(c, req, nil).(*daemon.Resp)
   328  
   329  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   330  	c.Check(rsp.Result.(map[string]interface{})["managed"], check.Equals, true)
   331  }
   332  
   333  func (s *generalSuite) TestSysInfoWorksDegraded(c *check.C) {
   334  	d := s.daemon(c)
   335  
   336  	d.SetDegradedMode(fmt.Errorf("some error"))
   337  
   338  	req, err := http.NewRequest("GET", "/v2/system-info", nil)
   339  	c.Assert(err, check.IsNil)
   340  
   341  	rsp := s.req(c, req, nil).(*daemon.Resp)
   342  	c.Check(rsp.Status, check.Equals, 200)
   343  }
   344  
   345  func setupChanges(st *state.State) []string {
   346  	chg1 := st.NewChange("install", "install...")
   347  	chg1.Set("snap-names", []string{"funky-snap-name"})
   348  	t1 := st.NewTask("download", "1...")
   349  	t2 := st.NewTask("activate", "2...")
   350  	chg1.AddAll(state.NewTaskSet(t1, t2))
   351  	t1.Logf("l11")
   352  	t1.Logf("l12")
   353  	chg2 := st.NewChange("remove", "remove..")
   354  	t3 := st.NewTask("unlink", "1...")
   355  	chg2.AddTask(t3)
   356  	t3.SetStatus(state.ErrorStatus)
   357  	t3.Errorf("rm failed")
   358  
   359  	return []string{chg1.ID(), chg2.ID(), t1.ID(), t2.ID(), t3.ID()}
   360  }
   361  
   362  func (s *generalSuite) TestStateChangesDefaultToInProgress(c *check.C) {
   363  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   364  	defer restore()
   365  
   366  	// Setup
   367  	d := s.daemon(c)
   368  	st := d.Overlord().State()
   369  	st.Lock()
   370  	setupChanges(st)
   371  	st.Unlock()
   372  
   373  	// Execute
   374  	req, err := http.NewRequest("GET", "/v2/changes", nil)
   375  	c.Assert(err, check.IsNil)
   376  	rsp := s.req(c, req, nil).(*daemon.Resp)
   377  
   378  	// Verify
   379  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   380  	c.Check(rsp.Status, check.Equals, 200)
   381  	c.Assert(rsp.Result, check.HasLen, 1)
   382  
   383  	res, err := rsp.MarshalJSON()
   384  	c.Assert(err, check.IsNil)
   385  
   386  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*`)
   387  }
   388  
   389  func (s *generalSuite) TestStateChangesInProgress(c *check.C) {
   390  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   391  	defer restore()
   392  
   393  	// Setup
   394  	d := s.daemon(c)
   395  	st := d.Overlord().State()
   396  	st.Lock()
   397  	setupChanges(st)
   398  	st.Unlock()
   399  
   400  	// Execute
   401  	req, err := http.NewRequest("GET", "/v2/changes?select=in-progress", nil)
   402  	c.Assert(err, check.IsNil)
   403  	rsp := s.req(c, req, nil).(*daemon.Resp)
   404  
   405  	// Verify
   406  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   407  	c.Check(rsp.Status, check.Equals, 200)
   408  	c.Assert(rsp.Result, check.HasLen, 1)
   409  
   410  	res, err := rsp.MarshalJSON()
   411  	c.Assert(err, check.IsNil)
   412  
   413  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*],"ready":false,"spawn-time":"2016-04-21T01:02:03Z"}.*`)
   414  }
   415  
   416  func (s *generalSuite) TestStateChangesAll(c *check.C) {
   417  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   418  	defer restore()
   419  
   420  	// Setup
   421  	d := s.daemon(c)
   422  	st := d.Overlord().State()
   423  	st.Lock()
   424  	setupChanges(st)
   425  	st.Unlock()
   426  
   427  	// Execute
   428  	req, err := http.NewRequest("GET", "/v2/changes?select=all", nil)
   429  	c.Assert(err, check.IsNil)
   430  	rsp := s.req(c, req, nil).(*daemon.Resp)
   431  
   432  	// Verify
   433  	c.Check(rsp.Status, check.Equals, 200)
   434  	c.Assert(rsp.Result, check.HasLen, 2)
   435  
   436  	res, err := rsp.MarshalJSON()
   437  	c.Assert(err, check.IsNil)
   438  
   439  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"install","summary":"install...","status":"Do","tasks":\[{"id":"\w+","kind":"download","summary":"1...","status":"Do","log":\["2016-04-21T01:02:03Z INFO l11","2016-04-21T01:02:03Z INFO l12"],"progress":{"label":"","done":0,"total":1},"spawn-time":"2016-04-21T01:02:03Z"}.*],"ready":false,"spawn-time":"2016-04-21T01:02:03Z"}.*`)
   440  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"label":"","done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`)
   441  }
   442  
   443  func (s *generalSuite) TestStateChangesReady(c *check.C) {
   444  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   445  	defer restore()
   446  
   447  	// Setup
   448  	d := s.daemon(c)
   449  	st := d.Overlord().State()
   450  	st.Lock()
   451  	setupChanges(st)
   452  	st.Unlock()
   453  
   454  	// Execute
   455  	req, err := http.NewRequest("GET", "/v2/changes?select=ready", nil)
   456  	c.Assert(err, check.IsNil)
   457  	rsp := s.req(c, req, nil).(*daemon.Resp)
   458  
   459  	// Verify
   460  	c.Check(rsp.Status, check.Equals, 200)
   461  	c.Assert(rsp.Result, check.HasLen, 1)
   462  
   463  	res, err := rsp.MarshalJSON()
   464  	c.Assert(err, check.IsNil)
   465  
   466  	c.Check(string(res), check.Matches, `.*{"id":"\w+","kind":"remove","summary":"remove..","status":"Error","tasks":\[{"id":"\w+","kind":"unlink","summary":"1...","status":"Error","log":\["2016-04-21T01:02:03Z ERROR rm failed"],"progress":{"label":"","done":1,"total":1},"spawn-time":"2016-04-21T01:02:03Z","ready-time":"2016-04-21T01:02:03Z"}.*],"ready":true,"err":"[^"]+".*`)
   467  }
   468  
   469  func (s *generalSuite) TestStateChangesForSnapName(c *check.C) {
   470  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   471  	defer restore()
   472  
   473  	// Setup
   474  	d := s.daemon(c)
   475  	st := d.Overlord().State()
   476  	st.Lock()
   477  	setupChanges(st)
   478  	st.Unlock()
   479  
   480  	// Execute
   481  	req, err := http.NewRequest("GET", "/v2/changes?for=funky-snap-name&select=all", nil)
   482  	c.Assert(err, check.IsNil)
   483  	rsp := s.req(c, req, nil).(*daemon.Resp)
   484  
   485  	// Verify
   486  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   487  	c.Check(rsp.Status, check.Equals, 200)
   488  	c.Assert(rsp.Result, check.FitsTypeOf, []*daemon.ChangeInfo(nil))
   489  
   490  	res := rsp.Result.([]*daemon.ChangeInfo)
   491  	c.Assert(res, check.HasLen, 1)
   492  	c.Check(res[0].Kind, check.Equals, `install`)
   493  
   494  	_, err = rsp.MarshalJSON()
   495  	c.Assert(err, check.IsNil)
   496  }
   497  
   498  func (s *generalSuite) TestStateChangesForSnapNameWithApp(c *check.C) {
   499  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   500  	defer restore()
   501  
   502  	// Setup
   503  	d := s.daemon(c)
   504  	st := d.Overlord().State()
   505  	st.Lock()
   506  	chg1 := st.NewChange("service-control", "install...")
   507  	// as triggered by snap restart lxd.daemon
   508  	chg1.Set("snap-names", []string{"lxd.daemon"})
   509  	t1 := st.NewTask("exec-command", "1...")
   510  	chg1.AddAll(state.NewTaskSet(t1))
   511  	t1.Logf("foobar")
   512  
   513  	st.Unlock()
   514  
   515  	// Execute
   516  	req, err := http.NewRequest("GET", "/v2/changes?for=lxd&select=all", nil)
   517  	c.Assert(err, check.IsNil)
   518  	rsp := s.req(c, req, nil).(*daemon.Resp)
   519  
   520  	// Verify
   521  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   522  	c.Check(rsp.Status, check.Equals, 200)
   523  	c.Assert(rsp.Result, check.FitsTypeOf, []*daemon.ChangeInfo(nil))
   524  
   525  	res := rsp.Result.([]*daemon.ChangeInfo)
   526  	c.Assert(res, check.HasLen, 1)
   527  	c.Check(res[0].Kind, check.Equals, `service-control`)
   528  
   529  	_, err = rsp.MarshalJSON()
   530  	c.Assert(err, check.IsNil)
   531  }
   532  
   533  func (s *generalSuite) TestStateChange(c *check.C) {
   534  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   535  	defer restore()
   536  
   537  	// Setup
   538  	d := s.daemon(c)
   539  	st := d.Overlord().State()
   540  	st.Lock()
   541  	ids := setupChanges(st)
   542  	chg := st.Change(ids[0])
   543  	chg.Set("api-data", map[string]int{"n": 42})
   544  	st.Unlock()
   545  
   546  	// Execute
   547  	req, err := http.NewRequest("GET", "/v2/changes/"+ids[0], nil)
   548  	c.Assert(err, check.IsNil)
   549  	rsp := s.req(c, req, nil).(*daemon.Resp)
   550  	rec := httptest.NewRecorder()
   551  	rsp.ServeHTTP(rec, req)
   552  
   553  	// Verify
   554  	c.Check(rec.Code, check.Equals, 200)
   555  	c.Check(rsp.Status, check.Equals, 200)
   556  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   557  	c.Check(rsp.Result, check.NotNil)
   558  
   559  	var body map[string]interface{}
   560  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   561  	c.Check(err, check.IsNil)
   562  	c.Check(body["result"], check.DeepEquals, map[string]interface{}{
   563  		"id":         ids[0],
   564  		"kind":       "install",
   565  		"summary":    "install...",
   566  		"status":     "Do",
   567  		"ready":      false,
   568  		"spawn-time": "2016-04-21T01:02:03Z",
   569  		"tasks": []interface{}{
   570  			map[string]interface{}{
   571  				"id":         ids[2],
   572  				"kind":       "download",
   573  				"summary":    "1...",
   574  				"status":     "Do",
   575  				"log":        []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"},
   576  				"progress":   map[string]interface{}{"label": "", "done": 0., "total": 1.},
   577  				"spawn-time": "2016-04-21T01:02:03Z",
   578  			},
   579  			map[string]interface{}{
   580  				"id":         ids[3],
   581  				"kind":       "activate",
   582  				"summary":    "2...",
   583  				"status":     "Do",
   584  				"progress":   map[string]interface{}{"label": "", "done": 0., "total": 1.},
   585  				"spawn-time": "2016-04-21T01:02:03Z",
   586  			},
   587  		},
   588  		"data": map[string]interface{}{
   589  			"n": float64(42),
   590  		},
   591  	})
   592  }
   593  
   594  func (s *generalSuite) TestStateChangeAbort(c *check.C) {
   595  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   596  	defer restore()
   597  
   598  	soon := 0
   599  	_, restore = daemon.MockEnsureStateSoon(func(st *state.State) {
   600  		soon++
   601  	})
   602  	defer restore()
   603  
   604  	// Setup
   605  	d := s.daemon(c)
   606  	st := d.Overlord().State()
   607  	st.Lock()
   608  	ids := setupChanges(st)
   609  	st.Unlock()
   610  
   611  	buf := bytes.NewBufferString(`{"action": "abort"}`)
   612  
   613  	// Execute
   614  	req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf)
   615  	c.Assert(err, check.IsNil)
   616  	rsp := s.req(c, req, nil).(*daemon.Resp)
   617  	rec := httptest.NewRecorder()
   618  	rsp.ServeHTTP(rec, req)
   619  
   620  	// Ensure scheduled
   621  	c.Check(soon, check.Equals, 1)
   622  
   623  	// Verify
   624  	c.Check(rec.Code, check.Equals, 200)
   625  	c.Check(rsp.Status, check.Equals, 200)
   626  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   627  	c.Check(rsp.Result, check.NotNil)
   628  
   629  	var body map[string]interface{}
   630  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   631  	c.Check(err, check.IsNil)
   632  	c.Check(body["result"], check.DeepEquals, map[string]interface{}{
   633  		"id":         ids[0],
   634  		"kind":       "install",
   635  		"summary":    "install...",
   636  		"status":     "Hold",
   637  		"ready":      true,
   638  		"spawn-time": "2016-04-21T01:02:03Z",
   639  		"ready-time": "2016-04-21T01:02:03Z",
   640  		"tasks": []interface{}{
   641  			map[string]interface{}{
   642  				"id":         ids[2],
   643  				"kind":       "download",
   644  				"summary":    "1...",
   645  				"status":     "Hold",
   646  				"log":        []interface{}{"2016-04-21T01:02:03Z INFO l11", "2016-04-21T01:02:03Z INFO l12"},
   647  				"progress":   map[string]interface{}{"label": "", "done": 1., "total": 1.},
   648  				"spawn-time": "2016-04-21T01:02:03Z",
   649  				"ready-time": "2016-04-21T01:02:03Z",
   650  			},
   651  			map[string]interface{}{
   652  				"id":         ids[3],
   653  				"kind":       "activate",
   654  				"summary":    "2...",
   655  				"status":     "Hold",
   656  				"progress":   map[string]interface{}{"label": "", "done": 1., "total": 1.},
   657  				"spawn-time": "2016-04-21T01:02:03Z",
   658  				"ready-time": "2016-04-21T01:02:03Z",
   659  			},
   660  		},
   661  	})
   662  }
   663  
   664  func (s *generalSuite) TestStateChangeAbortIsReady(c *check.C) {
   665  	restore := state.MockTime(time.Date(2016, 04, 21, 1, 2, 3, 0, time.UTC))
   666  	defer restore()
   667  
   668  	// Setup
   669  	d := s.daemon(c)
   670  	st := d.Overlord().State()
   671  	st.Lock()
   672  	ids := setupChanges(st)
   673  	st.Change(ids[0]).SetStatus(state.DoneStatus)
   674  	st.Unlock()
   675  
   676  	buf := bytes.NewBufferString(`{"action": "abort"}`)
   677  
   678  	// Execute
   679  	req, err := http.NewRequest("POST", "/v2/changes/"+ids[0], buf)
   680  	c.Assert(err, check.IsNil)
   681  	rsp := s.req(c, req, nil).(*daemon.Resp)
   682  	rec := httptest.NewRecorder()
   683  	rsp.ServeHTTP(rec, req)
   684  
   685  	// Verify
   686  	c.Check(rec.Code, check.Equals, 400)
   687  	c.Check(rsp.Status, check.Equals, 400)
   688  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeError)
   689  	c.Check(rsp.Result, check.NotNil)
   690  
   691  	var body map[string]interface{}
   692  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   693  	c.Check(err, check.IsNil)
   694  	c.Check(body["result"], check.DeepEquals, map[string]interface{}{
   695  		"message": fmt.Sprintf("cannot abort change %s with nothing pending", ids[0]),
   696  	})
   697  }
   698  
   699  func (s *generalSuite) testWarnings(c *check.C, all bool, body io.Reader) (calls string, result interface{}) {
   700  	s.daemon(c)
   701  
   702  	okayWarns := func(*state.State, time.Time) int { calls += "ok"; return 0 }
   703  	allWarns := func(*state.State) []*state.Warning { calls += "all"; return nil }
   704  	pendingWarns := func(*state.State) ([]*state.Warning, time.Time) { calls += "show"; return nil, time.Time{} }
   705  	restore := daemon.MockWarningsAccessors(okayWarns, allWarns, pendingWarns)
   706  	defer restore()
   707  
   708  	method := "GET"
   709  	if body != nil {
   710  		method = "POST"
   711  	}
   712  	q := url.Values{}
   713  	if all {
   714  		q.Set("select", "all")
   715  	}
   716  	req, err := http.NewRequest(method, "/v2/warnings?"+q.Encode(), body)
   717  	c.Assert(err, check.IsNil)
   718  
   719  	rsp, ok := s.req(c, req, nil).(*daemon.Resp)
   720  	c.Assert(ok, check.Equals, true)
   721  
   722  	c.Check(rsp.Type, check.Equals, daemon.ResponseTypeSync)
   723  	c.Check(rsp.Status, check.Equals, 200)
   724  	c.Assert(rsp.Result, check.NotNil)
   725  	return calls, rsp.Result
   726  }
   727  
   728  func (s *generalSuite) TestAllWarnings(c *check.C) {
   729  	calls, result := s.testWarnings(c, true, nil)
   730  	c.Check(calls, check.Equals, "all")
   731  	c.Check(result, check.DeepEquals, []state.Warning{})
   732  }
   733  
   734  func (s *generalSuite) TestSomeWarnings(c *check.C) {
   735  	calls, result := s.testWarnings(c, false, nil)
   736  	c.Check(calls, check.Equals, "show")
   737  	c.Check(result, check.DeepEquals, []state.Warning{})
   738  }
   739  
   740  func (s *generalSuite) TestAckWarnings(c *check.C) {
   741  	calls, result := s.testWarnings(c, false, bytes.NewReader([]byte(`{"action": "okay", "timestamp": "2006-01-02T15:04:05Z"}`)))
   742  	c.Check(calls, check.Equals, "ok")
   743  	c.Check(result, check.DeepEquals, 0)
   744  }