mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-03-09 17:09:29 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
247 lines
7.7 KiB
Go
247 lines
7.7 KiB
Go
/*
|
|
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 (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"runtime/debug"
|
|
|
|
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"
|
|
"k8s.io/klog/v2"
|
|
)
|
|
|
|
// 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
|
|
// Context gets cancelled when the server stops.
|
|
context.Context
|
|
}
|
|
|
|
// 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
|
|
// originatingStack holds the stack that registered postStartHooks. This allows us to show a more helpful message
|
|
// for duplicate registration.
|
|
originatingStack string
|
|
|
|
// done will be closed when the postHook is finished
|
|
done chan struct{}
|
|
}
|
|
|
|
type PostStartHookConfigEntry struct {
|
|
hook PostStartHookFunc
|
|
// originatingStack holds the stack that registered postStartHooks. This allows us to show a more helpful message
|
|
// for duplicate registration.
|
|
originatingStack string
|
|
}
|
|
|
|
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 fmt.Errorf("hook func may not be nil: %q", name)
|
|
}
|
|
if s.disabledPostStartHooks.Has(name) {
|
|
klog.V(1).Infof("skipping %q because it was explicitly disabled", 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 postStartHook, exists := s.postStartHooks[name]; exists {
|
|
// this is programmer error, but it can be hard to debug
|
|
return fmt.Errorf("unable to add %q because it was already registered by: %s", name, postStartHook.originatingStack)
|
|
}
|
|
|
|
// 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{})
|
|
if err := s.AddBootSequenceHealthChecks(postStartHookHealthz{name: "poststarthook/" + name, done: done}); err != nil {
|
|
return err
|
|
}
|
|
s.postStartHooks[name] = postStartHookEntry{hook: hook, originatingStack: string(debug.Stack()), 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 {
|
|
klog.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 {
|
|
klog.Fatalf("Error registering PreShutdownHook %q: %v", name, err)
|
|
}
|
|
}
|
|
|
|
// RunPostStartHooks runs the PostStartHooks for the server.
|
|
func (s *GenericAPIServer) RunPostStartHooks(ctx context.Context) {
|
|
s.postStartHookLock.Lock()
|
|
defer s.postStartHookLock.Unlock()
|
|
s.postStartHooksCalled = true
|
|
|
|
context := PostStartHookContext{
|
|
LoopbackClientConfig: s.LoopbackClientConfig,
|
|
Context: ctx,
|
|
}
|
|
|
|
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 {
|
|
klog.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.HealthChecker = postStartHookHealthz{}
|
|
|
|
func (h postStartHookHealthz) Name() string {
|
|
return h.name
|
|
}
|
|
|
|
var errHookNotFinished = errors.New("not finished")
|
|
|
|
func (h postStartHookHealthz) Check(req *http.Request) error {
|
|
select {
|
|
case <-h.done:
|
|
return nil
|
|
default:
|
|
return errHookNotFinished
|
|
}
|
|
}
|