diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 660de7087..1317fa36c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -39,6 +39,18 @@ rules: - patch - update - watch +- apiGroups: + - batch + resources: + - cronjobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - batch resources: diff --git a/controllers/novaconductor_controller.go b/controllers/novaconductor_controller.go index 2d70bcefe..ab03c2be2 100644 --- a/controllers/novaconductor_controller.go +++ b/controllers/novaconductor_controller.go @@ -37,6 +37,7 @@ import ( "github.com/go-logr/logr" common "github.com/openstack-k8s-operators/lib-common/modules/common" "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/cronjob" "github.com/openstack-k8s-operators/lib-common/modules/common/env" helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" job "github.com/openstack-k8s-operators/lib-common/modules/common/job" @@ -67,6 +68,7 @@ func (r *NovaConductorReconciler) GetLogger(ctx context.Context) logr.Logger { //+kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete; //+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete; //+kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch +// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete; // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -231,6 +233,11 @@ func (r *NovaConductorReconciler) Reconcile(ctx context.Context, req ctrl.Reques return result, err } + err = r.ensureDBPurgeCronJob(ctx, h, instance, serviceAnnotations) + if err != nil { + return ctrl.Result{}, err + } + // clean up nova services from nova db should be always a last step in reconcile err = r.cleanServiceFromNovaDb(ctx, h, instance, secret, Log) if err != nil { @@ -300,6 +307,11 @@ func (r *NovaConductorReconciler) initConditions( condition.InitReason, condition.InputReadyInitMessage, ), + condition.UnknownCondition( + condition.CronJobReadyCondition, + condition.InitReason, + condition.CronJobReadyInitMessage, + ), ) instance.Status.Conditions.Init(&cl) @@ -492,6 +504,34 @@ func (r *NovaConductorReconciler) ensureDeployment( return ctrl.Result{}, nil } +func (r *NovaConductorReconciler) ensureDBPurgeCronJob( + ctx context.Context, + h *helper.Helper, + instance *novav1.NovaConductor, + annotations map[string]string, +) error { + serviceLabels := map[string]string{ + common.AppSelector: NovaConductorLabelPrefix, + } + cronDef := novaconductor.DBPurgeCronJob(instance, serviceLabels, annotations) + cronjob := cronjob.NewCronJob(cronDef, r.RequeueTimeout) + + _, err := cronjob.CreateOrPatch(ctx, h) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CronJobReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.CronJobReadyErrorMessage, + err.Error())) + return err + } + + instance.Status.Conditions.MarkTrue( + condition.CronJobReadyCondition, condition.CronJobReadyMessage) + return nil +} + func (r *NovaConductorReconciler) cleanServiceFromNovaDb( ctx context.Context, h *helper.Helper, @@ -579,6 +619,7 @@ func (r *NovaConductorReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&novav1.NovaConductor{}). Owns(&v1.StatefulSet{}). Owns(&batchv1.Job{}). + Owns(&batchv1.CronJob{}). Owns(&corev1.Secret{}). // watch the input secrets Watches( diff --git a/pkg/novaconductor/dbpurge.go b/pkg/novaconductor/dbpurge.go new file mode 100644 index 000000000..bb3998c4c --- /dev/null +++ b/pkg/novaconductor/dbpurge.go @@ -0,0 +1,88 @@ +package novaconductor + +import ( + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/openstack-k8s-operators/lib-common/modules/common/env" + novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1" + "github.com/openstack-k8s-operators/nova-operator/pkg/nova" +) + +func DBPurgeCronJob( + instance *novav1.NovaConductor, + labels map[string]string, + annotations map[string]string, +) *batchv1.CronJob { + args := []string{"-c", nova.KollaServiceCommand} + + envVars := map[string]env.Setter{} + envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") + envVars["KOLLA_BOOTSTRAP"] = env.SetValue("true") + + env := env.MergeEnvs([]corev1.EnvVar{}, envVars) + + volumes := []corev1.Volume{ + nova.GetConfigVolume(nova.GetServiceConfigSecretName(instance.Name)), + nova.GetScriptVolume(nova.GetScriptSecretName(instance.Name)), + } + volumeMounts := []corev1.VolumeMount{ + nova.GetConfigVolumeMount(), + nova.GetScriptVolumeMount(), + nova.GetKollaConfigVolumeMount("nova-conductor-dbpurge"), + } + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + + cron := &batchv1.CronJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name + "-db-purge", + Namespace: instance.Namespace, + Labels: labels, + }, + Spec: batchv1.CronJobSpec{ + Schedule: *instance.Spec.DBPurge.Schedule, + ConcurrencyPolicy: batchv1.ForbidConcurrent, + JobTemplate: batchv1.JobTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + Annotations: annotations, + }, + Spec: batchv1.JobSpec{ + Parallelism: ptr.To[int32](1), + Completions: ptr.To[int32](1), + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyOnFailure, + ServiceAccountName: instance.Spec.ServiceAccount, + Volumes: volumes, + Containers: []corev1.Container{ + { + Name: "nova-manage", + Command: []string{ + "/bin/bash", + }, + Args: args, + Image: instance.Spec.ContainerImage, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: ptr.To(nova.NovaUserID), + }, + Env: env, + VolumeMounts: volumeMounts, + }, + }, + }, + }, + }, + }, + }, + } + + return cron +} diff --git a/templates/novaconductor/bin/dbpurge.sh b/templates/novaconductor/bin/dbpurge.sh new file mode 100755 index 000000000..4b795401d --- /dev/null +++ b/templates/novaconductor/bin/dbpurge.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Copyright 2024. +# +# 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. + +set -ex + +nova-manage db purge --help +##FIXME diff --git a/templates/novaconductor/config/nova-conductor-dbpurge-config.json b/templates/novaconductor/config/nova-conductor-dbpurge-config.json new file mode 100644 index 000000000..7adf60f94 --- /dev/null +++ b/templates/novaconductor/config/nova-conductor-dbpurge-config.json @@ -0,0 +1,37 @@ +{ + "command": "/bin/dbpurge.sh", + "config_files": [ + { + "source": "/var/lib/openstack/config/nova-blank.conf", + "dest": "/etc/nova/nova.conf", + "owner": "nova", + "perm": "0600" + }, + { + "source": "/var/lib/openstack/config/01-nova.conf", + "dest": "/etc/nova/nova.conf.d/01-nova.conf", + "owner": "nova", + "perm": "0600" + }, + { + "source": "/var/lib/openstack/config/02-nova-override.conf", + "dest": "/etc/nova/nova.conf.d/02-nova-override.conf", + "owner": "nova", + "perm": "0600", + "optional": true + }, + { + "source": "/var/lib/openstack/bin/dbpurge.sh", + "dest": "/bin/", + "owner": "nova", + "perm": "0700" + } + ], + "permissions": [ + { + "path": "/var/log/nova", + "owner": "nova:nova", + "recurse": true + } + ] +}