Merge pull request #4650 from shubham-pampattiwar/add-label-selectors
Add multiple label selector support to Velero Backup and Restore APIspull/4911/head
commit
9b5257663a
|
@ -0,0 +1 @@
|
|||
Add multiple label selector support to Velero Backup and Restore APIs
|
|
@ -314,6 +314,61 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
orLabelSelectors:
|
||||
description: OrLabelSelectors is list of metav1.LabelSelector to filter
|
||||
with when adding individual objects to the backup. If multiple provided
|
||||
they will be joined by the OR operator. LabelSelector as well as
|
||||
OrLabelSelectors cannot co-exist in backup request, only one of
|
||||
them can be used.
|
||||
items:
|
||||
description: A label selector is a label query over a set of resources.
|
||||
The result of matchLabels and matchExpressions are ANDed. An empty
|
||||
label selector matches all objects. A null label selector matches
|
||||
no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that
|
||||
contains values, a key, and an operator that relates the
|
||||
key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn, Exists
|
||||
and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the
|
||||
operator is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during a
|
||||
strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
orderedResources:
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
|
|
@ -1708,6 +1708,61 @@ spec:
|
|||
included in the map will be restored into namespaces of the same
|
||||
name.
|
||||
type: object
|
||||
orLabelSelectors:
|
||||
description: OrLabelSelectors is list of metav1.LabelSelector to filter
|
||||
with when restoring individual objects from the backup. If multiple
|
||||
provided they will be joined by the OR operator. LabelSelector as
|
||||
well as OrLabelSelectors cannot co-exist in restore request, only
|
||||
one of them can be used
|
||||
items:
|
||||
description: A label selector is a label query over a set of resources.
|
||||
The result of matchLabels and matchExpressions are ANDed. An empty
|
||||
label selector matches all objects. A null label selector matches
|
||||
no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector requirements.
|
||||
The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector that
|
||||
contains values, a key, and an operator that relates the
|
||||
key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector applies
|
||||
to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn, Exists
|
||||
and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values. If the
|
||||
operator is In or NotIn, the values array must be non-empty.
|
||||
If the operator is Exists or DoesNotExist, the values
|
||||
array must be empty. This array is replaced during a
|
||||
strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs. A single
|
||||
{key,value} in the matchLabels map is equivalent to an element
|
||||
of matchExpressions, whose key field is "key", the operator
|
||||
is "In", and the values array contains only "value". The requirements
|
||||
are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
preserveNodePorts:
|
||||
description: PreserveNodePorts specifies whether to restore old nodePorts
|
||||
from backup.
|
||||
|
|
|
@ -343,6 +343,61 @@ spec:
|
|||
type: string
|
||||
type: object
|
||||
type: object
|
||||
orLabelSelectors:
|
||||
description: OrLabelSelectors is list of metav1.LabelSelector
|
||||
to filter with when adding individual objects to the backup.
|
||||
If multiple provided they will be joined by the OR operator.
|
||||
LabelSelector as well as OrLabelSelectors cannot co-exist in
|
||||
backup request, only one of them can be used.
|
||||
items:
|
||||
description: A label selector is a label query over a set of
|
||||
resources. The result of matchLabels and matchExpressions
|
||||
are ANDed. An empty label selector matches all objects. A
|
||||
null label selector matches no objects.
|
||||
properties:
|
||||
matchExpressions:
|
||||
description: matchExpressions is a list of label selector
|
||||
requirements. The requirements are ANDed.
|
||||
items:
|
||||
description: A label selector requirement is a selector
|
||||
that contains values, a key, and an operator that relates
|
||||
the key and values.
|
||||
properties:
|
||||
key:
|
||||
description: key is the label key that the selector
|
||||
applies to.
|
||||
type: string
|
||||
operator:
|
||||
description: operator represents a key's relationship
|
||||
to a set of values. Valid operators are In, NotIn,
|
||||
Exists and DoesNotExist.
|
||||
type: string
|
||||
values:
|
||||
description: values is an array of string values.
|
||||
If the operator is In or NotIn, the values array
|
||||
must be non-empty. If the operator is Exists or
|
||||
DoesNotExist, the values array must be empty. This
|
||||
array is replaced during a strategic merge patch.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
type: object
|
||||
type: array
|
||||
matchLabels:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: matchLabels is a map of {key,value} pairs.
|
||||
A single {key,value} in the matchLabels map is equivalent
|
||||
to an element of matchExpressions, whose key field is
|
||||
"key", the operator is "In", and the values array contains
|
||||
only "value". The requirements are ANDed.
|
||||
type: object
|
||||
type: object
|
||||
nullable: true
|
||||
type: array
|
||||
orderedResources:
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -59,6 +59,15 @@ type BackupSpec struct {
|
|||
// +nullable
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
|
||||
// OrLabelSelectors is list of metav1.LabelSelector to filter with
|
||||
// when adding individual objects to the backup. If multiple provided
|
||||
// they will be joined by the OR operator. LabelSelector as well as
|
||||
// OrLabelSelectors cannot co-exist in backup request, only one of them
|
||||
// can be used.
|
||||
// +optional
|
||||
// +nullable
|
||||
OrLabelSelectors []*metav1.LabelSelector `json:"orLabelSelectors,omitempty"`
|
||||
|
||||
// SnapshotVolumes specifies whether to take cloud snapshots
|
||||
// of any PV's referenced in the set of objects included
|
||||
// in the Backup.
|
||||
|
|
|
@ -71,6 +71,15 @@ type RestoreSpec struct {
|
|||
// +nullable
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
|
||||
// OrLabelSelectors is list of metav1.LabelSelector to filter with
|
||||
// when restoring individual objects from the backup. If multiple provided
|
||||
// they will be joined by the OR operator. LabelSelector as well as
|
||||
// OrLabelSelectors cannot co-exist in restore request, only one of them
|
||||
// can be used
|
||||
// +optional
|
||||
// +nullable
|
||||
OrLabelSelectors []*metav1.LabelSelector `json:"orLabelSelectors,omitempty"`
|
||||
|
||||
// RestorePVs specifies whether to restore all included
|
||||
// PVs from snapshot (via the cloudprovider).
|
||||
// +optional
|
||||
|
|
|
@ -210,6 +210,17 @@ func (in *BackupSpec) DeepCopyInto(out *BackupSpec) {
|
|||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.OrLabelSelectors != nil {
|
||||
in, out := &in.OrLabelSelectors, &out.OrLabelSelectors
|
||||
*out = make([]*metav1.LabelSelector, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.SnapshotVolumes != nil {
|
||||
in, out := &in.SnapshotVolumes, &out.SnapshotVolumes
|
||||
*out = new(bool)
|
||||
|
@ -1251,6 +1262,17 @@ func (in *RestoreSpec) DeepCopyInto(out *RestoreSpec) {
|
|||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.OrLabelSelectors != nil {
|
||||
in, out := &in.OrLabelSelectors, &out.OrLabelSelectors
|
||||
*out = make([]*metav1.LabelSelector, len(*in))
|
||||
for i := range *in {
|
||||
if (*in)[i] != nil {
|
||||
in, out := &(*in)[i], &(*out)[i]
|
||||
*out = new(metav1.LabelSelector)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
}
|
||||
if in.RestorePVs != nil {
|
||||
in, out := &in.RestorePVs, &out.RestorePVs
|
||||
*out = new(bool)
|
||||
|
|
|
@ -331,6 +331,35 @@ func TestBackupResourceFiltering(t *testing.T) {
|
|||
"resources/persistentvolumes/v1-preferredversion/cluster/bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OrLabelSelector only backs up matching resources",
|
||||
backup: defaultBackup().
|
||||
OrLabelSelector([]*metav1.LabelSelector{{MatchLabels: map[string]string{"a1": "b1"}}, {MatchLabels: map[string]string{"a2": "b2"}},
|
||||
{MatchLabels: map[string]string{"a3": "b3"}}, {MatchLabels: map[string]string{"a4": "b4"}}}).
|
||||
Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Pods(
|
||||
builder.ForPod("foo", "bar").ObjectMeta(builder.WithLabels("a1", "b1")).Result(),
|
||||
builder.ForPod("zoo", "raz").Result(),
|
||||
),
|
||||
test.Deployments(
|
||||
builder.ForDeployment("foo", "bar").Result(),
|
||||
builder.ForDeployment("zoo", "raz").ObjectMeta(builder.WithLabels("a2", "b2")).Result(),
|
||||
),
|
||||
test.PVs(
|
||||
builder.ForPersistentVolume("bar").ObjectMeta(builder.WithLabels("a4", "b4")).Result(),
|
||||
builder.ForPersistentVolume("baz").ObjectMeta(builder.WithLabels("a5", "b5")).Result(),
|
||||
),
|
||||
},
|
||||
want: []string{
|
||||
"resources/pods/namespaces/foo/bar.json",
|
||||
"resources/deployments.apps/namespaces/zoo/raz.json",
|
||||
"resources/persistentvolumes/cluster/bar.json",
|
||||
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
|
||||
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
|
||||
"resources/persistentvolumes/v1-preferredversion/cluster/bar.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "resources with velero.io/exclude-from-backup=true label are not included",
|
||||
backup: defaultBackup().
|
||||
|
|
|
@ -291,53 +291,44 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
|
|||
continue
|
||||
}
|
||||
|
||||
var labelSelector string
|
||||
if selector := r.backupRequest.Spec.LabelSelector; selector != nil {
|
||||
labelSelector = metav1.FormatLabelSelector(selector)
|
||||
var orLabelSelectors []string
|
||||
if r.backupRequest.Spec.OrLabelSelectors != nil {
|
||||
for _, s := range r.backupRequest.Spec.OrLabelSelectors {
|
||||
orLabelSelectors = append(orLabelSelectors, metav1.FormatLabelSelector(s))
|
||||
}
|
||||
} else {
|
||||
orLabelSelectors = []string{}
|
||||
}
|
||||
|
||||
log.Info("Listing items")
|
||||
unstructuredItems := make([]unstructured.Unstructured, 0)
|
||||
|
||||
if r.pageSize > 0 {
|
||||
// If limit is positive, use a pager to split list over multiple requests
|
||||
// Use Velero's dynamic list function instead of the default
|
||||
listPager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
return resourceClient.List(opts)
|
||||
}))
|
||||
// Use the page size defined in the server config
|
||||
// TODO allow configuration of page buffer size
|
||||
listPager.PageSize = int64(r.pageSize)
|
||||
// Add each item to temporary slice
|
||||
list, paginated, err := listPager.List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector})
|
||||
// Listing items for orLabelSelectors
|
||||
errListingForNS := false
|
||||
for _, label := range orLabelSelectors {
|
||||
unstructuredItems, err = r.listItemsForLabel(unstructuredItems, gr, label, resourceClient)
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error listing resources")
|
||||
errListingForNS = true
|
||||
}
|
||||
}
|
||||
|
||||
if errListingForNS {
|
||||
log.WithError(err).Error("Error listing items")
|
||||
continue
|
||||
}
|
||||
|
||||
var labelSelector string
|
||||
if selector := r.backupRequest.Spec.LabelSelector; selector != nil {
|
||||
labelSelector = metav1.FormatLabelSelector(selector)
|
||||
}
|
||||
|
||||
// Listing items for labelSelector (singular)
|
||||
if len(orLabelSelectors) == 0 {
|
||||
unstructuredItems, err = r.listItemsForLabel(unstructuredItems, gr, labelSelector, resourceClient)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error listing items")
|
||||
continue
|
||||
}
|
||||
if !paginated {
|
||||
log.Infof("list for groupResource %s was not paginated", gr)
|
||||
}
|
||||
err = meta.EachListItem(list, func(object runtime.Object) error {
|
||||
u, ok := object.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
log.WithError(errors.WithStack(fmt.Errorf("expected *unstructured.Unstructured but got %T", u))).Error("unable to understand entry in the list")
|
||||
return fmt.Errorf("expected *unstructured.Unstructured but got %T", u)
|
||||
}
|
||||
unstructuredItems = append(unstructuredItems, *u)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("unable to understand paginated list")
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// If limit is not positive, do not use paging. Instead, request all items at once
|
||||
unstructuredList, err := resourceClient.List(metav1.ListOptions{LabelSelector: labelSelector})
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error listing items")
|
||||
continue
|
||||
}
|
||||
unstructuredItems = append(unstructuredItems, unstructuredList.Items...)
|
||||
}
|
||||
|
||||
log.Infof("Retrieved %d items", len(unstructuredItems))
|
||||
|
@ -467,3 +458,60 @@ func newCohabitatingResource(resource, group1, group2 string) *cohabitatingResou
|
|||
seen: false,
|
||||
}
|
||||
}
|
||||
|
||||
// function to process pager client calls when the pageSize is specified
|
||||
func (r *itemCollector) processPagerClientCalls(gr schema.GroupResource, label string, resourceClient client.Dynamic) (runtime.Object, error) {
|
||||
// If limit is positive, use a pager to split list over multiple requests
|
||||
// Use Velero's dynamic list function instead of the default
|
||||
listPager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
return resourceClient.List(opts)
|
||||
}))
|
||||
// Use the page size defined in the server config
|
||||
// TODO allow configuration of page buffer size
|
||||
listPager.PageSize = int64(r.pageSize)
|
||||
// Add each item to temporary slice
|
||||
list, paginated, err := listPager.List(context.Background(), metav1.ListOptions{LabelSelector: label})
|
||||
|
||||
if err != nil {
|
||||
r.log.WithError(errors.WithStack(err)).Error("Error listing resources")
|
||||
return list, err
|
||||
}
|
||||
|
||||
if !paginated {
|
||||
r.log.Infof("list for groupResource %s was not paginated", gr)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *itemCollector) listItemsForLabel(unstructuredItems []unstructured.Unstructured, gr schema.GroupResource, label string, resourceClient client.Dynamic) ([]unstructured.Unstructured, error) {
|
||||
if r.pageSize > 0 {
|
||||
// process pager client calls
|
||||
list, err := r.processPagerClientCalls(gr, label, resourceClient)
|
||||
if err != nil {
|
||||
return unstructuredItems, err
|
||||
}
|
||||
|
||||
err = meta.EachListItem(list, func(object runtime.Object) error {
|
||||
u, ok := object.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
r.log.WithError(errors.WithStack(fmt.Errorf("expected *unstructured.Unstructured but got %T", u))).Error("unable to understand entry in the list")
|
||||
return fmt.Errorf("expected *unstructured.Unstructured but got %T", u)
|
||||
}
|
||||
unstructuredItems = append(unstructuredItems, *u)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
r.log.WithError(errors.WithStack(err)).Error("unable to understand paginated list")
|
||||
return unstructuredItems, err
|
||||
}
|
||||
} else {
|
||||
unstructuredList, err := resourceClient.List(metav1.ListOptions{LabelSelector: label})
|
||||
if err != nil {
|
||||
r.log.WithError(errors.WithStack(err)).Error("Error listing items")
|
||||
return unstructuredItems, err
|
||||
}
|
||||
unstructuredItems = append(unstructuredItems, unstructuredList.Items...)
|
||||
}
|
||||
return unstructuredItems, nil
|
||||
}
|
||||
|
|
|
@ -162,6 +162,12 @@ func (b *BackupBuilder) LabelSelector(selector *metav1.LabelSelector) *BackupBui
|
|||
return b
|
||||
}
|
||||
|
||||
// OrLabelSelector sets the Backup's orLabelSelector set.
|
||||
func (b *BackupBuilder) OrLabelSelector(orSelectors []*metav1.LabelSelector) *BackupBuilder {
|
||||
b.object.Spec.OrLabelSelectors = orSelectors
|
||||
return b
|
||||
}
|
||||
|
||||
// SnapshotVolumes sets the Backup's "snapshot volumes" flag.
|
||||
func (b *BackupBuilder) SnapshotVolumes(val bool) *BackupBuilder {
|
||||
b.object.Spec.SnapshotVolumes = &val
|
||||
|
|
|
@ -113,6 +113,12 @@ func (b *RestoreBuilder) LabelSelector(selector *metav1.LabelSelector) *RestoreB
|
|||
return b
|
||||
}
|
||||
|
||||
// OrLabelSelector sets the Restore's orLabelSelector set.
|
||||
func (b *RestoreBuilder) OrLabelSelector(orSelectors []*metav1.LabelSelector) *RestoreBuilder {
|
||||
b.object.Spec.OrLabelSelectors = orSelectors
|
||||
return b
|
||||
}
|
||||
|
||||
// NamespaceMappings sets the Restore's namespace mappings.
|
||||
func (b *RestoreBuilder) NamespaceMappings(mapping ...string) *RestoreBuilder {
|
||||
if b.object.Spec.NamespaceMapping == nil {
|
||||
|
|
|
@ -461,6 +461,11 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup) *pkg
|
|||
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
|
||||
}
|
||||
|
||||
// validate that only one exists orLabelSelector or just labelSelector (singular)
|
||||
if request.Spec.OrLabelSelectors != nil && request.Spec.LabelSelector != nil {
|
||||
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("encountered labelSelector as well as orLabelSelectors in backup spec, only one can be specified"))
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
|
|
|
@ -189,6 +189,13 @@ func TestProcessBackupValidationFailures(t *testing.T) {
|
|||
backupLocation: builder.ForBackupStorageLocation("velero", "read-only").AccessMode(velerov1api.BackupStorageLocationAccessModeReadOnly).Result(),
|
||||
expectedErrs: []string{"backup can't be created because backup storage location read-only is currently in read-only mode"},
|
||||
},
|
||||
{
|
||||
name: "labelSelector as well as orLabelSelectors both are specified in backup request fails validation",
|
||||
backup: defaultBackup().LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}).OrLabelSelector([]*metav1.LabelSelector{{MatchLabels: map[string]string{"a1": "b1"}}, {MatchLabels: map[string]string{"a2": "b2"}},
|
||||
{MatchLabels: map[string]string{"a3": "b3"}}, {MatchLabels: map[string]string{"a4": "b4"}}}).Result(),
|
||||
backupLocation: defaultBackupLocation,
|
||||
expectedErrs: []string{"encountered labelSelector as well as orLabelSelectors in backup spec, only one can be specified"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -326,6 +326,11 @@ func (c *restoreController) validateAndComplete(restore *api.Restore, pluginMana
|
|||
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
|
||||
}
|
||||
|
||||
// validate that only one exists orLabelSelector or just labelSelector (singular)
|
||||
if restore.Spec.OrLabelSelectors != nil && restore.Spec.LabelSelector != nil {
|
||||
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, fmt.Sprintf("encountered labelSelector as well as orLabelSelectors in restore spec, only one can be specified"))
|
||||
}
|
||||
|
||||
// validate that exactly one of BackupName and ScheduleName have been specified
|
||||
if !backupXorScheduleProvided(restore) {
|
||||
restore.Status.ValidationErrors = append(restore.Status.ValidationErrors, "Either a backup or schedule must be specified as a source for the restore, but not both")
|
||||
|
|
|
@ -308,6 +308,15 @@ func TestProcessQueueItem(t *testing.T) {
|
|||
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
|
||||
expectedValidationErrors: []string{"Either a backup or schedule must be specified as a source for the restore, but not both"},
|
||||
},
|
||||
{
|
||||
name: "new restore with labelSelector as well as orLabelSelector fails validation",
|
||||
location: defaultStorageLocation,
|
||||
restore: NewRestore("foo", "bar", "backup-1", "ns-1", "", velerov1api.RestorePhaseNew).LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}).OrLabelSelector([]*metav1.LabelSelector{{MatchLabels: map[string]string{"a1": "b1"}}, {MatchLabels: map[string]string{"a2": "b2"}}, {MatchLabels: map[string]string{"a3": "b3"}}, {MatchLabels: map[string]string{"a4": "b4"}}}).Result(),
|
||||
backup: defaultBackup().StorageLocation("default").Result(),
|
||||
expectedErr: false,
|
||||
expectedValidationErrors: []string{"encountered labelSelector as well as orLabelSelectors in restore spec, only one can be specified"},
|
||||
expectedPhase: string(velerov1api.RestorePhaseFailedValidation),
|
||||
},
|
||||
{
|
||||
name: "valid restore with schedule name gets executed",
|
||||
location: defaultStorageLocation,
|
||||
|
|
|
@ -183,6 +183,17 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
|||
ls = &metav1.LabelSelector{}
|
||||
}
|
||||
|
||||
var OrSelectors []labels.Selector
|
||||
if req.Restore.Spec.OrLabelSelectors != nil {
|
||||
for _, s := range req.Restore.Spec.OrLabelSelectors {
|
||||
labelAsSelector, err := metav1.LabelSelectorAsSelector(s)
|
||||
if err != nil {
|
||||
return Result{}, Result{Velero: []string{err.Error()}}
|
||||
}
|
||||
OrSelectors = append(OrSelectors, labelAsSelector)
|
||||
}
|
||||
}
|
||||
|
||||
selector, err := metav1.LabelSelectorAsSelector(ls)
|
||||
if err != nil {
|
||||
return Result{}, Result{Velero: []string{err.Error()}}
|
||||
|
@ -264,6 +275,7 @@ func (kr *kubernetesRestorer) RestoreWithResolvers(
|
|||
namespaceIncludesExcludes: namespaceIncludesExcludes,
|
||||
chosenGrpVersToRestore: make(map[string]ChosenGroupVersion),
|
||||
selector: selector,
|
||||
OrSelectors: OrSelectors,
|
||||
log: req.Log,
|
||||
dynamicFactory: kr.dynamicFactory,
|
||||
fileSystem: kr.fileSystem,
|
||||
|
@ -305,6 +317,7 @@ type restoreContext struct {
|
|||
namespaceIncludesExcludes *collections.IncludesExcludes
|
||||
chosenGrpVersToRestore map[string]ChosenGroupVersion
|
||||
selector labels.Selector
|
||||
OrSelectors []labels.Selector
|
||||
log logrus.FieldLogger
|
||||
dynamicFactory client.DynamicFactory
|
||||
fileSystem filesystem.Interface
|
||||
|
@ -1862,6 +1875,27 @@ func (ctx *restoreContext) getSelectedRestoreableItems(resource, targetNamespace
|
|||
continue
|
||||
}
|
||||
|
||||
// Processing OrLabelSelectors when specified in the restore request. LabelSelectors as well as OrLabelSelectors
|
||||
// cannot co-exist, only one of them can be specified
|
||||
var skipItem = false
|
||||
var skip = 0
|
||||
ctx.log.Debugf("orSelectors specified: %s for item: %s", ctx.OrSelectors, item)
|
||||
for _, s := range ctx.OrSelectors {
|
||||
if !s.Matches(labels.Set(obj.GetLabels())) {
|
||||
skip++
|
||||
}
|
||||
|
||||
if len(ctx.OrSelectors) == skip && skip > 0 {
|
||||
ctx.log.Infof("setting skip flag to true for item: %s", item)
|
||||
skipItem = true
|
||||
}
|
||||
}
|
||||
|
||||
if skipItem {
|
||||
ctx.log.Infof("restore orSelector labels did not match, skipping restore of item: %s", skipItem, item)
|
||||
continue
|
||||
}
|
||||
|
||||
selectedItem := restoreableItem{
|
||||
path: itemPath,
|
||||
name: item,
|
||||
|
|
|
@ -252,6 +252,36 @@ func TestRestoreResourceFiltering(t *testing.T) {
|
|||
test.PVs(): {"/pv-1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OrLabelSelectors only restores matching resources",
|
||||
restore: defaultRestore().OrLabelSelector([]*metav1.LabelSelector{{MatchLabels: map[string]string{"a1": "b1"}}, {MatchLabels: map[string]string{"a2": "b2"}},
|
||||
{MatchLabels: map[string]string{"a3": "b3"}}, {MatchLabels: map[string]string{"a4": "b4"}}}).Result(),
|
||||
backup: defaultBackup().Result(),
|
||||
tarball: test.NewTarWriter(t).
|
||||
AddItems("pods",
|
||||
builder.ForPod("ns-1", "pod-1").ObjectMeta(builder.WithLabels("a1", "b1")).Result(),
|
||||
builder.ForPod("ns-2", "pod-2").Result(),
|
||||
).
|
||||
AddItems("deployments.apps",
|
||||
builder.ForDeployment("ns-1", "deploy-1").Result(),
|
||||
builder.ForDeployment("ns-2", "deploy-2").ObjectMeta(builder.WithLabels("a3", "b3")).Result(),
|
||||
).
|
||||
AddItems("persistentvolumes",
|
||||
builder.ForPersistentVolume("pv-1").ObjectMeta(builder.WithLabels("a5", "b5")).Result(),
|
||||
builder.ForPersistentVolume("pv-2").ObjectMeta(builder.WithLabels("a4", "b4")).Result(),
|
||||
).
|
||||
Done(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Pods(),
|
||||
test.Deployments(),
|
||||
test.PVs(),
|
||||
},
|
||||
want: map[*test.APIResource][]string{
|
||||
test.Pods(): {"ns-1/pod-1"},
|
||||
test.Deployments(): {"ns-2/deploy-2"},
|
||||
test.PVs(): {"/pv-2"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should include cluster-scoped resources if restoring subset of namespaces and IncludeClusterResources=true",
|
||||
restore: defaultRestore().IncludedNamespaces("ns-1").IncludeClusterResources(true).Result(),
|
||||
|
|
|
@ -59,6 +59,13 @@ spec:
|
|||
matchLabels:
|
||||
app: velero
|
||||
component: server
|
||||
# Individual object when matched with any of the label selector specified in the set are to be included in the backup. Optional.
|
||||
# orLabelSelectors as well as labelSelector cannot co-exist, only one of them can be specified in the backup request
|
||||
orLabelSelectors:
|
||||
- matchLabels:
|
||||
app: velero
|
||||
- matchLabels:
|
||||
app: data-protection
|
||||
# Whether or not to snapshot volumes. This only applies to PersistentVolumes for Azure, GCE, and
|
||||
# AWS. Valid values are true, false, and null/unset. If unset, Velero performs snapshots as long as
|
||||
# a persistent volume provider is configured for Velero.
|
||||
|
|
|
@ -61,6 +61,13 @@ spec:
|
|||
matchLabels:
|
||||
app: velero
|
||||
component: server
|
||||
# Individual object when matched with any of the label selector specified in the set are to be included in the restore. Optional.
|
||||
# orLabelSelectors as well as labelSelector cannot co-exist, only one of them can be specified in the restore request
|
||||
orLabelSelectors:
|
||||
- matchLabels:
|
||||
app: velero
|
||||
- matchLabels:
|
||||
app: data-protection
|
||||
# NamespaceMapping is a map of source namespace names to
|
||||
# target namespace names to restore into. Any source namespaces not
|
||||
# included in the map will be restored into namespaces of the same name.
|
||||
|
|
Loading…
Reference in New Issue