blob: ccf8ee17ad02d8566319da336632031dee9424d8 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package server
18
19import (
20 "errors"
21 "fmt"
22 "net/http"
23
24 "github.com/golang/glog"
25
26 utilerrors "k8s.io/apimachinery/pkg/util/errors"
27 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
28 "k8s.io/apiserver/pkg/server/healthz"
29 restclient "k8s.io/client-go/rest"
30)
31
32// PostStartHookFunc is a function that is called after the server has started.
33// It must properly handle cases like:
34// 1. asynchronous start in multiple API server processes
35// 2. conflicts between the different processes all trying to perform the same action
36// 3. partially complete work (API server crashes while running your hook)
37// 4. API server access **BEFORE** your hook has completed
38// Think of it like a mini-controller that is super privileged and gets to run in-process
39// If you use this feature, tag @deads2k on github who has promised to review code for anyone's PostStartHook
40// until it becomes easier to use.
41type PostStartHookFunc func(context PostStartHookContext) error
42
43// PreShutdownHookFunc is a function that can be added to the shutdown logic.
44type PreShutdownHookFunc func() error
45
46// PostStartHookContext provides information about this API server to a PostStartHookFunc
47type PostStartHookContext struct {
48 // LoopbackClientConfig is a config for a privileged loopback connection to the API server
49 LoopbackClientConfig *restclient.Config
50 // StopCh is the channel that will be closed when the server stops
51 StopCh <-chan struct{}
52}
53
54// PostStartHookProvider is an interface in addition to provide a post start hook for the api server
55type PostStartHookProvider interface {
56 PostStartHook() (string, PostStartHookFunc, error)
57}
58
59type postStartHookEntry struct {
60 hook PostStartHookFunc
61
62 // done will be closed when the postHook is finished
63 done chan struct{}
64}
65
66type preShutdownHookEntry struct {
67 hook PreShutdownHookFunc
68}
69
70// AddPostStartHook allows you to add a PostStartHook.
71func (s *GenericAPIServer) AddPostStartHook(name string, hook PostStartHookFunc) error {
72 if len(name) == 0 {
73 return fmt.Errorf("missing name")
74 }
75 if hook == nil {
76 return nil
77 }
78 if s.disabledPostStartHooks.Has(name) {
79 return nil
80 }
81
82 s.postStartHookLock.Lock()
83 defer s.postStartHookLock.Unlock()
84
85 if s.postStartHooksCalled {
86 return fmt.Errorf("unable to add %q because PostStartHooks have already been called", name)
87 }
88 if _, exists := s.postStartHooks[name]; exists {
89 return fmt.Errorf("unable to add %q because it is already registered", name)
90 }
91
92 // done is closed when the poststarthook is finished. This is used by the health check to be able to indicate
93 // that the poststarthook is finished
94 done := make(chan struct{})
95 s.AddHealthzChecks(postStartHookHealthz{name: "poststarthook/" + name, done: done})
96 s.postStartHooks[name] = postStartHookEntry{hook: hook, done: done}
97
98 return nil
99}
100
101// AddPostStartHookOrDie allows you to add a PostStartHook, but dies on failure
102func (s *GenericAPIServer) AddPostStartHookOrDie(name string, hook PostStartHookFunc) {
103 if err := s.AddPostStartHook(name, hook); err != nil {
104 glog.Fatalf("Error registering PostStartHook %q: %v", name, err)
105 }
106}
107
108// AddPreShutdownHook allows you to add a PreShutdownHook.
109func (s *GenericAPIServer) AddPreShutdownHook(name string, hook PreShutdownHookFunc) error {
110 if len(name) == 0 {
111 return fmt.Errorf("missing name")
112 }
113 if hook == nil {
114 return nil
115 }
116
117 s.preShutdownHookLock.Lock()
118 defer s.preShutdownHookLock.Unlock()
119
120 if s.preShutdownHooksCalled {
121 return fmt.Errorf("unable to add %q because PreShutdownHooks have already been called", name)
122 }
123 if _, exists := s.preShutdownHooks[name]; exists {
124 return fmt.Errorf("unable to add %q because it is already registered", name)
125 }
126
127 s.preShutdownHooks[name] = preShutdownHookEntry{hook: hook}
128
129 return nil
130}
131
132// AddPreShutdownHookOrDie allows you to add a PostStartHook, but dies on failure
133func (s *GenericAPIServer) AddPreShutdownHookOrDie(name string, hook PreShutdownHookFunc) {
134 if err := s.AddPreShutdownHook(name, hook); err != nil {
135 glog.Fatalf("Error registering PreShutdownHook %q: %v", name, err)
136 }
137}
138
139// RunPostStartHooks runs the PostStartHooks for the server
140func (s *GenericAPIServer) RunPostStartHooks(stopCh <-chan struct{}) {
141 s.postStartHookLock.Lock()
142 defer s.postStartHookLock.Unlock()
143 s.postStartHooksCalled = true
144
145 context := PostStartHookContext{
146 LoopbackClientConfig: s.LoopbackClientConfig,
147 StopCh: stopCh,
148 }
149
150 for hookName, hookEntry := range s.postStartHooks {
151 go runPostStartHook(hookName, hookEntry, context)
152 }
153}
154
155// RunPreShutdownHooks runs the PreShutdownHooks for the server
156func (s *GenericAPIServer) RunPreShutdownHooks() error {
157 var errorList []error
158
159 s.preShutdownHookLock.Lock()
160 defer s.preShutdownHookLock.Unlock()
161 s.preShutdownHooksCalled = true
162
163 for hookName, hookEntry := range s.preShutdownHooks {
164 if err := runPreShutdownHook(hookName, hookEntry); err != nil {
165 errorList = append(errorList, err)
166 }
167 }
168 return utilerrors.NewAggregate(errorList)
169}
170
171// isPostStartHookRegistered checks whether a given PostStartHook is registered
172func (s *GenericAPIServer) isPostStartHookRegistered(name string) bool {
173 s.postStartHookLock.Lock()
174 defer s.postStartHookLock.Unlock()
175 _, exists := s.postStartHooks[name]
176 return exists
177}
178
179func runPostStartHook(name string, entry postStartHookEntry, context PostStartHookContext) {
180 var err error
181 func() {
182 // don't let the hook *accidentally* panic and kill the server
183 defer utilruntime.HandleCrash()
184 err = entry.hook(context)
185 }()
186 // if the hook intentionally wants to kill server, let it.
187 if err != nil {
188 glog.Fatalf("PostStartHook %q failed: %v", name, err)
189 }
190 close(entry.done)
191}
192
193func runPreShutdownHook(name string, entry preShutdownHookEntry) error {
194 var err error
195 func() {
196 // don't let the hook *accidentally* panic and kill the server
197 defer utilruntime.HandleCrash()
198 err = entry.hook()
199 }()
200 if err != nil {
201 return fmt.Errorf("PreShutdownHook %q failed: %v", name, err)
202 }
203 return nil
204}
205
206// postStartHookHealthz implements a healthz check for poststarthooks. It will return a "hookNotFinished"
207// error until the poststarthook is finished.
208type postStartHookHealthz struct {
209 name string
210
211 // done will be closed when the postStartHook is finished
212 done chan struct{}
213}
214
215var _ healthz.HealthzChecker = postStartHookHealthz{}
216
217func (h postStartHookHealthz) Name() string {
218 return h.name
219}
220
221var hookNotFinished = errors.New("not finished")
222
223func (h postStartHookHealthz) Check(req *http.Request) error {
224 select {
225 case <-h.done:
226 return nil
227 default:
228 return hookNotFinished
229 }
230}