Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

o/devicestate: raise conflict when requesting system action while seeding #8689

Merged
merged 14 commits into from May 21, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 26 additions & 0 deletions daemon/api_systems_test.go
Expand Up @@ -218,9 +218,12 @@ func (s *apiSuite) TestSystemActionRequestErrors(c *check.C) {
restore := s.mockSystemSeeds(c)
defer restore()

st := d.overlord.State()

type table struct {
label, body, error string
status int
unseeded bool
}
tests := []table{
{
Expand Down Expand Up @@ -254,9 +257,23 @@ func (s *apiSuite) TestSystemActionRequestErrors(c *check.C) {
body: `{"action":"do","mode":"foobar"}`,
error: `requested action is not supported by system "20191119"`,
status: 400,
}, {
// valid label and action, but seeding is not complete yet
label: "20191119",
body: `{"action":"do","mode":"install"}`,
error: `cannot request system action, seeding not started yet`,
status: 500,
unseeded: true,
},
}
for _, tc := range tests {
st.Lock()
if tc.unseeded {
st.Set("seeded", nil)
} else {
st.Set("seeded", true)
}
st.Unlock()
s.vars = map[string]string{"label": tc.label}
c.Logf("tc: %#v", tc)
req, err := http.NewRequest("POST", "/v2/systems/"+tc.label, strings.NewReader(tc.body))
Expand Down Expand Up @@ -402,6 +419,8 @@ func (s *apiSuite) TestSystemActionRequestWithSeeded(c *check.C) {
// only set in run mode
st.Set("seeded-systems", currentSystem)
}
// the seeding is done
st.Set("seeded", true)
st.Unlock()

body := map[string]string{
Expand Down Expand Up @@ -472,13 +491,20 @@ func (s *apiSuite) TestSystemActionRequestWithSeeded(c *check.C) {
}

func (s *apiSuite) TestSystemActionBrokenSeed(c *check.C) {

d := s.daemonWithOverlordMock(c)
hookMgr, err := hookstate.Manager(d.overlord.State(), d.overlord.TaskRunner())
c.Assert(err, check.IsNil)
mgr, err := devicestate.Manager(d.overlord.State(), hookMgr, d.overlord.TaskRunner(), nil)
c.Assert(err, check.IsNil)
d.overlord.AddManager(mgr)

// the seeding is done
st := d.overlord.State()
st.Lock()
st.Set("seeded", true)
st.Unlock()

restore := s.mockSystemSeeds(c)
defer restore()

Expand Down
94 changes: 4 additions & 90 deletions overlord/devicestate/devicemgr.go
Expand Up @@ -43,7 +43,6 @@ import (
"github.com/snapcore/snapd/overlord/state"
"github.com/snapcore/snapd/overlord/storecontext"
"github.com/snapcore/snapd/release"
"github.com/snapcore/snapd/seed"
"github.com/snapcore/snapd/snapdenv"
"github.com/snapcore/snapd/timings"
)
Expand Down Expand Up @@ -773,97 +772,8 @@ var currentSystemActions = []SystemAction{
{Title: "Run normally", Mode: "run"},
}

func systemFromSeed(label string) (*System, error) {
s, err := seed.Open(dirs.SnapSeedDir, label)
if err != nil {
return nil, fmt.Errorf("cannot open: %v", err)
}
if err := s.LoadAssertions(nil, nil); err != nil {
return nil, fmt.Errorf("cannot load assertions: %v", err)
}
// get the model
model, err := s.Model()
if err != nil {
return nil, fmt.Errorf("cannot obtain model: %v", err)
}
brand, err := s.Brand()
if err != nil {
return nil, fmt.Errorf("cannot obtain brand: %v", err)
}
system := System{
Current: false,
Label: label,
Model: model,
Brand: brand,
Actions: defaultSystemActions,
}
return &system, nil
}

var ErrNoSystems = errors.New("no systems seeds")

func currentSystemForMode(st *state.State, mode string) (*seededSystem, error) {
switch mode {
case "run":
return currentSeedSystem(st)
case "install":
// there is no current system for install mode
return nil, nil
case "recover":
// recover mode uses modeenv for reference
return seededSystemFromModeenv()
}
return nil, fmt.Errorf("internal error: cannot identify current system for unsupported mode %q", mode)
}

func seededSystemFromModeenv() (*seededSystem, error) {
modeEnv, err := maybeReadModeenv()
if err != nil {
return nil, err
}
if modeEnv == nil {
return nil, fmt.Errorf("internal error: modeenv does not exist")
}
if modeEnv.RecoverySystem == "" {
return nil, fmt.Errorf("internal error: recovery system is unset")
}

system, err := systemFromSeed(modeEnv.RecoverySystem)
if err != nil {
return nil, err
}
seededSys := &seededSystem{
System: modeEnv.RecoverySystem,
Model: system.Model.Model(),
BrandID: system.Model.BrandID(),
Revision: system.Model.Revision(),
Timestamp: system.Model.Timestamp(),
// SeedTime is intentionally left unset
}
return seededSys, nil
}

func currentSeedSystem(st *state.State) (*seededSystem, error) {
st.Lock()
defer st.Unlock()

var whatseeded []seededSystem
if err := st.Get("seeded-systems", &whatseeded); err != nil {
return nil, err
}
if len(whatseeded) == 0 {
// unexpected
return nil, state.ErrNoState
}
return &whatseeded[0], nil
}

func isCurrentSystem(current *seededSystem, other *System) bool {
return current.System == other.Label &&
current.Model == other.Model.Model() &&
current.BrandID == other.Brand.AccountID()
}

// Systems list the available recovery/seeding systems. Returns the list of
// systems, ErrNoSystems when no systems seeds were found or other error.
func (m *DeviceManager) Systems() ([]*System, error) {
Expand Down Expand Up @@ -904,6 +814,10 @@ var ErrUnsupportedAction = errors.New("unsupported action")
// system reboot will be requested when the request can be successfully carried
// out.
func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAction) error {
if err := checkSystemRequestConflict(m.state, systemLabel); err != nil {
return err
}

currentSys, _ := currentSystemForMode(m.state, m.systemMode)

systemSeedDir := filepath.Join(dirs.SnapSeedDir, "systems", systemLabel)
Expand Down