-
Notifications
You must be signed in to change notification settings - Fork 54
/
restart.go
169 lines (151 loc) · 4.76 KB
/
restart.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// Copyright (c) 2014-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
// published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package restart implements requesting restarts from any part of the code that has access to state.
package restart
import (
"errors"
"github.com/canonical/pebble/internals/overlord/state"
)
type RestartType int
const (
RestartUnset RestartType = iota
RestartDaemon
RestartSystem
// RestartSystemNow is like RestartSystem but action is immediate
RestartSystemNow
// RestartSocket will restart the daemon so that it goes into
// socket activation mode.
RestartSocket
// Stop just stops the daemon (used with image pre-seeding)
StopDaemon
// RestartSystemHaltNow will shutdown --halt the system asap
RestartSystemHaltNow
// RestartSystemPoweroffNow will shutdown --poweroff the system asap
RestartSystemPoweroffNow
RestartServiceFailure
RestartCheckFailure
)
// Handler can handle restart requests and whether expected reboots happen.
type Handler interface {
HandleRestart(t RestartType)
// RebootIsFine is called early when either a reboot was
// requested by snapd and happened or no reboot was expected at all.
RebootIsFine(st *state.State) error
// RebootIsMissing is called early instead when a reboot was
// requested by snapd but did not happen.
RebootIsMissing(st *state.State) error
}
// Init initializes the support for restarts requests.
// It takes the current boot id to track and verify reboots and a
// Handler that handles the actual requests and reacts to reboot
// happening.
// It must be called with the state lock held.
func Init(st *state.State, curBootID string, h Handler) error {
rs := &restartState{
h: h,
bootID: curBootID,
}
var fromBootID string
err := st.Get("system-restart-from-boot-id", &fromBootID)
if err != nil && !errors.Is(err, state.ErrNoState) {
return err
}
st.Cache(restartStateKey{}, rs)
if fromBootID == "" {
return rs.rebootAsExpected(st)
}
if fromBootID == curBootID {
return rs.rebootDidNotHappen(st)
}
// we rebooted alright
ClearReboot(st)
return rs.rebootAsExpected(st)
}
// ClearReboot clears state information about tracking requested reboots.
func ClearReboot(st *state.State) {
st.Set("system-restart-from-boot-id", nil)
}
type restartStateKey struct{}
type restartState struct {
restarting RestartType
h Handler
bootID string
}
func (rs *restartState) handleRestart(t RestartType) {
if rs.h != nil {
rs.h.HandleRestart(t)
}
}
func (rs *restartState) rebootAsExpected(st *state.State) error {
if rs.h != nil {
return rs.h.RebootIsFine(st)
}
return nil
}
func (rs *restartState) rebootDidNotHappen(st *state.State) error {
if rs.h != nil {
return rs.h.RebootIsMissing(st)
}
return nil
}
// Request asks for a restart of the managing process.
// The state needs to be locked to request a restart.
func Request(st *state.State, t RestartType) {
cached := st.Cached(restartStateKey{})
if cached == nil {
panic("internal error: cannot request a restart before restart.Init")
}
rs := cached.(*restartState)
switch t {
case RestartSystem, RestartSystemNow, RestartSystemHaltNow, RestartSystemPoweroffNow:
st.Set("system-restart-from-boot-id", rs.bootID)
}
rs.restarting = t
rs.handleRestart(t)
}
// Pending returns whether a restart was requested with Request and of which type.
func Pending(st *state.State) (bool, RestartType) {
cached := st.Cached(restartStateKey{})
if cached == nil {
return false, RestartUnset
}
rs := cached.(*restartState)
return rs.restarting != RestartUnset, rs.restarting
}
func FakePending(st *state.State, restarting RestartType) RestartType {
cached := st.Cached(restartStateKey{})
if cached == nil {
panic("internal error: cannot fake a restart request before restart.Init")
}
rs := cached.(*restartState)
old := rs.restarting
rs.restarting = restarting
return old
}
func ReplaceBootID(st *state.State, bootID string) {
cached := st.Cached(restartStateKey{})
if cached == nil {
panic("internal error: cannot fake a restart request before restart.Init")
}
rs := cached.(*restartState)
rs.bootID = bootID
}
func BootID(st *state.State) string {
cached := st.Cached(restartStateKey{})
if cached == nil {
panic("internal error: cannot fake a restart request before restart.Init")
}
rs := cached.(*restartState)
return rs.bootID
}