Skip to content

Commit 5e2355e

Browse files
committed
NS included via labelSelector act as IncludedNamespaces
every item under NS is included. Signed-off-by: Tiger Kaovilai <[email protected]>
1 parent ebbeb7a commit 5e2355e

File tree

3 files changed

+103
-30
lines changed

3 files changed

+103
-30
lines changed

changelogs/unreleased/8342-kaovilai

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
breaking: Namespaces included by labelSelector act as IncludedNamespaces, all items including unlabled under a namespace with labelSelector selected by backup selector will be included in the backup.

pkg/backup/backup_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,41 @@ func TestBackupOldResourceFiltering(t *testing.T) {
267267
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
268268
},
269269
},
270+
{
271+
name: "included namespaces via label filter backs up all resources in those namespaces and other includedNamespaces resources containing label",
272+
backup: defaultBackup().
273+
IncludedNamespaces("moo").
274+
LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"backup-ns": "true"}}).
275+
Result(),
276+
apiResources: []*test.APIResource{
277+
test.Namespaces(
278+
builder.ForNamespace("foo").ObjectMeta(builder.WithLabels("backup-ns", "true")).Result(),
279+
builder.ForNamespace("moo").Result(),
280+
),
281+
test.Pods(
282+
builder.ForPod("foo", "bar").Result(),
283+
builder.ForPod("moo", "mar").ObjectMeta(builder.WithLabels("backup-ns", "true")).Result(),
284+
builder.ForPod("moo", "unlabeled-not-included").Result(),
285+
builder.ForPod("zoo", "raz").Result(),
286+
),
287+
test.Deployments(
288+
builder.ForDeployment("foo", "bar").Result(),
289+
builder.ForDeployment("zoo", "raz").Result(),
290+
),
291+
},
292+
want: []string{
293+
"resources/deployments.apps/namespaces/foo/bar.json",
294+
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
295+
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
296+
"resources/pods/v1-preferredversion/namespaces/moo/mar.json",
297+
"resources/pods/namespaces/foo/bar.json",
298+
"resources/pods/namespaces/moo/mar.json",
299+
"resources/namespaces/cluster/foo.json",
300+
"resources/namespaces/cluster/moo.json",
301+
"resources/namespaces/v1-preferredversion/cluster/foo.json",
302+
"resources/namespaces/v1-preferredversion/cluster/moo.json",
303+
},
304+
},
270305
{
271306
name: "excluded namespaces filter only backs up resources not in those namespaces",
272307
backup: defaultBackup().

pkg/backup/item_collector.go

+67-30
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"k8s.io/apimachinery/pkg/labels"
3333
"k8s.io/apimachinery/pkg/runtime"
3434
"k8s.io/apimachinery/pkg/runtime/schema"
35+
"k8s.io/apimachinery/pkg/util/sets"
3536
"k8s.io/client-go/tools/pager"
3637

3738
"github.com/vmware-tanzu/velero/pkg/client"
@@ -44,14 +45,16 @@ import (
4445
// itemCollector collects items from the Kubernetes API according to
4546
// the backup spec and writes them to files inside dir.
4647
type itemCollector struct {
47-
log logrus.FieldLogger
48-
backupRequest *Request
49-
discoveryHelper discovery.Helper
50-
dynamicFactory client.DynamicFactory
51-
cohabitatingResources map[string]*cohabitatingResource
52-
dir string
53-
pageSize int
54-
nsTracker nsTracker
48+
log logrus.FieldLogger
49+
backupRequest *Request
50+
// Namespaces that are included by the backup's labelSelector will backup all resources in that namespace even if resources are not labeled.
51+
namespacesIncludedByLabelSelector sets.Set[string]
52+
discoveryHelper discovery.Helper
53+
dynamicFactory client.DynamicFactory
54+
cohabitatingResources map[string]*cohabitatingResource
55+
dir string
56+
pageSize int
57+
nsTracker nsTracker
5558
}
5659

5760
// nsTracker is used to integrate several namespace filters together.
@@ -72,42 +75,47 @@ type nsTracker struct {
7275
orLabelSelector []labels.Selector
7376
namespaceFilter *collections.IncludesExcludes
7477
logger logrus.FieldLogger
75-
76-
namespaceMap map[string]bool
78+
namespaceMap sets.Set[string] // namespaceMap is a set of namespaces tracked to include in backup
79+
labeledNamespaces sets.Set[string] // labeledNamespaces is a subset of namespaceMap added via label selectors.
7780
}
7881

7982
// track add the namespace into the namespaceMap.
8083
func (nt *nsTracker) track(ns string) {
8184
if nt.namespaceMap == nil {
82-
nt.namespaceMap = make(map[string]bool)
85+
nt.namespaceMap = make(sets.Set[string])
8386
}
87+
nt.namespaceMap.Insert(ns)
88+
}
8489

85-
if _, ok := nt.namespaceMap[ns]; !ok {
86-
nt.namespaceMap[ns] = true
90+
// trackViaLabel add the namespace into the labeledNamespaces and namespacesMap.
91+
func (nt *nsTracker) trackViaLabel(ns string) {
92+
if nt.labeledNamespaces == nil {
93+
nt.labeledNamespaces = make(sets.Set[string])
8794
}
95+
nt.labeledNamespaces.Insert(ns)
96+
nt.track(ns)
8897
}
8998

9099
// isTracked check whether the namespace's name exists in
91100
// namespaceMap.
92101
func (nt *nsTracker) isTracked(ns string) bool {
93-
if nt.namespaceMap != nil {
94-
return nt.namespaceMap[ns]
95-
}
96-
return false
102+
return nt.namespaceMap.Has(ns)
103+
}
104+
105+
func (nt *nsTracker) isTrackedViaLabel(ns string) bool {
106+
return nt.labeledNamespaces.Has(ns)
97107
}
98108

99-
// init initialize the namespaceMap, and add elements according to
109+
// init initialize the namespaceMap, and add (track) elements according to
100110
// namespace include/exclude filters and the backup label selectors.
111+
// and return only namespaces that are added to tracker.
101112
func (nt *nsTracker) init(
102113
unstructuredNSs []unstructured.Unstructured,
103114
singleLabelSelector labels.Selector,
104115
orLabelSelector []labels.Selector,
105116
namespaceFilter *collections.IncludesExcludes,
106117
logger logrus.FieldLogger,
107118
) {
108-
if nt.namespaceMap == nil {
109-
nt.namespaceMap = make(map[string]bool)
110-
}
111119
nt.singleLabelSelector = singleLabelSelector
112120
nt.orLabelSelector = orLabelSelector
113121
nt.namespaceFilter = namespaceFilter
@@ -120,7 +128,7 @@ func (nt *nsTracker) init(
120128
namespace.GetName(),
121129
)
122130

123-
nt.track(namespace.GetName())
131+
nt.trackViaLabel(namespace.GetName())
124132
continue
125133
}
126134

@@ -130,7 +138,7 @@ func (nt *nsTracker) init(
130138
nt.logger.Debugf("Track namespace %s, because its labels match the backup OrLabelSelector.",
131139
namespace.GetName(),
132140
)
133-
nt.track(namespace.GetName())
141+
nt.trackViaLabel(namespace.GetName())
134142
continue
135143
}
136144
}
@@ -194,6 +202,7 @@ func (r *itemCollector) getItemsFromResourceIdentifiers(
194202

195203
// getAllItems gets all backup-relevant items from all API groups.
196204
func (r *itemCollector) getAllItems() []*kubernetesResource {
205+
// getItems should return all namespaces and will be filtered by nsTracker.filterNamespaces
197206
resources := r.getItems(nil)
198207

199208
return r.nsTracker.filterNamespaces(resources)
@@ -436,13 +445,33 @@ func (r *itemCollector) getResourceItems(
436445
// Namespace are filtered by namespace include/exclude filters,
437446
// backup LabelSelectors and OrLabelSelectors are checked too.
438447
if gr == kuberesource.Namespaces {
439-
return r.collectNamespaces(
448+
// collectNamespaces initializes the nsTracker which should only contain namespaces to backup but returns all namespaces.
449+
namespaces, err := r.collectNamespaces(
440450
resource,
441451
gv,
442452
gr,
443453
preferredGVR,
444454
log,
445455
)
456+
if err != nil {
457+
return nil, err
458+
}
459+
// The namespaces collected contains namespaces selected by namespace filters like include/exclude and label selector.
460+
// Per https://github.com/vmware-tanzu/velero/issues/7492#issuecomment-1986146411 we want labelSelector included namespace to act as IncludedNamespaces so add to NamespaceIncludesExcludes string set.
461+
r.namespacesIncludedByLabelSelector = make(sets.Set[string])
462+
for i := range namespaces {
463+
if namespaces[i] != nil {
464+
if r.nsTracker.isTrackedViaLabel(namespaces[i].name) {
465+
// this namespace is included by the backup's label selector, not the namespace include/exclude filter
466+
// this is a special case where we want to include this namespace in the backup and all resources in it regardless of the resource's label selector
467+
// in other cases, if label selector is set, we only want to include resources that match the label selector
468+
r.namespacesIncludedByLabelSelector.Insert(namespaces[i].name)
469+
r.backupRequest.NamespaceIncludesExcludes.Includes(namespaces[i].name)
470+
r.log.Infof("including namespace %s by labelselector\n", namespaces[i].name)
471+
}
472+
}
473+
}
474+
return namespaces, err
446475
}
447476

448477
clusterScoped := !resource.Namespaced
@@ -509,9 +538,13 @@ func (r *itemCollector) listResourceByLabelsPerNamespace(
509538
logger.WithError(err).Error("Error getting dynamic client")
510539
return nil, err
511540
}
512-
541+
labeledNsSoBackupUnlabeled := false
542+
if r.namespacesIncludedByLabelSelector.Has(namespace) {
543+
logger.Infof("Listing all items including unlabeled in namespace %s because ns was selected by the backup's label selector and acts as IncludedNamespaces", namespace)
544+
labeledNsSoBackupUnlabeled = true
545+
}
513546
var orLabelSelectors []string
514-
if r.backupRequest.Spec.OrLabelSelectors != nil {
547+
if r.backupRequest.Spec.OrLabelSelectors != nil && !labeledNsSoBackupUnlabeled {
515548
for _, s := range r.backupRequest.Spec.OrLabelSelectors {
516549
orLabelSelectors = append(orLabelSelectors, metav1.FormatLabelSelector(s))
517550
}
@@ -537,7 +570,7 @@ func (r *itemCollector) listResourceByLabelsPerNamespace(
537570
}
538571

539572
var labelSelector string
540-
if selector := r.backupRequest.Spec.LabelSelector; selector != nil {
573+
if selector := r.backupRequest.Spec.LabelSelector; selector != nil && !labeledNsSoBackupUnlabeled {
541574
labelSelector = metav1.FormatLabelSelector(selector)
542575
}
543576

@@ -594,7 +627,8 @@ func sortCoreGroup(group *metav1.APIResourceList) {
594627
// pod is backed up, we can perform a pre hook, then process pvcs and pvs (including taking a
595628
// snapshot), then perform a post hook on the pod.
596629
const (
597-
pod = iota
630+
namespaces = iota
631+
pod
598632
pvc
599633
pv
600634
other
@@ -604,6 +638,8 @@ const (
604638
// pods, pvcs, pvs, everything else.
605639
func coreGroupResourcePriority(resource string) int {
606640
switch strings.ToLower(resource) {
641+
case "namespaces":
642+
return namespaces
607643
case "pods":
608644
return pod
609645
case "persistentvolumeclaims":
@@ -722,6 +758,7 @@ func (r *itemCollector) listItemsForLabel(
722758
}
723759

724760
// collectNamespaces process namespace resource according to namespace filters.
761+
// returns a list of ALL namespaces. Filtering will happen outside of this function.
725762
func (r *itemCollector) collectNamespaces(
726763
resource metav1.APIResource,
727764
gv schema.GroupVersion,
@@ -780,7 +817,8 @@ func (r *itemCollector) collectNamespaces(
780817
orSelectors = append(orSelectors, orSelector)
781818
}
782819
}
783-
820+
// init initialize the namespaceMap, and add elements according to
821+
// namespace include/exclude filters and the backup label selectors.
784822
r.nsTracker.init(
785823
unstructuredList.Items,
786824
singleSelector,
@@ -806,6 +844,5 @@ func (r *itemCollector) collectNamespaces(
806844
path: path,
807845
})
808846
}
809-
810847
return items, nil
811848
}

0 commit comments

Comments
 (0)