This repository has been archived by the owner on Mar 14, 2024. It is now read-only.
/
kubelet.go
268 lines (244 loc) · 7.4 KB
/
kubelet.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
package bootstrapper
import (
"fmt"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
"k8s.io/apimachinery/pkg/util/wait"
)
const (
// svcPollInterval is the is the interval at which we poll the kubelet service
svcPollInterval = 30 * time.Second
// svcRunTimeout is the maximum duration to wait for the kubelet service to go to running state
svcRunTimeout = 2 * time.Minute
)
// kubeletService struct contains the kubelet specific service information
type kubeletService struct {
// obj is a pointer to the Windows service object
obj *mgr.Service
// dependents contains a list of services dependent on the current service
dependents []*mgr.Service
}
// newKubeletService creates and returns a new kubeletService object
func newKubeletService(ksvc *mgr.Service, dependents []*mgr.Service) (*kubeletService, error) {
if ksvc == nil {
return nil, fmt.Errorf("service object should not be nil")
}
return &kubeletService{
obj: ksvc,
dependents: dependents,
}, nil
}
// config retrieves service config from service object Config()
func (k *kubeletService) config() (mgr.Config, error) {
config, err := k.obj.Config()
if err != nil {
return mgr.Config{}, err
}
return config, nil
}
// start ensures that the kubelet service is running
func (k *kubeletService) start() error {
if k.obj == nil {
return fmt.Errorf("no kubelet service found")
}
// if the service is running we do not need to start it
isServiceRunning, err := k.isRunning()
if err != nil {
return fmt.Errorf("unable to check if Windows service is running: %v", err)
}
if isServiceRunning {
return nil
}
if err := k.obj.Start(); err != nil {
return err
}
if len(k.dependents) == 0 {
return nil
}
for _, dependent := range k.dependents {
err := startService(dependent)
if err != nil {
return fmt.Errorf("failed to start dependent service %s", dependent.Name)
}
}
return nil
}
// control sends a signal to the service and waits until it changes state in response to the signal
func (k *kubeletService) control(cmd svc.Cmd, desiredState svc.State) error {
status, err := k.obj.Control(cmd)
if err != nil {
return err
}
// Most of the rest of the function borrowed from https://godoc.org/golang.org/x/sys/windows/svc/mgr#Service.Control
timeout := time.Now().Add(serviceWaitTime)
for status.State != desiredState {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", desiredState)
}
time.Sleep(300 * time.Millisecond)
status, err = k.obj.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}
// stop ensures that the kubelet service and its dependent services are stopped,
// the list of dependent services is static and contains one level of dependencies
func (k *kubeletService) stop() error {
isServiceRunning, err := k.isRunning()
if err != nil {
return fmt.Errorf("unable to check if kubelet service is running: %v", err)
}
if !isServiceRunning {
return nil
}
// the list of dependents is static here and contains one level of dependencies
if len(k.dependents) != 0 {
for _, dependent := range k.dependents {
if err := stopService(dependent); err != nil {
return fmt.Errorf("failed to stop dependent service %s", dependent.Name)
}
}
}
if err := k.control(svc.Stop, svc.Stopped); err != nil {
return fmt.Errorf("unable to stop Windows Service %s", KubeletServiceName)
}
return nil
}
// refresh updates the kubelet service with the given config and restarts the service
func (k *kubeletService) refresh(config mgr.Config) error {
if err := k.stop(); err != nil {
return fmt.Errorf("error stopping kubelet service: %v", err)
}
if err := k.obj.UpdateConfig(config); err != nil {
return fmt.Errorf("error updating kubelet service: %v", err)
}
if err := k.start(); err != nil {
return fmt.Errorf("error starting kubelet service: %v", err)
}
// Wait for service to go to Running state
err := wait.Poll(svcPollInterval, svcRunTimeout, func() (done bool, err error) {
isKubeletRunning, err := k.isRunning()
if err != nil {
return false, nil
}
return isKubeletRunning, nil
})
if err != nil {
return fmt.Errorf("error running kubelet service %v", err)
}
return nil
}
// isRunning returns true if the kubelet service is running
func (k *kubeletService) isRunning() (bool, error) {
status, err := k.obj.Query()
if err != nil {
return false, err
}
return status.State == svc.Running, nil
}
// disconnect removes all connections to the Windows service svcMgr api, and allows services to be deleted
func (k *kubeletService) disconnect() error {
if k.obj == nil {
return nil
}
err := k.obj.Close()
if err != nil {
return err
}
return nil
}
// setRecoveryActions sets the recovery actions for service on a failure
func (k *kubeletService) setRecoveryActions() error {
if k.obj == nil {
return fmt.Errorf("kubelet service object should not be nil")
}
err := k.obj.SetRecoveryActions([]mgr.RecoveryAction{
{Type: mgr.ServiceRestart, Delay: 5},
}, 600)
if err != nil {
return err
}
return nil
}
// stopAndRemove stops and removes the kubelet service
func (k *kubeletService) stopAndRemove() error {
if k.obj == nil {
return nil
}
if err := k.stop(); err != nil {
return fmt.Errorf("failed to stop kubelet service: %v", err)
}
return k.obj.Delete()
}
// startService is a helper to start a given service
func startService(serviceObj *mgr.Service) error {
if serviceObj == nil {
return fmt.Errorf("service object should not be nil")
}
isServiceRunning, err := isServiceRunning(serviceObj)
if err != nil {
return fmt.Errorf("unable to check if service is running: %v", err)
}
if !isServiceRunning {
err := serviceObj.Start()
if err != nil {
return err
}
}
return nil
}
// controlService is a helper to send control signal to a given service
func controlService(serviceObj *mgr.Service, cmd svc.Cmd, desiredState svc.State) error {
if serviceObj == nil {
return fmt.Errorf("service object should not be nil")
}
status, err := serviceObj.Control(cmd)
if err != nil {
return err
}
// Most of the rest of the function borrowed from https://godoc.org/golang.org/x/sys/windows/svc/mgr#Service.Control
// Arbitrary service wait time of 20 seconds
timeout := time.Now().Add(serviceWaitTime)
for status.State != desiredState {
if timeout.Before(time.Now()) {
return fmt.Errorf("timeout waiting for service to go to state=%d", desiredState)
}
time.Sleep(300 * time.Millisecond)
status, err = serviceObj.Query()
if err != nil {
return fmt.Errorf("could not retrieve service status: %v", err)
}
}
return nil
}
// stopService is a helper to stop a given service
func stopService(serviceObj *mgr.Service) error {
if serviceObj == nil {
return fmt.Errorf("service object should not be nil")
}
isServiceRunning, err := isServiceRunning(serviceObj)
if err != nil {
return fmt.Errorf("unable to check if service is running: %v", err)
}
if isServiceRunning {
err := controlService(serviceObj, svc.Stop, svc.Stopped)
if err != nil {
return fmt.Errorf("unable to stop %s service", serviceObj.Name)
}
}
return nil
}
// isServiceRunning returns true if the given service is running
func isServiceRunning(serviceObj *mgr.Service) (bool, error) {
if serviceObj == nil {
return false, fmt.Errorf("service object should not be nil")
}
status, err := serviceObj.Query()
if err != nil {
return false, err
}
return status.State == svc.Running, nil
}