diff --git a/daemon/api_systems_test.go b/daemon/api_systems_test.go index a433056afed..4b3fb9c5b20 100644 --- a/daemon/api_systems_test.go +++ b/daemon/api_systems_test.go @@ -349,9 +349,10 @@ func (s *apiSuite) TestSystemActionRequestWithSeeded(c *check.C) { }, { // from recover mode -> recover mode is no-op - currentMode: "recover", - actionMode: "recover", - comment: "recover mode to recover mode", + currentMode: "recover", + actionMode: "recover", + expUnsupported: true, + comment: "recover mode to recover mode", }, { // from install mode -> install mode is no-no diff --git a/overlord/devicestate/devicemgr.go b/overlord/devicestate/devicemgr.go index 40117fa580f..dc95c2df9e4 100644 --- a/overlord/devicestate/devicemgr.go +++ b/overlord/devicestate/devicemgr.go @@ -772,6 +772,10 @@ var currentSystemActions = []SystemAction{ {Title: "Recover", Mode: "recover"}, {Title: "Run normally", Mode: "run"}, } +var recoverSystemActions = []SystemAction{ + {Title: "Reinstall", Mode: "install"}, + {Title: "Run normally", Mode: "run"}, +} func systemFromSeed(label string) (*System, error) { s, err := seed.Open(dirs.SnapSeedDir, label) @@ -864,6 +868,19 @@ func isCurrentSystem(current *seededSystem, other *System) bool { current.BrandID == other.Brand.AccountID() } +func currentSystemActionsForMode(mode string) []SystemAction { + switch mode { + case "install": + // there should be no current system in install mode + return nil + case "run": + return currentSystemActions + case "recover": + return recoverSystemActions + } + return nil +} + // 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) { @@ -891,7 +908,7 @@ func (m *DeviceManager) Systems() ([]*System, error) { } if currentSys != nil && isCurrentSystem(currentSys, system) { system.Current = true - system.Actions = currentSystemActions + system.Actions = currentSystemActionsForMode(m.systemMode) } systems = append(systems, system) } @@ -915,8 +932,7 @@ func (m *DeviceManager) RequestSystemAction(systemLabel string, action SystemAct return fmt.Errorf("cannot load seed system: %v", err) } if currentSys != nil && isCurrentSystem(currentSys, system) { - system.Current = true - system.Actions = currentSystemActions + system.Actions = currentSystemActionsForMode(m.systemMode) } var sysAction *SystemAction diff --git a/overlord/devicestate/devicestate_systems_test.go b/overlord/devicestate/devicestate_systems_test.go index 50a8b53f5dc..21dc971799d 100644 --- a/overlord/devicestate/devicestate_systems_test.go +++ b/overlord/devicestate/devicestate_systems_test.go @@ -337,8 +337,8 @@ func (s *deviceMgrSystemsSuite) TestRequestSameModeSameSystem(c *C) { label := s.mockedSystemSeeds[0].label - happyModes := []string{"run", "recover"} - sadModes := []string{"install"} + happyModes := []string{"run"} + sadModes := []string{"install", "recover"} for _, mode := range append(happyModes, sadModes...) { c.Logf("checking mode: %q", mode) @@ -393,6 +393,24 @@ func (s *deviceMgrSystemsSuite) TestRequestNotYetSeeded(c *C) { } } +func (s *deviceMgrSystemsSuite) testRequestModeWithRestart(c *C, toModes []string, label string) { + for _, mode := range toModes { + c.Logf("checking mode: %q", mode) + err := s.mgr.RequestSystemAction(label, + devicestate.SystemAction{Mode: mode}) + c.Assert(err, IsNil) + m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") + c.Assert(err, IsNil) + c.Check(m, DeepEquals, map[string]string{ + "snapd_recovery_system": label, + "snapd_recovery_mode": mode, + }) + c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) + s.restartRequests = nil + s.bootloader.BootVars = map[string]string{} + } +} + func (s *deviceMgrSystemsSuite) TestRequestModeRunInstallForRecover(c *C) { // we are in recover mode here devicestate.SetSystemMode(s.mgr, "recover") @@ -414,21 +432,7 @@ func (s *deviceMgrSystemsSuite) TestRequestModeRunInstallForRecover(c *C) { }) s.state.Unlock() - for _, mode := range []string{"install", "run"} { - c.Logf("checking mode: %q", mode) - err := s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, - devicestate.SystemAction{Mode: mode}) - c.Assert(err, IsNil) - m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") - c.Assert(err, IsNil) - c.Check(m, DeepEquals, map[string]string{ - "snapd_recovery_system": s.mockedSystemSeeds[0].label, - "snapd_recovery_mode": mode, - }) - c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) - s.restartRequests = nil - s.bootloader.BootVars = map[string]string{} - } + s.testRequestModeWithRestart(c, []string{"install", "run"}, s.mockedSystemSeeds[0].label) } func (s *deviceMgrSystemsSuite) TestRequestModeInstallRecoverForCurrent(c *C) { @@ -450,21 +454,7 @@ func (s *deviceMgrSystemsSuite) TestRequestModeInstallRecoverForCurrent(c *C) { }) s.state.Unlock() - for _, mode := range []string{"install", "recover"} { - c.Logf("checking mode: %q", mode) - err := s.mgr.RequestSystemAction(s.mockedSystemSeeds[0].label, - devicestate.SystemAction{Mode: mode}) - c.Assert(err, IsNil) - m, err := s.bootloader.GetBootVars("snapd_recovery_mode", "snapd_recovery_system") - c.Assert(err, IsNil) - c.Check(m, DeepEquals, map[string]string{ - "snapd_recovery_system": s.mockedSystemSeeds[0].label, - "snapd_recovery_mode": mode, - }) - c.Check(s.restartRequests, DeepEquals, []state.RestartType{state.RestartSystemNow}) - s.restartRequests = nil - s.bootloader.BootVars = map[string]string{} - } + s.testRequestModeWithRestart(c, []string{"install", "recover"}, s.mockedSystemSeeds[0].label) } func (s *deviceMgrSystemsSuite) TestRequestModeErrInBoot(c *C) { @@ -523,3 +513,25 @@ func (s *deviceMgrSystemsSuite) TestRequestModeForNonCurrent(c *C) { c.Assert(err, Equals, devicestate.ErrUnsupportedAction) c.Check(s.restartRequests, HasLen, 0) } + +func (s *deviceMgrSystemsSuite) TestRequestInstallForOther(c *C) { + devicestate.SetSystemMode(s.mgr, "run") + // non run modes use modeenv + modeenv := boot.Modeenv{ + Mode: "run", + } + err := modeenv.WriteTo("") + c.Assert(err, IsNil) + + s.state.Lock() + s.state.Set("seeded-systems", []devicestate.SeededSystem{ + { + System: s.mockedSystemSeeds[0].label, + Model: s.mockedSystemSeeds[0].model.Model(), + BrandID: s.mockedSystemSeeds[0].brand.AccountID(), + }, + }) + s.state.Unlock() + // reinstall from different system seed is ok + s.testRequestModeWithRestart(c, []string{"install"}, s.mockedSystemSeeds[1].label) +} diff --git a/tests/core/uc20-recovery/task.yaml b/tests/core/uc20-recovery/task.yaml index f2ce6992237..08abb5af808 100644 --- a/tests/core/uc20-recovery/task.yaml +++ b/tests/core/uc20-recovery/task.yaml @@ -14,6 +14,9 @@ debug: | if [ -e systems.json ]; then cat systems.json fi + if [ -e system-info ]; then + cat system-info + fi if [ -e /tmp/mock-shutdown.calls ]; then cat /tmp/mock-shutdown.calls fi @@ -39,7 +42,7 @@ execute: | test -n "$label" # make sure that the seed exists test -d "/var/lib/snapd/seed/systems/$label" - jq -r .result.systems[0].actions[].mode < systems.json | MATCH 'recover' + jq -r .result.systems[0].actions[].mode < systems.json | sort | tr '\n' ' ' | MATCH 'install recover run' # keep a copy of the systems dump for later reference cp systems.json /writable/systems.json.run @@ -92,8 +95,8 @@ execute: | test -e /host/ubuntu-data/systems.json.run test-snapd-curl.curl -s --unix-socket /run/snapd.socket http://localhost/v2/systems > systems.json - # systems list should be identical - diff -up /host/ubuntu-data/systems.json.run systems.json + jq -r .result.systems[0].actions[].mode < systems.json | sort | tr '\n' ' ' | MATCH 'install run' + label="$(cat /host/ubuntu-data/systems.label)" test -n "$label" # seed in mounted in recover mode too