Skip to content

Commit

Permalink
Optionally provide ordinal for specific pod snapshotting
Browse files Browse the repository at this point in the history
  • Loading branch information
agouin committed Oct 14, 2023
1 parent f240661 commit 3d91760
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 1 deletion.
5 changes: 5 additions & 0 deletions api/v1alpha1/scheduledvolumesnapshot_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ type LocalFullNodeRef struct {
// DEPRECATED: CosmosFullNode must be in the same namespace as the ScheduledVolumeSnapshot. This field is ignored.
// +optional
Namespace string `json:"namespace"`

// Index of the pod to snapshot. If not provided, will do any pod in the CosmosFullNode.
// Useful when snapshots are local to the same node as the pod, requiring snapshots across multiple pods/nodes.
// +optional
Ordinal *int32 `json:"index"`
}

// ScheduledVolumeSnapshotStatus defines the observed state of ScheduledVolumeSnapshot
Expand Down
18 changes: 17 additions & 1 deletion internal/volsnapshot/vol_snapshot_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,23 @@ func (control VolumeSnapshotControl) FindCandidate(ctx context.Context, crd *cos
return Candidate{}, fmt.Errorf("%d or more pods must be in-sync to prevent downtime, found %d in-sync", minAvail, availCount)
}

pod := synced[0]
var pod *corev1.Pod

if crd.Spec.FullNodeRef.Ordinal != nil {
podIndex := *crd.Spec.FullNodeRef.Ordinal
podIndexStr := fmt.Sprintf("%d", podIndex)
for _, p := range synced {
if p.Annotations["app.kubernetes.io/ordinal"] == podIndexStr {
pod = p
break
}
}
if pod == nil {
return Candidate{}, fmt.Errorf("in-sync pod with index %d not found", podIndex)
}
} else {
pod = synced[0]
}

return Candidate{
PodLabels: pod.Labels,
Expand Down
69 changes: 69 additions & 0 deletions internal/volsnapshot/vol_snapshot_control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,75 @@ func TestVolumeSnapshotControl_FindCandidate(t *testing.T) {
require.Equal(t, candidate.Labels, got.PodLabels)
})

t.Run("happy path with index", func(t *testing.T) {
pods := make([]corev1.Pod, 3)
for i := range pods {
pods[i].Status.Conditions = []corev1.PodCondition{readyCondition}
}
var mClient mockPodClient
mClient.Items = pods

var fullnodeCRD cosmosv1.CosmosFullNode
fullnodeCRD.Name = fullNodeName
// Purposefully using PodBuilder to cross-test any breaking changes in PodBuilder which affects
// finding the PVC name.
candidate, err := fullnode.NewPodBuilder(&fullnodeCRD).WithOrdinal(1).Build()
require.NoError(t, err)

candidate.Annotations["app.kubernetes.io/ordinal"] = "1"

control := NewVolumeSnapshotControl(&mClient, mockPodFilter{
SyncedPodsFn: func(ctx context.Context, controller client.ObjectKey) []*corev1.Pod {
require.Equal(t, namespace, controller.Namespace)
require.Equal(t, fullNodeName, controller.Name)
return []*corev1.Pod{candidate, new(corev1.Pod), new(corev1.Pod)}
},
})

indexCRD := crd.DeepCopy()
index := int32(1)
indexCRD.Spec.FullNodeRef.Ordinal = &index

got, err := control.FindCandidate(ctx, indexCRD)
require.NoError(t, err)

require.Equal(t, "cosmoshub-1", got.PodName)
require.Equal(t, "pvc-cosmoshub-1", got.PVCName)
require.NotEmpty(t, got.PodLabels)
require.Equal(t, candidate.Labels, got.PodLabels)
})

t.Run("index not available", func(t *testing.T) {
pods := make([]corev1.Pod, 3)
for i := range pods {
pods[i].Status.Conditions = []corev1.PodCondition{readyCondition}
}
var mClient mockPodClient
mClient.Items = pods

var fullnodeCRD cosmosv1.CosmosFullNode
fullnodeCRD.Name = fullNodeName
// Purposefully using PodBuilder to cross-test any breaking changes in PodBuilder which affects
// finding the PVC name.
candidate, err := fullnode.NewPodBuilder(&fullnodeCRD).WithOrdinal(1).Build()
require.NoError(t, err)

control := NewVolumeSnapshotControl(&mClient, mockPodFilter{
SyncedPodsFn: func(ctx context.Context, controller client.ObjectKey) []*corev1.Pod {
require.Equal(t, namespace, controller.Namespace)
require.Equal(t, fullNodeName, controller.Name)
return []*corev1.Pod{candidate, new(corev1.Pod), new(corev1.Pod)}
},
})

indexCRD := crd.DeepCopy()
index := int32(2)
indexCRD.Spec.FullNodeRef.Ordinal = &index

_, err = control.FindCandidate(ctx, indexCRD)
require.ErrorContains(t, err, "in-sync pod with index 2 not found")
})

t.Run("custom min available", func(t *testing.T) {
var pod corev1.Pod
pod.Name = "found-me"
Expand Down

0 comments on commit 3d91760

Please sign in to comment.