| /* |
| Copyright 2014 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| package server |
| |
| import ( |
| "errors" |
| "fmt" |
| "net/http" |
| |
| "github.com/golang/glog" |
| |
| utilerrors "k8s.io/apimachinery/pkg/util/errors" |
| utilruntime "k8s.io/apimachinery/pkg/util/runtime" |
| "k8s.io/apiserver/pkg/server/healthz" |
| restclient "k8s.io/client-go/rest" |
| ) |
| |
| // PostStartHookFunc is a function that is called after the server has started. |
| // It must properly handle cases like: |
| // 1. asynchronous start in multiple API server processes |
| // 2. conflicts between the different processes all trying to perform the same action |
| // 3. partially complete work (API server crashes while running your hook) |
| // 4. API server access **BEFORE** your hook has completed |
| // Think of it like a mini-controller that is super privileged and gets to run in-process |
| // If you use this feature, tag @deads2k on github who has promised to review code for anyone's PostStartHook |
| // until it becomes easier to use. |
| type PostStartHookFunc func(context PostStartHookContext) error |
| |
| // PreShutdownHookFunc is a function that can be added to the shutdown logic. |
| type PreShutdownHookFunc func() error |
| |
| // PostStartHookContext provides information about this API server to a PostStartHookFunc |
| type PostStartHookContext struct { |
| // LoopbackClientConfig is a config for a privileged loopback connection to the API server |
| LoopbackClientConfig *restclient.Config |
| // StopCh is the channel that will be closed when the server stops |
| StopCh <-chan struct{} |
| } |
| |
| // PostStartHookProvider is an interface in addition to provide a post start hook for the api server |
| type PostStartHookProvider interface { |
| PostStartHook() (string, PostStartHookFunc, error) |
| } |
| |
| type postStartHookEntry struct { |
| hook PostStartHookFunc |
| |
| // done will be closed when the postHook is finished |
| done chan struct{} |
| } |
| |
| type preShutdownHookEntry struct { |
| hook PreShutdownHookFunc |
| } |
| |
| // AddPostStartHook allows you to add a PostStartHook. |
| func (s *GenericAPIServer) AddPostStartHook(name string, hook PostStartHookFunc) error { |
| if len(name) == 0 { |
| return fmt.Errorf("missing name") |
| } |
| if hook == nil { |
| return nil |
| } |
| if s.disabledPostStartHooks.Has(name) { |
| return nil |
| } |
| |
| s.postStartHookLock.Lock() |
| defer s.postStartHookLock.Unlock() |
| |
| if s.postStartHooksCalled { |
| return fmt.Errorf("unable to add %q because PostStartHooks have already been called", name) |
| } |
| if _, exists := s.postStartHooks[name]; exists { |
| return fmt.Errorf("unable to add %q because it is already registered", name) |
| } |
| |
| // done is closed when the poststarthook is finished. This is used by the health check to be able to indicate |
| // that the poststarthook is finished |
| done := make(chan struct{}) |
| s.AddHealthzChecks(postStartHookHealthz{name: "poststarthook/" + name, done: done}) |
| s.postStartHooks[name] = postStartHookEntry{hook: hook, done: done} |
| |
| return nil |
| } |
| |
| // AddPostStartHookOrDie allows you to add a PostStartHook, but dies on failure |
| func (s *GenericAPIServer) AddPostStartHookOrDie(name string, hook PostStartHookFunc) { |
| if err := s.AddPostStartHook(name, hook); err != nil { |
| glog.Fatalf("Error registering PostStartHook %q: %v", name, err) |
| } |
| } |
| |
| // AddPreShutdownHook allows you to add a PreShutdownHook. |
| func (s *GenericAPIServer) AddPreShutdownHook(name string, hook PreShutdownHookFunc) error { |
| if len(name) == 0 { |
| return fmt.Errorf("missing name") |
| } |
| if hook == nil { |
| return nil |
| } |
| |
| s.preShutdownHookLock.Lock() |
| defer s.preShutdownHookLock.Unlock() |
| |
| if s.preShutdownHooksCalled { |
| return fmt.Errorf("unable to add %q because PreShutdownHooks have already been called", name) |
| } |
| if _, exists := s.preShutdownHooks[name]; exists { |
| return fmt.Errorf("unable to add %q because it is already registered", name) |
| } |
| |
| s.preShutdownHooks[name] = preShutdownHookEntry{hook: hook} |
| |
| return nil |
| } |
| |
| // AddPreShutdownHookOrDie allows you to add a PostStartHook, but dies on failure |
| func (s *GenericAPIServer) AddPreShutdownHookOrDie(name string, hook PreShutdownHookFunc) { |
| if err := s.AddPreShutdownHook(name, hook); err != nil { |
| glog.Fatalf("Error registering PreShutdownHook %q: %v", name, err) |
| } |
| } |
| |
| // RunPostStartHooks runs the PostStartHooks for the server |
| func (s *GenericAPIServer) RunPostStartHooks(stopCh <-chan struct{}) { |
| s.postStartHookLock.Lock() |
| defer s.postStartHookLock.Unlock() |
| s.postStartHooksCalled = true |
| |
| context := PostStartHookContext{ |
| LoopbackClientConfig: s.LoopbackClientConfig, |
| StopCh: stopCh, |
| } |
| |
| for hookName, hookEntry := range s.postStartHooks { |
| go runPostStartHook(hookName, hookEntry, context) |
| } |
| } |
| |
| // RunPreShutdownHooks runs the PreShutdownHooks for the server |
| func (s *GenericAPIServer) RunPreShutdownHooks() error { |
| var errorList []error |
| |
| s.preShutdownHookLock.Lock() |
| defer s.preShutdownHookLock.Unlock() |
| s.preShutdownHooksCalled = true |
| |
| for hookName, hookEntry := range s.preShutdownHooks { |
| if err := runPreShutdownHook(hookName, hookEntry); err != nil { |
| errorList = append(errorList, err) |
| } |
| } |
| return utilerrors.NewAggregate(errorList) |
| } |
| |
| // isPostStartHookRegistered checks whether a given PostStartHook is registered |
| func (s *GenericAPIServer) isPostStartHookRegistered(name string) bool { |
| s.postStartHookLock.Lock() |
| defer s.postStartHookLock.Unlock() |
| _, exists := s.postStartHooks[name] |
| return exists |
| } |
| |
| func runPostStartHook(name string, entry postStartHookEntry, context PostStartHookContext) { |
| var err error |
| func() { |
| // don't let the hook *accidentally* panic and kill the server |
| defer utilruntime.HandleCrash() |
| err = entry.hook(context) |
| }() |
| // if the hook intentionally wants to kill server, let it. |
| if err != nil { |
| glog.Fatalf("PostStartHook %q failed: %v", name, err) |
| } |
| close(entry.done) |
| } |
| |
| func runPreShutdownHook(name string, entry preShutdownHookEntry) error { |
| var err error |
| func() { |
| // don't let the hook *accidentally* panic and kill the server |
| defer utilruntime.HandleCrash() |
| err = entry.hook() |
| }() |
| if err != nil { |
| return fmt.Errorf("PreShutdownHook %q failed: %v", name, err) |
| } |
| return nil |
| } |
| |
| // postStartHookHealthz implements a healthz check for poststarthooks. It will return a "hookNotFinished" |
| // error until the poststarthook is finished. |
| type postStartHookHealthz struct { |
| name string |
| |
| // done will be closed when the postStartHook is finished |
| done chan struct{} |
| } |
| |
| var _ healthz.HealthzChecker = postStartHookHealthz{} |
| |
| func (h postStartHookHealthz) Name() string { |
| return h.name |
| } |
| |
| var hookNotFinished = errors.New("not finished") |
| |
| func (h postStartHookHealthz) Check(req *http.Request) error { |
| select { |
| case <-h.done: |
| return nil |
| default: |
| return hookNotFinished |
| } |
| } |