mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-08 06:15:40 +00:00
Merge pull request #6320 from blackpiglet/6302-fix
Include namespace resource needed by namespaced-scope resource in backup
This commit is contained in:
1
changelogs/unreleased/6320-blackpiglet
Normal file
1
changelogs/unreleased/6320-blackpiglet
Normal file
@@ -0,0 +1 @@
|
||||
Include namespaces needed by namespaced-scope resources in backup.
|
||||
@@ -4101,3 +4101,147 @@ func TestBackupNewResourceFiltering(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackupNamespaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
backup *velerov1.Backup
|
||||
apiResources []*test.APIResource
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "LabelSelector test",
|
||||
backup: defaultBackup().LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}).
|
||||
Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Namespaces(
|
||||
builder.ForNamespace("ns-1").Result(),
|
||||
builder.ForNamespace("ns-2").Result(),
|
||||
builder.ForNamespace("ns-3").Result(),
|
||||
),
|
||||
test.Deployments(
|
||||
builder.ForDeployment("ns-1", "deploy-1").ObjectMeta(builder.WithLabels("a", "b")).Result(),
|
||||
),
|
||||
},
|
||||
want: []string{
|
||||
"resources/namespaces/cluster/ns-1.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
|
||||
"resources/deployments.apps/namespaces/ns-1/deploy-1.json",
|
||||
"resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json",
|
||||
"resources/namespaces/cluster/ns-2.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-2.json",
|
||||
"resources/namespaces/cluster/ns-3.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OrLabelSelector test",
|
||||
backup: defaultBackup().OrLabelSelector([]*metav1.LabelSelector{
|
||||
{MatchLabels: map[string]string{"a": "b"}},
|
||||
{MatchLabels: map[string]string{"c": "d"}},
|
||||
}).
|
||||
Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Namespaces(
|
||||
builder.ForNamespace("ns-1").Result(),
|
||||
builder.ForNamespace("ns-2").Result(),
|
||||
builder.ForNamespace("ns-3").Result(),
|
||||
),
|
||||
test.Deployments(
|
||||
builder.ForDeployment("ns-1", "deploy-1").ObjectMeta(builder.WithLabels("a", "b")).Result(),
|
||||
builder.ForDeployment("ns-2", "deploy-2").ObjectMeta(builder.WithLabels("c", "d")).Result(),
|
||||
),
|
||||
},
|
||||
want: []string{
|
||||
"resources/namespaces/cluster/ns-1.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
|
||||
"resources/namespaces/cluster/ns-2.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-2.json",
|
||||
"resources/namespaces/cluster/ns-3.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
|
||||
"resources/deployments.apps/namespaces/ns-1/deploy-1.json",
|
||||
"resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json",
|
||||
"resources/deployments.apps/namespaces/ns-2/deploy-2.json",
|
||||
"resources/deployments.apps/v1-preferredversion/namespaces/ns-2/deploy-2.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "LabelSelector and Namespace filtering test",
|
||||
backup: defaultBackup().IncludedNamespaces("ns-1").LabelSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"a": "b"}}).
|
||||
Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Namespaces(
|
||||
builder.ForNamespace("ns-1").Result(),
|
||||
builder.ForNamespace("ns-2").Result(),
|
||||
builder.ForNamespace("ns-3").Result(),
|
||||
),
|
||||
test.Deployments(
|
||||
builder.ForDeployment("ns-1", "deploy-1").ObjectMeta(builder.WithLabels("a", "b")).Result(),
|
||||
),
|
||||
},
|
||||
want: []string{
|
||||
"resources/namespaces/cluster/ns-1.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
|
||||
"resources/deployments.apps/namespaces/ns-1/deploy-1.json",
|
||||
"resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Empty namespace test",
|
||||
backup: defaultBackup().IncludedNamespaces("invalid*").Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Namespaces(
|
||||
builder.ForNamespace("ns-1").Result(),
|
||||
builder.ForNamespace("ns-2").Result(),
|
||||
builder.ForNamespace("ns-3").Result(),
|
||||
),
|
||||
test.Deployments(
|
||||
builder.ForDeployment("ns-1", "deploy-1").ObjectMeta(builder.WithLabels("a", "b")).Result(),
|
||||
),
|
||||
},
|
||||
want: []string{},
|
||||
},
|
||||
{
|
||||
name: "Default namespace filter test",
|
||||
backup: defaultBackup().Result(),
|
||||
apiResources: []*test.APIResource{
|
||||
test.Namespaces(
|
||||
builder.ForNamespace("ns-1").Result(),
|
||||
builder.ForNamespace("ns-2").Result(),
|
||||
builder.ForNamespace("ns-3").Result(),
|
||||
),
|
||||
test.Deployments(
|
||||
builder.ForDeployment("ns-1", "deploy-1").ObjectMeta(builder.WithLabels("a", "b")).Result(),
|
||||
),
|
||||
},
|
||||
want: []string{
|
||||
"resources/namespaces/cluster/ns-1.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-1.json",
|
||||
"resources/namespaces/cluster/ns-2.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-2.json",
|
||||
"resources/namespaces/cluster/ns-3.json",
|
||||
"resources/namespaces/v1-preferredversion/cluster/ns-3.json",
|
||||
"resources/deployments.apps/namespaces/ns-1/deploy-1.json",
|
||||
"resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
h = newHarness(t)
|
||||
req = &Request{Backup: tc.backup}
|
||||
backupFile = bytes.NewBuffer([]byte{})
|
||||
)
|
||||
|
||||
for _, resource := range tc.apiResources {
|
||||
h.addItems(t, resource)
|
||||
}
|
||||
|
||||
h.backupper.Backup(h.log, req, backupFile, nil, nil)
|
||||
|
||||
assertTarballContents(t, backupFile, append(tc.want, "metadata/version")...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
@@ -275,56 +274,24 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
|
||||
|
||||
namespacesToList := getNamespacesToList(r.backupRequest.NamespaceIncludesExcludes)
|
||||
|
||||
// Check if we're backing up namespaces for a less-than-full backup.
|
||||
// We enter this block if resource is Namespaces and the namespace list is either empty or contains
|
||||
// an explicit namespace list. (We skip this block if the list contains "" since that indicates
|
||||
// a full-cluster backup
|
||||
if gr == kuberesource.Namespaces && (len(namespacesToList) == 0 || namespacesToList[0] != "") {
|
||||
// Handle namespace resource here.
|
||||
// Namespace are only filtered by namespace include/exclude filters.
|
||||
// Label selectors are not checked.
|
||||
if gr == kuberesource.Namespaces {
|
||||
resourceClient, err := r.dynamicFactory.ClientForGroupVersionResource(gv, resource, "")
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error getting dynamic client")
|
||||
} else {
|
||||
var labelSelector labels.Selector
|
||||
if r.backupRequest.Spec.LabelSelector != nil {
|
||||
labelSelector, err = metav1.LabelSelectorAsSelector(r.backupRequest.Spec.LabelSelector)
|
||||
if err != nil {
|
||||
// This should never happen...
|
||||
return nil, errors.Wrap(err, "invalid label selector")
|
||||
}
|
||||
}
|
||||
|
||||
var items []*kubernetesResource
|
||||
for _, ns := range namespacesToList {
|
||||
log = log.WithField("namespace", ns)
|
||||
log.Info("Getting namespace")
|
||||
unstructured, err := resourceClient.Get(ns, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("Error getting namespace")
|
||||
continue
|
||||
}
|
||||
|
||||
labels := labels.Set(unstructured.GetLabels())
|
||||
if labelSelector != nil && !labelSelector.Matches(labels) {
|
||||
log.Info("Skipping namespace because it does not match the backup's label selector")
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := r.writeToFile(unstructured)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error writing item to file")
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, &kubernetesResource{
|
||||
groupResource: gr,
|
||||
preferredGVR: preferredGVR,
|
||||
name: ns,
|
||||
path: path,
|
||||
})
|
||||
}
|
||||
|
||||
return items, nil
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
unstructuredList, err := resourceClient.List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
log.WithError(errors.WithStack(err)).Error("error list namespaces")
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
items := r.backupNamespaces(unstructuredList, namespacesToList, gr, preferredGVR, log)
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// If we get here, we're backing up something other than namespaces
|
||||
@@ -390,11 +357,6 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
|
||||
for i := range unstructuredItems {
|
||||
item := &unstructuredItems[i]
|
||||
|
||||
if gr == kuberesource.Namespaces && !r.backupRequest.NamespaceIncludesExcludes.ShouldInclude(item.GetName()) {
|
||||
log.WithField("name", item.GetName()).Info("Skipping namespace because it's excluded")
|
||||
continue
|
||||
}
|
||||
|
||||
path, err := r.writeToFile(item)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error writing item to file")
|
||||
@@ -568,3 +530,48 @@ func (r *itemCollector) listItemsForLabel(unstructuredItems []unstructured.Unstr
|
||||
}
|
||||
return unstructuredItems, nil
|
||||
}
|
||||
|
||||
// backupNamespaces process namespace resource according to namespace filters.
|
||||
func (r *itemCollector) backupNamespaces(unstructuredList *unstructured.UnstructuredList,
|
||||
namespacesToList []string, gr schema.GroupResource, preferredGVR schema.GroupVersionResource,
|
||||
log logrus.FieldLogger) []*kubernetesResource {
|
||||
var items []*kubernetesResource
|
||||
for index, unstructured := range unstructuredList.Items {
|
||||
found := false
|
||||
if len(namespacesToList) == 0 {
|
||||
// No namespace found. By far, this condition cannot be triggered. Either way,
|
||||
// namespacesToList is not empty.
|
||||
log.Debug("Skip namespace resource, because no item found by namespace filters.")
|
||||
break
|
||||
} else if len(namespacesToList) == 1 && namespacesToList[0] == "" {
|
||||
// All namespaces are included.
|
||||
log.Debugf("Backup namespace %s due to full cluster backup.", unstructured.GetName())
|
||||
found = true
|
||||
} else {
|
||||
for _, ns := range namespacesToList {
|
||||
if unstructured.GetName() == ns {
|
||||
log.Debugf("Backup namespace %s due to namespace filters setting.", unstructured.GetName())
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
path, err := r.writeToFile(&unstructuredList.Items[index])
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Error writing item to file")
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, &kubernetesResource{
|
||||
groupResource: gr,
|
||||
preferredGVR: preferredGVR,
|
||||
name: unstructured.GetName(),
|
||||
path: path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user