forked from rook/rook
Fix duplicate words, incorrect articles (a/an), it's/its, and other small grammar mistakes in Go comments and user-facing messages across pkg/, cmd/, and tests/. Signed-off-by: Joshua Hoblitt <josh@hoblitt.com>
380 lines
16 KiB
Go
380 lines
16 KiB
Go
/*
|
|
Copyright 2020 The Rook Authors. All rights reserved.
|
|
|
|
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 objectrealm to manage a rook object realm.
|
|
package realm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"reflect"
|
|
"syscall"
|
|
"time"
|
|
|
|
opcontroller "github.com/rook/rook/pkg/operator/ceph/controller"
|
|
"github.com/rook/rook/pkg/operator/ceph/reporting"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/client-go/tools/events"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller"
|
|
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
|
"sigs.k8s.io/controller-runtime/pkg/handler"
|
|
"sigs.k8s.io/controller-runtime/pkg/manager"
|
|
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
|
"sigs.k8s.io/controller-runtime/pkg/source"
|
|
|
|
"github.com/coreos/pkg/capnslog"
|
|
"github.com/pkg/errors"
|
|
cephv1 "github.com/rook/rook/pkg/apis/ceph.rook.io/v1"
|
|
"github.com/rook/rook/pkg/clusterd"
|
|
cephclient "github.com/rook/rook/pkg/daemon/ceph/client"
|
|
"github.com/rook/rook/pkg/operator/ceph/cluster/mgr"
|
|
"github.com/rook/rook/pkg/operator/ceph/object"
|
|
"github.com/rook/rook/pkg/operator/k8sutil"
|
|
"github.com/rook/rook/pkg/util/exec"
|
|
"github.com/rook/rook/pkg/util/log"
|
|
v1 "k8s.io/api/core/v1"
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
const (
|
|
controllerName = "ceph-object-realm-controller"
|
|
accessKeyLength = 14
|
|
secretKeyLength = 28
|
|
)
|
|
|
|
var waitForRequeueIfRealmNotReady = reconcile.Result{Requeue: true, RequeueAfter: 10 * time.Second}
|
|
|
|
var logger = capnslog.NewPackageLogger("github.com/rook/rook", controllerName)
|
|
|
|
// Sets the type meta for the controller main object
|
|
var controllerTypeMeta = metav1.TypeMeta{
|
|
Kind: reflect.TypeFor[cephv1.CephObjectRealm]().Name(),
|
|
APIVersion: fmt.Sprintf("%s/%s", cephv1.CustomResourceGroup, cephv1.Version),
|
|
}
|
|
|
|
// ReconcileObjectRealm reconciles a ObjectRealm object
|
|
type ReconcileObjectRealm struct {
|
|
client client.Client
|
|
scheme *runtime.Scheme
|
|
context *clusterd.Context
|
|
clusterInfo *cephclient.ClusterInfo
|
|
opManagerContext context.Context
|
|
recorder events.EventRecorder
|
|
}
|
|
|
|
// Add creates a new CephObjectRealm Controller and adds it to the Manager. The Manager will set fields on the Controller
|
|
// and Start it when the Manager is Started.
|
|
func Add(mgr manager.Manager, context *clusterd.Context, opManagerContext context.Context, opConfig opcontroller.OperatorConfig) error {
|
|
return add(mgr, newReconciler(mgr, context, opManagerContext))
|
|
}
|
|
|
|
// newReconciler returns a new reconcile.Reconciler
|
|
func newReconciler(mgr manager.Manager, context *clusterd.Context, opManagerContext context.Context) reconcile.Reconciler {
|
|
return &ReconcileObjectRealm{
|
|
client: mgr.GetClient(),
|
|
scheme: mgr.GetScheme(),
|
|
context: context,
|
|
opManagerContext: opManagerContext,
|
|
recorder: mgr.GetEventRecorder("rook-" + controllerName),
|
|
}
|
|
}
|
|
|
|
func add(mgr manager.Manager, r reconcile.Reconciler) error {
|
|
// Create a new controller
|
|
c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
logger.Info("successfully started")
|
|
|
|
// Watch for changes on the CephObjectRealm CRD object
|
|
err = c.Watch(
|
|
source.Kind(
|
|
mgr.GetCache(),
|
|
&cephv1.CephObjectRealm{TypeMeta: controllerTypeMeta},
|
|
&handler.TypedEnqueueRequestForObject[*cephv1.CephObjectRealm]{},
|
|
opcontroller.WatchControllerPredicate[*cephv1.CephObjectRealm](mgr.GetScheme()),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Reconcile reads that state of the cluster for a CephObjectRealm object and makes changes based on the state read
|
|
// and what is in the CephObjectRealm.Spec
|
|
// The Controller will requeue the Request to be processed again if the returned error is non-nil or
|
|
// Result.Requeue is true, otherwise upon completion it will remove the work from the queue.
|
|
func (r *ReconcileObjectRealm) Reconcile(context context.Context, request reconcile.Request) (reconcile.Result, error) {
|
|
defer opcontroller.RecoverAndLogException()
|
|
// workaround because the rook logging mechanism is not compatible with the controller-runtime logging interface
|
|
reconcileResponse, cephObjectRealm, err := r.reconcile(request)
|
|
|
|
return reporting.ReportReconcileResult(logger, r.recorder, request, &cephObjectRealm, reconcileResponse, err)
|
|
}
|
|
|
|
func (r *ReconcileObjectRealm) reconcile(request reconcile.Request) (reconcile.Result, cephv1.CephObjectRealm, error) {
|
|
// Fetch the CephObjectRealm instance
|
|
cephObjectRealm := &cephv1.CephObjectRealm{}
|
|
err := r.client.Get(r.opManagerContext, request.NamespacedName, cephObjectRealm)
|
|
if err != nil {
|
|
if kerrors.IsNotFound(err) {
|
|
log.NamedDebug(request.NamespacedName, logger, "CephObjectRealm resource not found. Ignoring since object must be deleted")
|
|
return reconcile.Result{}, *cephObjectRealm, nil
|
|
}
|
|
// Error reading the object - requeue the request.
|
|
return reconcile.Result{}, *cephObjectRealm, errors.Wrap(err, "failed to get CephObjectRealm")
|
|
}
|
|
// update observedGeneration local variable with current generation value,
|
|
// because generation can be changed before reconcile got completed
|
|
// CR status will be updated at end of reconcile, so to reflect the reconcile has finished
|
|
observedGeneration := cephObjectRealm.ObjectMeta.Generation
|
|
|
|
// The CR was just created, initializing status fields
|
|
if cephObjectRealm.Status == nil {
|
|
r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.EmptyStatus)
|
|
}
|
|
|
|
// Make sure a CephCluster is present otherwise do nothing
|
|
cephCluster, isReadyToReconcile, cephClusterExists, reconcileResponse := opcontroller.IsReadyToReconcile(r.opManagerContext, r.client, request.NamespacedName, controllerName)
|
|
if !isReadyToReconcile {
|
|
// This handles the case where the Ceph Cluster is gone and we want to delete that CR
|
|
if !cephObjectRealm.GetDeletionTimestamp().IsZero() && !cephClusterExists {
|
|
// Return and do not requeue. Successful deletion.
|
|
return reconcile.Result{}, *cephObjectRealm, nil
|
|
}
|
|
return reconcileResponse, *cephObjectRealm, nil
|
|
}
|
|
|
|
// DELETE: the CR was deleted
|
|
if !cephObjectRealm.GetDeletionTimestamp().IsZero() {
|
|
log.NamedDebug(request.NamespacedName, logger, "deleting realm CR")
|
|
r.recorder.Eventf(cephObjectRealm, nil, v1.EventTypeNormal, string(cephv1.ReconcileStarted), string(cephv1.ReconcileStarted), "deleting CephObjectRealm %q", cephObjectRealm.Name)
|
|
|
|
// Return and do not requeue. Successful deletion.
|
|
return reconcile.Result{}, *cephObjectRealm, nil
|
|
}
|
|
|
|
// Populate clusterInfo during each reconcile
|
|
r.clusterInfo, _, _, err = opcontroller.LoadClusterInfo(r.context, r.opManagerContext, request.NamespacedName.Namespace, &cephCluster.Spec)
|
|
if err != nil {
|
|
return reconcile.Result{}, *cephObjectRealm, errors.Wrap(err, "failed to populate cluster info")
|
|
}
|
|
|
|
// validate the realm settings
|
|
err = validateRealmCR(cephObjectRealm)
|
|
if err != nil {
|
|
r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.ReconcileFailedStatus)
|
|
return reconcile.Result{}, *cephObjectRealm, errors.Wrapf(err, "invalid CephObjectRealm CR %q", cephObjectRealm.Name)
|
|
}
|
|
|
|
// Start object reconciliation, updating status for this
|
|
r.updateStatus(k8sutil.ObservedGenerationNotAvailable, request.NamespacedName, k8sutil.ReconcilingStatus)
|
|
|
|
// Create/Pull Ceph Realm
|
|
if cephObjectRealm.Spec.IsPullRealm() {
|
|
log.NamedDebug(request.NamespacedName, logger, "pull section in realm spec found")
|
|
_, err = r.pullCephRealm(cephObjectRealm)
|
|
if err != nil {
|
|
return reconcile.Result{}, *cephObjectRealm, err
|
|
}
|
|
} else {
|
|
_, err = r.createRealmKeys(cephObjectRealm)
|
|
if err != nil {
|
|
return r.setFailedStatus(k8sutil.ObservedGenerationNotAvailable, cephObjectRealm, request.NamespacedName, "failed to create keys for realm", err)
|
|
}
|
|
|
|
_, err = r.createCephRealm(cephObjectRealm)
|
|
if err != nil {
|
|
return r.setFailedStatus(k8sutil.ObservedGenerationNotAvailable, cephObjectRealm, request.NamespacedName, "failed to create ceph realm", err)
|
|
}
|
|
}
|
|
|
|
// Set the realm as default if specified and supported
|
|
if cephObjectRealm.Spec.DefaultRealm {
|
|
objCtx := object.NewContext(r.context, r.clusterInfo, cephObjectRealm.Namespace)
|
|
if err := object.SetDefaultRealm(objCtx, cephObjectRealm.Name); err != nil {
|
|
return reconcile.Result{}, *cephObjectRealm, errors.Wrapf(err,
|
|
"failed to set realm %q as default", cephObjectRealm.Name)
|
|
}
|
|
}
|
|
|
|
// update ObservedGeneration in status at the end of reconcile
|
|
// Set Ready status, we are done reconciling
|
|
r.updateStatus(observedGeneration, request.NamespacedName, k8sutil.ReadyStatus)
|
|
|
|
// Return and do not requeue
|
|
log.NamedDebug(request.NamespacedName, logger, "realm done reconciling")
|
|
return reconcile.Result{}, *cephObjectRealm, nil
|
|
}
|
|
|
|
func (r *ReconcileObjectRealm) pullCephRealm(realm *cephv1.CephObjectRealm) (reconcile.Result, error) {
|
|
nsName := opcontroller.NsName(realm.Namespace, realm.Name)
|
|
realmArg := fmt.Sprintf("--rgw-realm=%s", realm.Name)
|
|
urlArg := fmt.Sprintf("--url=%s", realm.Spec.Pull.Endpoint)
|
|
log.NamedDebug(nsName, logger, "getting keys to pull realm for CephObjectRealm %q", realm.Name)
|
|
accessKeyArg, secretKeyArg, err := object.GetRealmKeyArgs(r.opManagerContext, r.context, realm.Name, realm.Namespace)
|
|
if err != nil {
|
|
if kerrors.IsNotFound(err) {
|
|
return waitForRequeueIfRealmNotReady, err
|
|
}
|
|
return waitForRequeueIfRealmNotReady, errors.Wrap(err, "failed to get keys for realm")
|
|
}
|
|
log.NamedDebug(nsName, logger, "keys found to pull realm for CephObjectRealm %q, getting ready to pull from endpoint %q", realm.Name, realm.Spec.Pull.Endpoint)
|
|
|
|
objContext := object.NewContext(r.context, r.clusterInfo, realm.Name)
|
|
output, err := object.RunAdminCommandNoMultisite(objContext, false, "realm", "pull", realmArg, urlArg, accessKeyArg, secretKeyArg)
|
|
if err != nil {
|
|
return waitForRequeueIfRealmNotReady, errors.Wrapf(err, "realm pull failed for reason: %v", output)
|
|
}
|
|
log.NamedDebug(nsName, logger, "realm pull for %q from endpoint %q succeeded", realm.Name, realm.Spec.Pull.Endpoint)
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
func (r *ReconcileObjectRealm) createCephRealm(realm *cephv1.CephObjectRealm) (reconcile.Result, error) {
|
|
nsName := opcontroller.NsName(realm.Namespace, realm.Name)
|
|
realmArg := fmt.Sprintf("--rgw-realm=%s", realm.Name)
|
|
objContext := object.NewContext(r.context, r.clusterInfo, realm.Namespace)
|
|
|
|
_, err := object.RunAdminCommandNoMultisite(objContext, true, "realm", "get", realmArg)
|
|
if err != nil {
|
|
if code, ok := exec.ExitStatus(err); ok && code == int(syscall.ENOENT) {
|
|
log.NamedDebug(nsName, logger, "ceph realm not found, running `radosgw-admin realm create`")
|
|
_, err := object.RunAdminCommandNoMultisite(objContext, false, "realm", "create", realmArg)
|
|
if err != nil {
|
|
return reconcile.Result{}, errors.Wrapf(err, "failed to create ceph realm %s", realm.Name)
|
|
}
|
|
log.NamedDebug(nsName, logger, "created ceph realm %q", realm.Name)
|
|
} else {
|
|
return reconcile.Result{}, errors.Wrapf(err, "radosgw-admin realm get failed with code %d", code)
|
|
}
|
|
}
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
func (r *ReconcileObjectRealm) createRealmKeys(realm *cephv1.CephObjectRealm) (reconcile.Result, error) {
|
|
nsName := opcontroller.NsName(realm.Namespace, realm.Name)
|
|
realmName := types.NamespacedName{Namespace: realm.Namespace, Name: realm.Name}
|
|
log.NamedDebug(nsName, logger, "generating access and secret keys for new realm")
|
|
secretName := realm.Name + "-keys"
|
|
|
|
// Check if the secret exists first, and check that it has the access information needed.
|
|
secret, err := object.GetRealmKeySecret(r.opManagerContext, r.context, realmName)
|
|
if err == nil {
|
|
// secret exists, now verify access info. We don't need the args, but we do want to get the
|
|
// error if the args can't be built
|
|
_, _, err := object.GetRealmKeyArgsFromSecret(secret, realmName)
|
|
if err != nil {
|
|
return reconcile.Result{}, errors.Wrapf(err, "failed to ensure secret keys for CephObjectRealm %q", realmName.String())
|
|
}
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
// The realm's secret key and access key are randomly generated and then encoded to base64.
|
|
// The encoded values are used verbatim as the S3 keys of the realm's system user, so the
|
|
// encoding alphabet must be URL-safe: an access key containing '/' breaks AWS SigV4
|
|
// credential scope parsing ("<access-key>/<date>/<region>/...") and makes
|
|
// "radosgw-admin realm pull" against this realm fail with "(22) Invalid argument".
|
|
accessKey, err := mgr.GeneratePassword(accessKeyLength, mgr.AccessKey)
|
|
if err != nil {
|
|
return reconcile.Result{}, errors.Wrapf(err, "access key failed to generate")
|
|
}
|
|
accessKey = base64.RawURLEncoding.EncodeToString([]byte(accessKey))
|
|
|
|
secretKey, err := mgr.GeneratePassword(secretKeyLength, mgr.DefaultKey)
|
|
if err != nil {
|
|
return reconcile.Result{}, errors.Wrapf(err, "failed to generate secret key")
|
|
}
|
|
secretKey = base64.RawURLEncoding.EncodeToString([]byte(secretKey))
|
|
|
|
log.NamedDebug(nsName, logger, "creating secrets for new realm %q", realm.Name)
|
|
|
|
secrets := map[string][]byte{
|
|
object.AccessKeyName: []byte(accessKey),
|
|
object.SecretKeyName: []byte(secretKey),
|
|
}
|
|
|
|
secret = &v1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: secretName,
|
|
Namespace: realm.Namespace,
|
|
},
|
|
Data: secrets,
|
|
Type: k8sutil.RookType,
|
|
}
|
|
err = controllerutil.SetControllerReference(realm, secret, r.scheme)
|
|
if err != nil {
|
|
return reconcile.Result{}, errors.Wrapf(err, "failed to set owner reference of rgw secret %q", secret.Name)
|
|
}
|
|
|
|
if _, err = r.context.Clientset.CoreV1().Secrets(realm.Namespace).Create(r.opManagerContext, secret, metav1.CreateOptions{}); err != nil {
|
|
return reconcile.Result{}, errors.Wrap(err, "failed to save rgw secrets")
|
|
}
|
|
log.NamedInfo(nsName, logger, "secret keys have been created for realm %q in secret %q", realm.Name, secret.Name)
|
|
|
|
return reconcile.Result{}, nil
|
|
}
|
|
|
|
// validateRealmCR validates the realm arguments
|
|
func validateRealmCR(u *cephv1.CephObjectRealm) error {
|
|
if u.Name == "" {
|
|
return errors.New("missing name")
|
|
}
|
|
if u.Namespace == "" {
|
|
return errors.New("missing namespace")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *ReconcileObjectRealm) setFailedStatus(observedGeneration int64, cephObjectRealm *cephv1.CephObjectRealm, name types.NamespacedName, errMessage string, err error) (reconcile.Result, cephv1.CephObjectRealm, error) {
|
|
r.updateStatus(observedGeneration, name, k8sutil.ReconcileFailedStatus)
|
|
return reconcile.Result{}, *cephObjectRealm, errors.Wrapf(err, "%s", errMessage)
|
|
}
|
|
|
|
// updateStatus updates a realm with a given status
|
|
func (r *ReconcileObjectRealm) updateStatus(observedGeneration int64, name types.NamespacedName, status string) {
|
|
objectRealm := &cephv1.CephObjectRealm{}
|
|
if err := r.client.Get(r.opManagerContext, name, objectRealm); err != nil {
|
|
if kerrors.IsNotFound(err) {
|
|
log.NamedDebug(name, logger, "CephObjectRealm resource not found. Ignoring since object must be deleted")
|
|
return
|
|
}
|
|
log.NamedWarning(name, logger, "failed to retrieve object realm to update status to %q. %v", status, err)
|
|
return
|
|
}
|
|
if objectRealm.Status == nil {
|
|
objectRealm.Status = &cephv1.Status{}
|
|
}
|
|
|
|
objectRealm.Status.Phase = status
|
|
if observedGeneration != k8sutil.ObservedGenerationNotAvailable {
|
|
objectRealm.Status.ObservedGeneration = observedGeneration
|
|
}
|
|
if err := reporting.UpdateStatus(r.client, objectRealm); err != nil {
|
|
log.NamedError(name, logger, "failed to set object realm status to %q. %v", status, err)
|
|
return
|
|
}
|
|
log.NamedDebug(name, logger, "object realm status updated to %q", status)
|
|
}
|