diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 4e64e49e8..80b5c18db 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -40,6 +40,18 @@ rules: verbs: - list - watch +- apiGroups: + - batch + resources: + - cronjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - cassandra.datastax.com resources: diff --git a/controllers/k8ssandra/k8ssandracluster_controller.go b/controllers/k8ssandra/k8ssandracluster_controller.go index 478a0fb49..92a8d5346 100644 --- a/controllers/k8ssandra/k8ssandracluster_controller.go +++ b/controllers/k8ssandra/k8ssandracluster_controller.go @@ -74,6 +74,7 @@ type K8ssandraClusterReconciler struct { // +kubebuilder:rbac:groups=monitoring.coreos.com,namespace="k8ssandra",resources=servicemonitors,verbs=get;list;watch;create;update;patch;delete;deletecollection // +kubebuilder:rbac:groups=core,namespace="k8ssandra",resources=configmaps,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups="",namespace="k8ssandra",resources=events,verbs=create;patch +// +kubebuilder:rbac:groups=batch,namespace="k8ssandra",resources=cronjobs,verbs=get;list;watch;create;update;patch;delete func (r *K8ssandraClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx).WithValues("K8ssandraCluster", req.NamespacedName) diff --git a/controllers/k8ssandra/medusa_reconciler.go b/controllers/k8ssandra/medusa_reconciler.go index a4837c146..758c182d1 100644 --- a/controllers/k8ssandra/medusa_reconciler.go +++ b/controllers/k8ssandra/medusa_reconciler.go @@ -112,6 +112,16 @@ func (r *K8ssandraClusterReconciler) reconcileMedusa( logger.Info("Medusa standalone deployment is not ready yet") return result.RequeueSoon(r.DefaultDelay) } + // Create a cron job to purge Medusa backups + purgeCronJob := medusa.PurgeCronJob(dcConfig, medusaSpec, kc.SanitizedName(), namespace, logger) + recRes = reconciliation.ReconcileObject(ctx, remoteClient, r.DefaultDelay, *purgeCronJob) + switch { + case recRes.IsError(): + return recRes + case recRes.IsRequeue(): + return recRes + } + } else { logger.Info("Medusa is not enabled") } diff --git a/pkg/medusa/reconcile.go b/pkg/medusa/reconcile.go index 819de19a3..bf4de5d2d 100644 --- a/pkg/medusa/reconcile.go +++ b/pkg/medusa/reconcile.go @@ -11,6 +11,7 @@ import ( api "github.com/k8ssandra/k8ssandra-operator/apis/medusa/v1alpha1" "github.com/k8ssandra/k8ssandra-operator/pkg/images" appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/utils/pointer" @@ -442,6 +443,10 @@ func MedusaStandaloneDeploymentName(clusterName string, dcName string) string { return fmt.Sprintf("%s-%s-medusa-standalone", clusterName, dcName) } +func MedusaPurgeCronJobName(clusterName string, dcName string) string { + return fmt.Sprintf("%s-%s-medusa-purge", clusterName, dcName) +} + func StandaloneMedusaDeployment(medusaContainer corev1.Container, clusterName, dcName, namespace string, logger logr.Logger) *appsv1.Deployment { // The standalone medusa pod won't be able to resolve its own IP address using DNS entries medusaContainer.Env = append(medusaContainer.Env, corev1.EnvVar{Name: "MEDUSA_RESOLVE_IP_ADDRESSES", Value: "False"}) @@ -513,6 +518,52 @@ func StandaloneMedusaService(dcConfig *cassandra.DatacenterConfig, medusaSpec *a return medusaService } +func PurgeCronJob(dcConfig *cassandra.DatacenterConfig, medusaSpec *api.MedusaClusterTemplate, clusterName, namespace string, logger logr.Logger) *batchv1.CronJob { + purgeCronJob := &batchv1.CronJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: MedusaPurgeCronJobName(clusterName, dcConfig.SanitizedName()), + Namespace: namespace, + }, + Spec: batchv1.CronJobSpec{ + Schedule: "0 0 * * *", + Suspend: pointer.Bool(false), + SuccessfulJobsHistoryLimit: pointer.Int32(3), + FailedJobsHistoryLimit: pointer.Int32(1), + JobTemplate: batchv1.JobTemplateSpec{ + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Name: "k8ssandra-purge-backups", + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyOnFailure, + TerminationGracePeriodSeconds: pointer.Int64(30), + DNSPolicy: corev1.DNSClusterFirst, + SchedulerName: "default-scheduler", + Containers: []corev1.Container{ + { + Name: "k8ssandra-purge-backups", + Image: "bitnami/kubectl:1.17.3", + ImagePullPolicy: corev1.PullIfNotPresent, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: "File", + Command: []string{ + "/bin/bash", + "-c", + ">-", + createPurgeTaskStr(dcConfig, namespace), + }, + }, + }, + }, + }, + }, + }, + }, + } + return purgeCronJob +} + func generateMedusaProbe(configuredProbe *corev1.Probe) (*corev1.Probe, error) { // Goalesce the custom probe with the default probe, defaultProbe := defaultMedusaProbe() @@ -546,3 +597,7 @@ func defaultMedusaProbe() *corev1.Probe { return probe } + +func createPurgeTaskStr(dcConfig *cassandra.DatacenterConfig, namespace string) string { + return fmt.Sprintf("printf \"apiVersion: medusa.k8ssandra.io/v1alpha1\nkind:MedusaTask\nmetadata:\n name: purge-backups-timestamp\nnamespace: %s\nspec:\n cassandraDatacenter: %s\n operation: purge\" | sed \"s/timestamp/$(date+%%Y%%m%%d%%H%%M%%S)/g\" | kubectl apply -f -", namespace, dcConfig.CassDcName()) +}