Skip to content

Commit 8171645

Browse files
committed
Merge remote-tracking branch 'origin/main' into bugfix/scaleout-restore-one-to-one-ordinal
2 parents c3f1710 + 49f14a1 commit 8171645

10 files changed

Lines changed: 546 additions & 9 deletions

File tree

apis/operations/v1alpha1/opsrequest_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,13 @@ type FromBackup struct {
518518
// +optional
519519
Namespace string `json:"namespace,omitempty"`
520520

521+
// Specifies the backup source target name to restore from.
522+
// This field is required when the referenced Backup has multiple source targets.
523+
// It is propagated to Restore.spec.backup.sourceTargetName.
524+
//
525+
// +optional
526+
SourceTargetName string `json:"sourceTargetName,omitempty"`
527+
521528
// Defines container environment variables for the restore process.
522529
// merged with the ones specified in the Backup and ActionSet resources.
523530
//

config/crd/bases/operations.kubeblocks.io_opsrequests.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,12 @@ spec:
757757
- RFC3339 format, e.g. "2023-11-25T18:52:53Z"
758758
- A human-readable date-time format, e.g. "Jul 25,2023 18:52:53 UTC+0800"
759759
type: string
760+
sourceTargetName:
761+
description: |-
762+
Specifies the backup source target name to restore from.
763+
This field is required when the referenced Backup has multiple source targets.
764+
It is propagated to Restore.spec.backup.sourceTargetName.
765+
type: string
760766
required:
761767
- name
762768
type: object

controllers/dataprotection/volumepopulator_controller.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,14 @@ import (
3838
"k8s.io/client-go/tools/record"
3939
"k8s.io/component-helpers/storage/volume"
4040
ctrl "sigs.k8s.io/controller-runtime"
41+
"sigs.k8s.io/controller-runtime/pkg/builder"
4142
"sigs.k8s.io/controller-runtime/pkg/client"
4243
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
44+
"sigs.k8s.io/controller-runtime/pkg/event"
45+
"sigs.k8s.io/controller-runtime/pkg/handler"
4346
"sigs.k8s.io/controller-runtime/pkg/log"
47+
"sigs.k8s.io/controller-runtime/pkg/predicate"
48+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4449

4550
appsv1 "github.com/apecloud/kubeblocks/apis/apps/v1"
4651
dpv1alpha1 "github.com/apecloud/kubeblocks/apis/dataprotection/v1alpha1"
@@ -83,6 +88,8 @@ type pvcRestoreDecision struct {
8388

8489
// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/status,verbs=get;update;patch
8590
// +kubebuilder:rbac:groups=core,resources=persistentvolumeclaims/finalizers,verbs=update
91+
// +kubebuilder:rbac:groups=dataprotection.kubeblocks.io,resources=restores,verbs=get;list;watch
92+
// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=clusters,verbs=get;list;watch
8693
// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=components,verbs=get;list;watch
8794
// +kubebuilder:rbac:groups=apps.kubeblocks.io,resources=componentdefinitions,verbs=get;list;watch
8895

@@ -133,9 +140,195 @@ func (r *VolumePopulatorReconciler) handleSyncPVCError(reqCtx intctrlutil.Reques
133140
func (r *VolumePopulatorReconciler) SetupWithManager(mgr ctrl.Manager) error {
134141
return intctrlutil.NewControllerManagedBy(mgr).
135142
For(&corev1.PersistentVolumeClaim{}).
143+
Watches(&dpv1alpha1.Restore{}, handler.EnqueueRequestsFromMapFunc(r.mapRestoreToPVCs),
144+
builder.WithPredicates(restoreDependencyPredicate())).
145+
Watches(&appsv1.Component{}, handler.EnqueueRequestsFromMapFunc(r.mapComponentToPVCs),
146+
builder.WithPredicates(componentDependencyPredicate())).
147+
Watches(&appsv1.Cluster{}, handler.EnqueueRequestsFromMapFunc(r.mapClusterToPVCs),
148+
builder.WithPredicates(clusterDependencyPredicate())).
136149
Complete(r)
137150
}
138151

152+
func (r *VolumePopulatorReconciler) mapRestoreToPVCs(ctx context.Context, obj client.Object) []reconcile.Request {
153+
restore, ok := obj.(*dpv1alpha1.Restore)
154+
if !ok || restore.Labels[dprestore.DataProtectionRestoreLabelKey] != restore.Name {
155+
return nil
156+
}
157+
158+
if owner := exactOwnerReference(restore.OwnerReferences, corev1.SchemeGroupVersion.String(), "PersistentVolumeClaim"); owner != nil {
159+
pvc := &corev1.PersistentVolumeClaim{}
160+
key := types.NamespacedName{Namespace: restore.Namespace, Name: owner.Name}
161+
if err := r.Client.Get(ctx, key, pvc); err != nil || pvc.UID != owner.UID ||
162+
!isClusterRestorePVC(pvc) || restore.Name != getPopulatePVCName(pvc.UID) {
163+
return nil
164+
}
165+
return []reconcile.Request{{NamespacedName: key}}
166+
}
167+
168+
owner := exactOwnerReference(restore.OwnerReferences, appsv1.GroupVersion.String(), "Component")
169+
if owner == nil {
170+
return nil
171+
}
172+
comp := &appsv1.Component{}
173+
key := types.NamespacedName{Namespace: restore.Namespace, Name: owner.Name}
174+
if err := r.Client.Get(ctx, key, comp); err != nil || comp.UID != owner.UID ||
175+
restore.Name != postReadyRestoreName(comp.UID) {
176+
return nil
177+
}
178+
clusterName := comp.Labels[constant.AppInstanceLabelKey]
179+
componentName := restore.Labels[constant.KBAppComponentLabelKey]
180+
ownerComponentName := comp.Labels[constant.KBAppComponentLabelKey]
181+
if clusterName == "" || componentName == "" || ownerComponentName == "" ||
182+
restore.Labels[constant.AppInstanceLabelKey] != clusterName {
183+
return nil
184+
}
185+
// A postReady Restore is owned by its target Component, while its labels
186+
// identify only the first source PVC that created it. Other Components in
187+
// the Cluster can wait on the same Restore through postReady redirection.
188+
return r.mapRestorePVCs(ctx, restore.Namespace, client.MatchingLabels{
189+
constant.AppInstanceLabelKey: clusterName,
190+
})
191+
}
192+
193+
func (r *VolumePopulatorReconciler) mapComponentToPVCs(ctx context.Context, obj client.Object) []reconcile.Request {
194+
comp, ok := obj.(*appsv1.Component)
195+
if !ok {
196+
return nil
197+
}
198+
clusterName := comp.Labels[constant.AppInstanceLabelKey]
199+
componentName := comp.Labels[constant.KBAppComponentLabelKey]
200+
if clusterName == "" || componentName == "" {
201+
return nil
202+
}
203+
// A PVC can depend on another Component through a redirected postReady
204+
// Restore. That relationship is derived from Backup status and is not
205+
// represented on the Component, so a Component event must fan out to all
206+
// active restore PVCs in its Cluster.
207+
return r.mapRestorePVCs(ctx, comp.Namespace, client.MatchingLabels{
208+
constant.AppInstanceLabelKey: clusterName,
209+
})
210+
}
211+
212+
func (r *VolumePopulatorReconciler) mapClusterToPVCs(ctx context.Context, obj client.Object) []reconcile.Request {
213+
cluster, ok := obj.(*appsv1.Cluster)
214+
if !ok || cluster.Name == "" {
215+
return nil
216+
}
217+
return r.mapRestorePVCs(ctx, cluster.Namespace, client.MatchingLabels{
218+
constant.AppInstanceLabelKey: cluster.Name,
219+
})
220+
}
221+
222+
func (r *VolumePopulatorReconciler) mapRestorePVCs(ctx context.Context, namespace string,
223+
labels client.MatchingLabels) []reconcile.Request {
224+
list := &corev1.PersistentVolumeClaimList{}
225+
if err := r.Client.List(ctx, list, client.InNamespace(namespace), labels); err != nil {
226+
return nil
227+
}
228+
requests := make([]reconcile.Request, 0, len(list.Items))
229+
for i := range list.Items {
230+
pvc := &list.Items[i]
231+
if !isClusterRestorePVC(pvc) || pvcRestoreTerminal(pvc) {
232+
continue
233+
}
234+
requests = append(requests, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(pvc)})
235+
}
236+
return requests
237+
}
238+
239+
func isClusterRestorePVC(pvc *corev1.PersistentVolumeClaim) bool {
240+
ref := pvc.Spec.DataSourceRef
241+
if ref == nil || ref.APIGroup == nil || *ref.APIGroup != dptypes.DataprotectionAPIGroup || ref.Name == "" ||
242+
(ref.Kind != dptypes.BackupKind && ref.Kind != dptypes.RestoreKind) {
243+
return false
244+
}
245+
if pvc.Labels[constant.AppInstanceLabelKey] == "" || pvc.Labels[constant.KBAppComponentLabelKey] == "" {
246+
return false
247+
}
248+
for _, key := range []string{
249+
constant.RestoreSourceAPIGroupAnnotationKey,
250+
constant.RestoreSourceKindAnnotationKey,
251+
constant.RestoreSourceNameAnnotationKey,
252+
constant.RestoreComponentAnnotationKey,
253+
constant.RestoreVolumeTemplateAnnotationKey,
254+
} {
255+
if pvc.Annotations[key] == "" {
256+
return false
257+
}
258+
}
259+
restoreComponent := pvc.Annotations[constant.RestoreComponentAnnotationKey]
260+
return restoreComponent == pvc.Labels[constant.KBAppComponentLabelKey] ||
261+
restoreComponent == pvc.Labels[constant.KBAppShardingNameLabelKey] ||
262+
restoreComponent == pvc.Labels[constant.KBAppShardTemplateLabelKey]
263+
}
264+
265+
func pvcRestoreTerminal(pvc *corev1.PersistentVolumeClaim) bool {
266+
condition := findPVCConditionByType(pvc, appsv1.ConditionTypeRestore)
267+
return condition != nil && (condition.Status == corev1.ConditionTrue || condition.Status == corev1.ConditionFalse)
268+
}
269+
270+
func exactOwnerReference(refs []metav1.OwnerReference, apiVersion, kind string) *metav1.OwnerReference {
271+
for i := range refs {
272+
if refs[i].APIVersion == apiVersion && refs[i].Kind == kind && refs[i].Name != "" && refs[i].UID != "" {
273+
return &refs[i]
274+
}
275+
}
276+
return nil
277+
}
278+
279+
func restoreDependencyPredicate() predicate.Predicate {
280+
return predicate.Funcs{
281+
CreateFunc: func(event.CreateEvent) bool { return true },
282+
DeleteFunc: func(event.DeleteEvent) bool { return true },
283+
GenericFunc: func(event.GenericEvent) bool { return false },
284+
UpdateFunc: func(e event.UpdateEvent) bool {
285+
oldRestore, oldOK := e.ObjectOld.(*dpv1alpha1.Restore)
286+
newRestore, newOK := e.ObjectNew.(*dpv1alpha1.Restore)
287+
return oldOK && newOK && (oldRestore.Status.Phase != newRestore.Status.Phase ||
288+
!reflect.DeepEqual(oldRestore.DeletionTimestamp, newRestore.DeletionTimestamp))
289+
},
290+
}
291+
}
292+
293+
func componentDependencyPredicate() predicate.Predicate {
294+
return predicate.Funcs{
295+
CreateFunc: func(event.CreateEvent) bool { return true },
296+
DeleteFunc: func(event.DeleteEvent) bool { return true },
297+
GenericFunc: func(event.GenericEvent) bool { return false },
298+
UpdateFunc: func(e event.UpdateEvent) bool {
299+
oldComp, oldOK := e.ObjectOld.(*appsv1.Component)
300+
newComp, newOK := e.ObjectNew.(*appsv1.Component)
301+
return oldOK && newOK && (oldComp.Status.Phase != newComp.Status.Phase ||
302+
!reflect.DeepEqual(oldComp.DeletionTimestamp, newComp.DeletionTimestamp) ||
303+
!reflect.DeepEqual(postProvisionCondition(oldComp), postProvisionCondition(newComp)))
304+
},
305+
}
306+
}
307+
308+
func clusterDependencyPredicate() predicate.Predicate {
309+
return predicate.Funcs{
310+
CreateFunc: func(event.CreateEvent) bool { return true },
311+
DeleteFunc: func(event.DeleteEvent) bool { return true },
312+
GenericFunc: func(event.GenericEvent) bool { return false },
313+
UpdateFunc: func(e event.UpdateEvent) bool {
314+
oldCluster, oldOK := e.ObjectOld.(*appsv1.Cluster)
315+
newCluster, newOK := e.ObjectNew.(*appsv1.Cluster)
316+
return oldOK && newOK && (oldCluster.Status.Phase != newCluster.Status.Phase ||
317+
!reflect.DeepEqual(oldCluster.DeletionTimestamp, newCluster.DeletionTimestamp))
318+
},
319+
}
320+
}
321+
322+
func postProvisionCondition(comp *appsv1.Component) *metav1.Condition {
323+
for i := range comp.Status.Conditions {
324+
condition := &comp.Status.Conditions[i]
325+
if condition.Type == appsv1.ComponentConditionProgressing && condition.Reason == "PostProvision" {
326+
return condition
327+
}
328+
}
329+
return nil
330+
}
331+
139332
func (r *VolumePopulatorReconciler) MatchToPopulate(pvc *corev1.PersistentVolumeClaim) (bool, error) {
140333
dataSourceRef := pvc.Spec.DataSourceRef
141334
if dataSourceRef == nil {

0 commit comments

Comments
 (0)