Skip to content

Commit

Permalink
daemon,tests: support forgetting device serial via API (#11135)
Browse files Browse the repository at this point in the history
this is done by posting {"action":"forget"} to /v2/model/serial

a flag no-registration-until-reboot is also supported
  • Loading branch information
pedronis authored and mvo5 committed Dec 2, 2021
1 parent ca2e0df commit eabc9c3
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 6 deletions.
52 changes: 48 additions & 4 deletions daemon/api_model.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2019 Canonical Ltd
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand Down Expand Up @@ -32,9 +32,11 @@ import (

var (
serialModelCmd = &Command{
Path: "/v2/model/serial",
GET: getSerial,
ReadAccess: openAccess{},
Path: "/v2/model/serial",
GET: getSerial,
POST: postSerial,
ReadAccess: openAccess{},
WriteAccess: rootAccess{},
}
modelCmd = &Command{
Path: "/v2/model",
Expand Down Expand Up @@ -165,3 +167,45 @@ func getSerial(c *Command, r *http.Request, _ *auth.UserState) Response {

return AssertResponse([]asserts.Assertion{serial}, false)
}

type postSerialData struct {
Action string `json:"action"`
NoRegistrationUntilReboot bool `json:"no-registration-until-reboot"`
}

var devicestateDeviceManagerUnregister = (*devicestate.DeviceManager).Unregister

func postSerial(c *Command, r *http.Request, _ *auth.UserState) Response {
var postData postSerialData

decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&postData); err != nil {
return BadRequest("cannot decode serial action data from request body: %v", err)
}
if decoder.More() {
return BadRequest("spurious content after serial action")
}
switch postData.Action {
case "forget":
case "":
return BadRequest("missing serial action")
default:
return BadRequest("unsupported serial action %q", postData.Action)
}

st := c.d.overlord.State()
st.Lock()
defer st.Unlock()

devmgr := c.d.overlord.DeviceManager()

unregOpts := &devicestate.UnregisterOptions{
NoRegistrationUntilReboot: postData.NoRegistrationUntilReboot,
}
err := devicestateDeviceManagerUnregister(devmgr, unregOpts)
if err != nil {
return InternalError("forgetting serial failed: %v", err)
}

return SyncResponse(nil)
}
63 changes: 62 additions & 1 deletion daemon/api_model_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2019-2020 Canonical Ltd
* Copyright (C) 2019-2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -22,6 +22,7 @@ package daemon_test
import (
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
Expand Down Expand Up @@ -376,3 +377,63 @@ func (s *modelSuite) TestGetModelJSONHasSerialAssertion(c *check.C) {
c.Assert(devKey, check.FitsTypeOf, "")
c.Assert(devKey.(string), check.Equals, string(encDevKey))
}

func (s *userSuite) TestPostSerialBadAction(c *check.C) {
buf := bytes.NewBufferString(`{"action":"what"}`)
req, err := http.NewRequest("POST", "/v2/model/serial", buf)
c.Assert(err, check.IsNil)

rspe := s.errorReq(c, req, nil)
c.Check(rspe, check.DeepEquals, daemon.BadRequest(`unsupported serial action "what"`))
}

func (s *userSuite) TestPostSerialForget(c *check.C) {
unregister := 0
defer daemon.MockDevicestateDeviceManagerUnregister(func(mgr *devicestate.DeviceManager, opts *devicestate.UnregisterOptions) error {
unregister++
c.Check(mgr, check.NotNil)
c.Check(opts.NoRegistrationUntilReboot, check.Equals, false)
return nil
})()

buf := bytes.NewBufferString(`{"action":"forget"}`)
req, err := http.NewRequest("POST", "/v2/model/serial", buf)
c.Assert(err, check.IsNil)

rsp := s.syncReq(c, req, nil)
c.Check(rsp.Result, check.IsNil)

c.Check(unregister, check.Equals, 1)
}

func (s *userSuite) TestPostSerialForgetNoRegistrationUntilReboot(c *check.C) {
unregister := 0
defer daemon.MockDevicestateDeviceManagerUnregister(func(mgr *devicestate.DeviceManager, opts *devicestate.UnregisterOptions) error {
unregister++
c.Check(mgr, check.NotNil)
c.Check(opts.NoRegistrationUntilReboot, check.Equals, true)
return nil
})()

buf := bytes.NewBufferString(`{"action":"forget", "no-registration-until-reboot": true}`)
req, err := http.NewRequest("POST", "/v2/model/serial", buf)
c.Assert(err, check.IsNil)

rsp := s.syncReq(c, req, nil)
c.Check(rsp.Result, check.IsNil)

c.Check(unregister, check.Equals, 1)
}

func (s *userSuite) TestPostSerialForgetError(c *check.C) {
defer daemon.MockDevicestateDeviceManagerUnregister(func(mgr *devicestate.DeviceManager, opts *devicestate.UnregisterOptions) error {
return errors.New("boom")
})()

buf := bytes.NewBufferString(`{"action":"forget"}`)
req, err := http.NewRequest("POST", "/v2/model/serial", buf)
c.Assert(err, check.IsNil)

rspe := s.errorReq(c, req, nil)
c.Check(rspe, check.DeepEquals, daemon.InternalError(`forgetting serial failed: boom`))
}
11 changes: 10 additions & 1 deletion daemon/export_api_model_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2020 Canonical Ltd
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
Expand All @@ -21,6 +21,7 @@ package daemon

import (
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/overlord/devicestate"
"github.com/snapcore/snapd/overlord/state"
)

Expand All @@ -32,6 +33,14 @@ func MockDevicestateRemodel(mock func(*state.State, *asserts.Model) (*state.Chan
}
}

func MockDevicestateDeviceManagerUnregister(mock func(*devicestate.DeviceManager, *devicestate.UnregisterOptions) error) (restore func()) {
oldDevicestateDeviceManagerUnregister := devicestateDeviceManagerUnregister
devicestateDeviceManagerUnregister = mock
return func() {
devicestateDeviceManagerUnregister = oldDevicestateDeviceManagerUnregister
}
}

type (
PostModelData = postModelData
ModelAssertJSON = modelAssertJSON
Expand Down
45 changes: 45 additions & 0 deletions tests/main/generic-unregister/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
summary: |
Ensure that unregistration API works.
# ubuntu-14.04: curl does not have --unix-socket option
systems: [-ubuntu-core-*, -ubuntu-14.04-*]

prepare: |
systemctl stop snapd.service snapd.socket
cp /var/lib/snapd/state.json state.json.bak
mkdir key
cp /var/lib/snapd/device/private-keys-v1/* key
systemctl start snapd.service snapd.socket
restore: |
systemctl stop snapd.service snapd.socket
cp key/* /var/lib/snapd/device/private-keys-v1/
cp state.json.bak /var/lib/snapd/state.json
rm -f /run/snapd/noregister
systemctl start snapd.service snapd.socket
execute: |
#shellcheck source=tests/lib/core-config.sh
. "$TESTSLIB"/core-config.sh
wait_for_device_initialized_change
snap model --assertion | MATCH "series: 16"
if snap model --verbose | NOMATCH "brand-id:\s* generic" ; then
echo "Not a generic model. Skipping."
exit 0
fi
curl --data '{"action":"forget","no-registration-until-reboot":true}' --unix-socket /run/snapd.socket http://localhost/v2/model/serial
test -f /run/snapd/noregister
snap model --serial 2>&1|MATCH "error: device not registered yet"
systemctl restart snapd.service
snap model --serial 2>&1|MATCH "error: device not registered yet"
snap find pc
NOMATCH '"session-macaroon":"[^"]' < /var/lib/snapd/state.json

0 comments on commit eabc9c3

Please sign in to comment.