Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update #1

Merged
merged 12 commits into from
Jul 15, 2024
Merged
2 changes: 1 addition & 1 deletion charts/k8up/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ keywords:
- backup
- operator
- restic
version: 4.7.0
version: 4.8.0
sources:
- https://github.com/k8up-io/k8up
maintainers:
Expand Down
4 changes: 2 additions & 2 deletions charts/k8up/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# k8up

![Version: 4.7.0](https://img.shields.io/badge/Version-4.7.0-informational?style=flat-square)
![Version: 4.8.0](https://img.shields.io/badge/Version-4.8.0-informational?style=flat-square)

Kubernetes and OpenShift Backup Operator based on restic

Expand All @@ -13,7 +13,7 @@ helm repo add k8up-io https://k8up-io.github.io/k8up
helm install k8up k8up-io/k8up
```
```bash
kubectl apply -f https://github.com/k8up-io/k8up/releases/download/k8up-4.7.0/k8up-crd.yaml
kubectl apply -f https://github.com/k8up-io/k8up/releases/download/k8up-4.8.0/k8up-crd.yaml
```

<!---
Expand Down
4 changes: 4 additions & 0 deletions charts/k8up/templates/cleanup-hook.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ spec:
spec:
restartPolicy: Never
serviceAccountName: cleanup-service-account
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: "{{ .Release.Name }}-cleanup"
image: "{{ include "cleanupImage" . }}"
Expand All @@ -99,3 +101,5 @@ spec:
kubectl -n "$ns" delete rolebinding pod-executor-namespaced --ignore-not-found=true
kubectl -n "$ns" delete role pod-executor --ignore-not-found=true
done
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
2 changes: 1 addition & 1 deletion charts/k8up/templates/grafana-dashboard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ metadata:
namespace: {{ default .Release.Namespace .Values.metrics.grafanaDashboard.namespace }}
labels:
{{- include "k8up.labels" . | nindent 4 }}
{{- with .Values.metrics.prometheusRule.additionalLabels }}
{{- with .Values.metrics.grafanaDashboard.additionalLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
data:
Expand Down
78 changes: 75 additions & 3 deletions operator/backupcontroller/controller_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
Expand Down Expand Up @@ -312,7 +313,9 @@ func (ts *BackupTestSuite) Test_GivenBackupAndMountedRWOPVCOnOneNode_ExpectBacku

pvc1.Status.Phase = corev1.ClaimBound
pvc2.Status.Phase = corev1.ClaimBound
ts.UpdateStatus(pvc1, pvc2)
pod1.Status.Phase = corev1.PodRunning
pod2.Status.Phase = corev1.PodRunning
ts.UpdateStatus(pvc1, pvc2, pod1, pod2)

result := ts.whenReconciling(ts.BackupResource)
ts.Assert().GreaterOrEqual(result.RequeueAfter, 30*time.Second)
Expand Down Expand Up @@ -359,7 +362,9 @@ func (ts *BackupTestSuite) Test_GivenBackupAndMountedRWOPVCOnTwoNodes_ExpectBack

pvc1.Status.Phase = corev1.ClaimBound
pvc2.Status.Phase = corev1.ClaimBound
ts.UpdateStatus(pvc1, pvc2)
pod1.Status.Phase = corev1.PodRunning
pod2.Status.Phase = corev1.PodRunning
ts.UpdateStatus(pvc1, pvc2, pod1, pod2)

result := ts.whenReconciling(ts.BackupResource)
ts.Assert().GreaterOrEqual(result.RequeueAfter, 30*time.Second)
Expand Down Expand Up @@ -395,7 +400,9 @@ func (ts *BackupTestSuite) Test_GivenBackupAndMountedRWOPVCOnOneNodeWithFinished
ts.EnsureResources(ts.BackupResource, pvc1, pod1, pod2)

pvc1.Status.Phase = corev1.ClaimBound
ts.UpdateStatus(pvc1)
pod1.Status.Phase = corev1.PodRunning
pod2.Status.Phase = corev1.PodRunning
ts.UpdateStatus(pvc1, pod1, pod2)

result := ts.whenReconciling(ts.BackupResource)
ts.Assert().GreaterOrEqual(result.RequeueAfter, 30*time.Second)
Expand Down Expand Up @@ -451,6 +458,71 @@ func (ts *BackupTestSuite) Test_GivenBackupAndUnmountedRWOPVCOnTwoNodes_ExpectBa
ts.assertJobSpecs(&job2, nodeNamePv2, []corev1.Volume{volumePvc2}, nil, []string{})
}

func (ts *BackupTestSuite) Test_GivenBackupAndUnmountedRWOPVC_ExpectBackup() {
pvc1 := ts.newPvc("test-pvc-without-node-affinity", corev1.ReadWriteOnce)
pv1 := &corev1.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "test-pvc-without-node-affinity",
},
Spec: corev1.PersistentVolumeSpec{
AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
Capacity: map[corev1.ResourceName]resource.Quantity{
corev1.ResourceStorage: resource.MustParse("1Mi"),
},
PersistentVolumeSource: corev1.PersistentVolumeSource{
HostPath: &corev1.HostPathVolumeSource{Path: "/tmp/integration-tests"},
},
},
}
volumePvc1 := corev1.Volume{
Name: "test-pvc-without-node-affinity",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvc1.Name,
},
},
}
ts.EnsureResources(ts.BackupResource, pvc1, pv1)

pvc1.Status.Phase = corev1.ClaimBound
ts.UpdateStatus(pvc1)

ts.whenReconciling(ts.BackupResource)
job := ts.expectABackupJob()

ts.Assert().Nil(job.Spec.Template.Spec.NodeSelector)
ts.Assert().Equal(volumePvc1.VolumeSource.PersistentVolumeClaim.ClaimName, job.Spec.Template.Spec.Volumes[0].VolumeSource.PersistentVolumeClaim.ClaimName)
}

func (ts *BackupTestSuite) Test_GivenBackupAndRWOPVCWithFinishedPod_ExpectFinishedPodToBeIgnored() {
pvc1 := ts.newPvc("test-pvc1", corev1.ReadWriteOnce)
nodeNamePod1 := "worker"
nodeNamePod2 := "control-plane"
volumePvc1 := corev1.Volume{
Name: "test-pvc1",
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvc1.Name,
},
},
}
tolerations := make([]corev1.Toleration, 0)
pod1 := ts.newPod("test-pod1", nodeNamePod1, tolerations, []corev1.Volume{volumePvc1})
pod2 := ts.newPod("test-pod2", nodeNamePod2, tolerations, []corev1.Volume{volumePvc1})

ts.EnsureResources(ts.BackupResource, pvc1, pod1, pod2)

pvc1.Status.Phase = corev1.ClaimBound
pod1.Status.Phase = corev1.PodRunning
pod2.Status.Phase = corev1.PodSucceeded
ts.UpdateStatus(pvc1, pod1, pod2)

ts.whenReconciling(ts.BackupResource)
job := ts.expectABackupJob()

ts.assertJobSpecs(job, nodeNamePod1, []corev1.Volume{volumePvc1}, tolerations, []string{pod1.Name})
}

func (ts *BackupTestSuite) assertCondition(conditions []metav1.Condition, condType k8upv1.ConditionType, reason k8upv1.ConditionReason, status metav1.ConditionStatus) {
cond := meta.FindStatusCondition(conditions, condType.String())
ts.Require().NotNil(cond, "condition of type %s missing", condType)
Expand Down
11 changes: 7 additions & 4 deletions operator/backupcontroller/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func (b *BackupExecutor) listAndFilterPVCs(ctx context.Context, annotation strin
return nil, fmt.Errorf("list pods: %w", err)
}
for _, pod := range pods.Items {
if pod.Status.Phase != corev1.PodRunning {
log.V(1).Info("Ignoring Pod which is not running", "pod", pod.GetName())
continue
}
for _, volume := range pod.Spec.Volumes {
if volume.PersistentVolumeClaim != nil {
pvcPodMap[volume.PersistentVolumeClaim.ClaimName] = pod
Expand All @@ -87,15 +91,15 @@ func (b *BackupExecutor) listAndFilterPVCs(ctx context.Context, annotation strin

for _, pvc := range claimlist.Items {
if pvc.Status.Phase != corev1.ClaimBound {
log.Info("PVC is not bound", "pvc", pvc.GetName())
log.Info("PVC is not bound, skipping PVC", "pvc", pvc.GetName())
continue
}

backupAnnotation, hasBackupAnnotation := pvc.GetAnnotations()[annotation]

isRWO := containsAccessMode(pvc.Spec.AccessModes, corev1.ReadWriteOnce)
if !containsAccessMode(pvc.Spec.AccessModes, corev1.ReadWriteMany) && !isRWO && !hasBackupAnnotation {
log.Info("PVC is neither RWX nor RWO and has no backup annotation", "pvc", pvc.GetName())
log.Info("PVC is neither RWX nor RWO and has no backup annotation, skipping PVC", "pvc", pvc.GetName())
continue
}

Expand Down Expand Up @@ -139,8 +143,7 @@ func (b *BackupExecutor) listAndFilterPVCs(ctx context.Context, annotation strin

bi.node = findNode(pv, pvc)
if bi.node == "" {
log.Info("RWO PVC not bound and no PV node affinity set, skipping", "pvc", pvc.GetName(), "affinity", pv.Spec.NodeAffinity)
continue
log.Info("RWO PVC not bound and no PV node affinity set, adding", "pvc", pvc.GetName(), "affinity", pv.Spec.NodeAffinity)
}
log.V(1).Info("node found in PV or PVC", "pvc", pvc.GetName(), "node", bi.node)
} else {
Expand Down
Loading