@@ -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.
4647type 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.
8083func (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.
92101func (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.
101112func (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 }
@@ -196,6 +204,7 @@ func (r *itemCollector) getItemsFromResourceIdentifiers(
196204
197205// getAllItems gets all backup-relevant items from all API groups.
198206func (r * itemCollector ) getAllItems () []* kubernetesResource {
207+ // getItems should return all namespaces and will be filtered by nsTracker.filterNamespaces
199208 resources := r .getItems (nil )
200209
201210 return r .nsTracker .filterNamespaces (resources )
@@ -438,13 +447,33 @@ func (r *itemCollector) getResourceItems(
438447 // Namespace are filtered by namespace include/exclude filters,
439448 // backup LabelSelectors and OrLabelSelectors are checked too.
440449 if gr == kuberesource .Namespaces {
441- return r .collectNamespaces (
450+ // collectNamespaces initializes the nsTracker which should only contain namespaces to backup but returns all namespaces.
451+ namespaces , err := r .collectNamespaces (
442452 resource ,
443453 gv ,
444454 gr ,
445455 preferredGVR ,
446456 log ,
447457 )
458+ if err != nil {
459+ return nil , err
460+ }
461+ // The namespaces collected contains namespaces selected by namespace filters like include/exclude and label selector.
462+ // 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.
463+ r .namespacesIncludedByLabelSelector = make (sets.Set [string ])
464+ for i := range namespaces {
465+ if namespaces [i ] != nil {
466+ if r .nsTracker .isTrackedViaLabel (namespaces [i ].name ) {
467+ // this namespace is included by the backup's label selector, not the namespace include/exclude filter
468+ // 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
469+ // in other cases, if label selector is set, we only want to include resources that match the label selector
470+ r .namespacesIncludedByLabelSelector .Insert (namespaces [i ].name )
471+ r .backupRequest .NamespaceIncludesExcludes .Includes (namespaces [i ].name )
472+ r .log .Infof ("including namespace %s by labelselector\n " , namespaces [i ].name )
473+ }
474+ }
475+ }
476+ return namespaces , err
448477 }
449478
450479 clusterScoped := ! resource .Namespaced
@@ -511,9 +540,13 @@ func (r *itemCollector) listResourceByLabelsPerNamespace(
511540 logger .WithError (err ).Error ("Error getting dynamic client" )
512541 return nil , err
513542 }
514-
543+ labeledNsSoBackupUnlabeled := false
544+ if r .namespacesIncludedByLabelSelector .Has (namespace ) {
545+ 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 )
546+ labeledNsSoBackupUnlabeled = true
547+ }
515548 var orLabelSelectors []string
516- if r .backupRequest .Spec .OrLabelSelectors != nil {
549+ if r .backupRequest .Spec .OrLabelSelectors != nil && ! labeledNsSoBackupUnlabeled {
517550 for _ , s := range r .backupRequest .Spec .OrLabelSelectors {
518551 orLabelSelectors = append (orLabelSelectors , metav1 .FormatLabelSelector (s ))
519552 }
@@ -539,7 +572,7 @@ func (r *itemCollector) listResourceByLabelsPerNamespace(
539572 }
540573
541574 var labelSelector string
542- if selector := r .backupRequest .Spec .LabelSelector ; selector != nil {
575+ if selector := r .backupRequest .Spec .LabelSelector ; selector != nil && ! labeledNsSoBackupUnlabeled {
543576 labelSelector = metav1 .FormatLabelSelector (selector )
544577 }
545578
@@ -592,11 +625,13 @@ func sortCoreGroup(group *metav1.APIResourceList) {
592625}
593626
594627// These constants represent the relative priorities for resources in the core API group. We want to
595- // ensure that we process pods, then pvcs, then pvs, then anything else. This ensures that when a
596- // pod is backed up, we can perform a pre hook, then process pvcs and pvs (including taking a
628+ // ensure that we process namespaces, pods, then pvcs, then pvs, then anything else. This ensures that when a
629+ // namespaces is labeled as included, that it is processed first to act as includedNamespaces for the following resources.
630+ // if pod is backed up, we can perform a pre hook, then process pvcs and pvs (including taking a
597631// snapshot), then perform a post hook on the pod.
598632const (
599- pod = iota
633+ namespaces = iota
634+ pod
600635 pvc
601636 pv
602637 other
@@ -606,6 +641,8 @@ const (
606641// pods, pvcs, pvs, everything else.
607642func coreGroupResourcePriority (resource string ) int {
608643 switch strings .ToLower (resource ) {
644+ case "namespaces" :
645+ return namespaces
609646 case "pods" :
610647 return pod
611648 case "persistentvolumeclaims" :
@@ -724,6 +761,7 @@ func (r *itemCollector) listItemsForLabel(
724761}
725762
726763// collectNamespaces process namespace resource according to namespace filters.
764+ // returns a list of ALL namespaces. Filtering will happen outside of this function.
727765func (r * itemCollector ) collectNamespaces (
728766 resource metav1.APIResource ,
729767 gv schema.GroupVersion ,
@@ -782,7 +820,8 @@ func (r *itemCollector) collectNamespaces(
782820 orSelectors = append (orSelectors , orSelector )
783821 }
784822 }
785-
823+ // init initialize the namespaceMap, and add elements according to
824+ // namespace include/exclude filters and the backup label selectors.
786825 r .nsTracker .init (
787826 unstructuredList .Items ,
788827 singleSelector ,
@@ -808,6 +847,5 @@ func (r *itemCollector) collectNamespaces(
808847 path : path ,
809848 })
810849 }
811-
812850 return items , nil
813851}
0 commit comments