Skip to content

Commit

Permalink
Create DB purge CronJob
Browse files Browse the repository at this point in the history
  • Loading branch information
gibizer committed Feb 27, 2024
1 parent 873cec7 commit c5f5523
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 0 deletions.
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- batch
resources:
- cronjobs
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- batch
resources:
Expand Down
41 changes: 41 additions & 0 deletions controllers/novaconductor_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1"
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"
Expand Down Expand Up @@ -73,6 +74,7 @@ func (r *NovaConductorReconciler) GetLogger(ctx context.Context) logr.Logger {
//+kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch
//+kubebuilder:rbac:groups=memcached.openstack.org,resources=memcacheds,verbs=get;list;watch;update;
//+kubebuilder:rbac:groups=memcached.openstack.org,resources=memcacheds/finalizers,verbs=update
// +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.
Expand Down Expand Up @@ -274,6 +276,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 {
Expand Down Expand Up @@ -348,6 +355,11 @@ func (r *NovaConductorReconciler) initConditions(
condition.InitReason,
condition.MemcachedReadyInitMessage,
),
condition.UnknownCondition(
condition.CronJobReadyCondition,
condition.InitReason,
condition.CronJobReadyInitMessage,
),
)

instance.Status.Conditions.Init(&cl)
Expand Down Expand Up @@ -553,6 +565,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,
Expand Down Expand Up @@ -700,6 +740,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(
Expand Down
93 changes: 93 additions & 0 deletions pkg/novaconductor/dbpurge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package novaconductor

import (
"fmt"

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")

envVars["ARCHIVE_AGE"] = env.SetValue(fmt.Sprintf("%d", *instance.Spec.DBPurge.ArchiveAge))
envVars["PURGE_AGE"] = env.SetValue(fmt.Sprintf("%d", *instance.Spec.DBPurge.PurgeAge))

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
}
38 changes: 38 additions & 0 deletions templates/novaconductor/bin/dbpurge.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/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 -x
export ARCHIVE_AGE=${ARCHIVE_AGE:?"Please specify ARCHIVE_AGE variable."}
export PURGE_AGE=${PURGE_AGE:?"Please specify PURGE_AGE variable."}
archive_before=$(date --date="${ARCHIVE_AGE} day ago" +%Y-%m-%d)
purge_before=$(date --date="${PURGE_AGE} day ago" +%Y-%m-%d)

nova-manage db archive_deleted_rows --verbose --until-complete --task-log --before "${archive_before}"
ret=$?
# 0 means no error and nothing is archived
# 1 means no error and someting is archived
if [ $ret -gt "1" ]; then
exit $ret
fi

nova-manage db purge --verbose --before "${purge_before}"
ret=$?
# 0 means no error and something is deleted
# 3 means no error and nothing is deleted
if [[ $ret -eq 1 || $ret -eq 2 || $ret -gt 3 ]]; then
exit $ret
fi

exit 0
37 changes: 37 additions & 0 deletions templates/novaconductor/config/nova-conductor-dbpurge-config.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}
15 changes: 15 additions & 0 deletions test/functional/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/go-logr/logr"
. "github.com/onsi/gomega"
"golang.org/x/exp/maps"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
k8s_errors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -553,6 +554,7 @@ type CellNames struct {
NovaComputeStatefulSetName types.NamespacedName
NovaComputeConfigDataName types.NamespacedName
HostDiscoveryJobName types.NamespacedName
DBPurgeCronJobName types.NamespacedName
}

func GetCellNames(novaName types.NamespacedName, cell string) CellNames {
Expand Down Expand Up @@ -641,6 +643,10 @@ func GetCellNames(novaName types.NamespacedName, cell string) CellNames {
Namespace: novaName.Namespace,
Name: cellName.Name + "-compute-config",
},
DBPurgeCronJobName: types.NamespacedName{
Namespace: novaName.Namespace,
Name: cellConductor.Name + "-db-purge",
},
}

if cell == "cell0" {
Expand Down Expand Up @@ -980,3 +986,12 @@ func AssertComputeDoesNotExist(name types.NamespacedName) {
g.Expect(k8s_errors.IsNotFound(err)).To(BeTrue())
}, timeout, interval).Should(Succeed())
}

func GetCronJob(name types.NamespacedName) *batchv1.CronJob {
cron := &batchv1.CronJob{}
Eventually(func(g Gomega) {
g.Expect(k8sClient.Get(ctx, name, cron)).Should(Succeed())
}, timeout, interval).Should(Succeed())

return cron
}
23 changes: 23 additions & 0 deletions test/functional/nova_reconfiguration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -678,4 +678,27 @@ var _ = Describe("Nova reconfiguration", func() {
g.Expect(configData).To(ContainSubstring("memcached_servers=inet_new"))
}, timeout, interval).Should(Succeed())
})

It("reconfigures DB Pruge job", func() {
Eventually(func(g Gomega) {
nova := GetNova(novaNames.NovaName)
cell0 := nova.Spec.CellTemplates["cell0"]
(&cell0).DBPurge.Schedule = ptr.To("3 0 * * *")
(&cell0).DBPurge.ArchiveAge = ptr.To(33)
(&cell0).DBPurge.PurgeAge = ptr.To(99)

nova.Spec.CellTemplates["cell0"] = cell0

g.Expect(k8sClient.Update(ctx, nova)).To(Succeed())
}, timeout, interval).Should(Succeed())

Eventually(func(g Gomega) {
cron := GetCronJob(cell0.DBPurgeCronJobName)

g.Expect(cron.Spec.Schedule).To(Equal("3 0 * * *"))
jobEnv := cron.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Env
g.Expect(GetEnvVarValue(jobEnv, "ARCHIVE_AGE", "")).To(Equal("33"))
g.Expect(GetEnvVarValue(jobEnv, "PURGE_AGE", "")).To(Equal("99"))
}, timeout, interval).Should(Succeed())
})
})
25 changes: 25 additions & 0 deletions test/functional/novaconductor_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ var _ = Describe("NovaConductor controller", func() {
scriptData := string(scriptMap.Data["dbsync.sh"])
Expect(scriptData).Should(ContainSubstring("nova-manage db sync"))
Expect(scriptData).Should(ContainSubstring("nova-manage api_db sync"))
Expect(scriptMap.Data).Should(HaveKey("dbpurge.sh"))
scriptData = string(scriptMap.Data["dbpurge.sh"])
Expect(scriptData).Should(ContainSubstring("nova-manage db archive_deleted_rows"))
Expect(scriptData).Should(ContainSubstring("nova-manage db purge"))
})

It("stored the input hash in the Status", func() {
Expand Down Expand Up @@ -340,6 +344,27 @@ var _ = Describe("NovaConductor controller", func() {
conductor := GetNovaConductor(cell0.ConductorName)
Expect(conductor.Status.ReadyCount).To(BeNumerically(">", 0))
})

It("creates the DB purge CronJob", func() {
th.SimulateStatefulSetReplicaReady(cell0.ConductorStatefulSetName)

conductor := GetNovaConductor(cell0.ConductorName)
cron := GetCronJob(cell0.DBPurgeCronJobName)

Expect(cron.Spec.Schedule).To(Equal(*conductor.Spec.DBPurge.Schedule))
jobEnv := cron.Spec.JobTemplate.Spec.Template.Spec.Containers[0].Env
Expect(GetEnvVarValue(jobEnv, "ARCHIVE_AGE", "")).To(
Equal(fmt.Sprintf("%d", *conductor.Spec.DBPurge.ArchiveAge)))
Expect(GetEnvVarValue(jobEnv, "PURGE_AGE", "")).To(
Equal(fmt.Sprintf("%d", *conductor.Spec.DBPurge.PurgeAge)))

th.ExpectCondition(
cell0.ConductorName,
ConditionGetterFunc(NovaConductorConditionGetter),
condition.CronJobReadyCondition,
corev1.ConditionTrue,
)
})
})
})

Expand Down

0 comments on commit c5f5523

Please sign in to comment.