polardbxoperator/pkg/k8s/control/context.go

306 lines
8.2 KiB
Go

/*
Copyright 2021 Alibaba Group Holding Limited.
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 control
import (
"bytes"
"context"
"errors"
"io"
"strings"
"time"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
k8shelper "github.com/alibaba/polardbx-operator/pkg/k8s/helper"
)
type ExecOptions struct {
Logger logr.Logger
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Timeout time.Duration
}
func (opts *ExecOptions) setDefaults() {
// Set default timeout to 5s if not specified.
if opts.Timeout == 0 {
opts.Timeout = 5 * time.Second
}
// Branch stdin/stdout/stderr are all null, set one.
if opts.Stdin == nil && opts.Stdout == nil && opts.Stderr == nil {
opts.Stdout = &bytes.Buffer{}
}
// Logging
if opts.Logger == nil {
opts.Logger = log.NullLogger{}
}
}
type ReconcileRemoteCommandHelper interface {
ExecuteCommandOn(pod *corev1.Pod, container string, command []string, opts ExecOptions) error
}
type ReconcileRequestHelper interface {
// Name is a helper method that returns the name of current reconcile object.
Name() string
// Namespace is a helper method that returns the namespace of current reconcile object.
Namespace() string
// NameInto is a helper method that returns a string like "f({name})" where name is
// the name of current reconcile object.
NameInto(f func(name string) string) string
// NamespacedNameInto is a helper method that returns a namespaced name like "{namespace}/f({name})"
// where "{namespace}/{name}" is of the current reconcile object.
NamespacedNameInto(f func(name string) string) types.NamespacedName
}
type ReconcileCustomResourceDefinitionHelper interface {
// GetCrd queries the api server and get the target custom resource definition if found.
GetCrd(apiVersion, kind string) (*apiextensionsv1.CustomResourceDefinition, error)
}
type ReconcileOptions interface {
Debug() bool
}
type ReconcileControl interface {
ForceRequeueAfter() time.Duration
ResetForceRequeueAfter(d time.Duration)
}
// ReconcileContext declares the context for reconciliation.
type ReconcileContext interface {
ReconcileOptions
ReconcileControl
ReconcileRemoteCommandHelper
ReconcileRequestHelper
ReconcileCustomResourceDefinitionHelper
// Client returns a API client of k8s.
Client() client.Client
// RestConfig returns the rest config used by client.
RestConfig() *rest.Config
// ClientSet returns the client set.
ClientSet() *kubernetes.Clientset
// Scheme returns the currently using scheme.
Scheme() *runtime.Scheme
// Context returns the current reconcile context.
Context() context.Context
// Request returns the current reconcile request.
Request() reconcile.Request
// Close closes the context to avoid resource leaks.
Close() error
}
type BaseReconcileContext struct {
client client.Client
restConfig *rest.Config
clientSet *kubernetes.Clientset
scheme *runtime.Scheme
context context.Context
request reconcile.Request
forceRequeueAfter time.Duration
customResourceDefinitions *apiextensionsv1.CustomResourceDefinitionList
}
func (rc *BaseReconcileContext) Debug() bool {
return false
}
func (rc *BaseReconcileContext) ExecuteCommandOn(pod *corev1.Pod, container string, command []string, opts ExecOptions) error {
if k8shelper.GetContainerFromPod(pod, container) == nil {
return errors.New("container " + container + " not found in pod " + pod.Name)
}
// Set defaults.
opts.setDefaults()
logger := opts.Logger
logger.Info("Executing command", "pod", pod.Name, "container", container, "command", command, "timeout", opts.Timeout)
// Start execute
req := rc.clientSet.
CoreV1().
RESTClient().
Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("exec").
Timeout(opts.Timeout)
req.VersionedParams(&corev1.PodExecOptions{
Container: container,
Command: command,
Stdin: opts.Stdin != nil,
Stdout: opts.Stdout != nil,
Stderr: opts.Stderr != nil,
TTY: false,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(rc.restConfig, "POST", req.URL())
if err != nil {
return err
}
ctx, cancelFn := rc.context, func() {}
if opts.Timeout > 0 {
ctx, cancelFn = context.WithTimeout(ctx, opts.Timeout)
}
defer cancelFn()
return exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdin: opts.Stdin,
Stdout: opts.Stdout,
Stderr: opts.Stderr,
})
}
func (rc *BaseReconcileContext) Name() string {
return rc.request.Name
}
func (rc *BaseReconcileContext) Namespace() string {
return rc.request.Namespace
}
func (rc *BaseReconcileContext) NameInto(f func(name string) string) string {
return f(rc.request.Name)
}
func (rc *BaseReconcileContext) NamespacedNameInto(f func(name string) string) types.NamespacedName {
return types.NamespacedName{
Namespace: rc.request.Namespace,
Name: f(rc.request.Name),
}
}
func (rc *BaseReconcileContext) GetCrd(apiVersion, kind string) (*apiextensionsv1.CustomResourceDefinition, error) {
groupVersion := strings.Split(apiVersion, "/")
if len(groupVersion) != 2 {
return nil, errors.New("invalid api version")
}
group, version := groupVersion[0], groupVersion[1]
// Branch not cached, query from api server.
if rc.customResourceDefinitions == nil {
var customResourceDefinitions apiextensionsv1.CustomResourceDefinitionList
if err := rc.Client().List(rc.Context(), &customResourceDefinitions); err != nil {
return nil, err
}
rc.customResourceDefinitions = &customResourceDefinitions
}
// Scan the CRD list.
for _, crd := range rc.customResourceDefinitions.Items {
if crd.Spec.Group == group && crd.Spec.Names.Kind == kind {
for _, ver := range crd.Spec.Versions {
if ver.Name == version {
return crd.DeepCopy(), nil
}
}
}
}
return nil, nil
}
func (rc *BaseReconcileContext) Client() client.Client {
return rc.client
}
func (rc *BaseReconcileContext) RestConfig() *rest.Config {
return rc.restConfig
}
func (rc *BaseReconcileContext) ClientSet() *kubernetes.Clientset {
return rc.clientSet
}
func (rc *BaseReconcileContext) Scheme() *runtime.Scheme {
return rc.scheme
}
func (rc *BaseReconcileContext) Context() context.Context {
return rc.context
}
func (rc *BaseReconcileContext) Request() reconcile.Request {
return rc.request
}
func (rc *BaseReconcileContext) Close() error {
return nil
}
func (rc *BaseReconcileContext) ForceRequeueAfter() time.Duration {
return rc.forceRequeueAfter
}
func (rc *BaseReconcileContext) ResetForceRequeueAfter(d time.Duration) {
rc.forceRequeueAfter = d
}
func (rc *BaseReconcileContext) shallowCopy() *BaseReconcileContext {
shallowCopy := *rc
return &shallowCopy
}
func NewBaseReconcileContextFrom(base *BaseReconcileContext, context context.Context, request reconcile.Request) *BaseReconcileContext {
rc := *base.shallowCopy()
rc.forceRequeueAfter = 0
rc.request = request
rc.context = context
rc.customResourceDefinitions = nil
return &rc
}
func NewBaseReconcileContext(client client.Client, restConfig *rest.Config, clientSet *kubernetes.Clientset,
scheme *runtime.Scheme, context context.Context, request reconcile.Request) *BaseReconcileContext {
return &BaseReconcileContext{
client: client,
restConfig: restConfig,
clientSet: clientSet,
scheme: scheme,
context: context,
request: request,
customResourceDefinitions: nil,
}
}