Backup all groups and versions with backward compatibility (#2373)

* Backup all API Groups versions while keeping backward compatibility

Signed-off-by: Rafael Brito <rbrito@vmware.com>

* Backup all API Groups versions while keeping backward compatibility

Signed-off-by: Rafael Brito <rbrito@vmware.com>

* Adding feature flag to enable backup of multiple API group versions

Signed-off-by: Rafael Brito <rbrito@vmware.com>
pull/2500/head
Rafael Brito 2020-05-01 14:54:57 -05:00 committed by GitHub
parent e569637dc7
commit 0d97f9400e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 399 additions and 38 deletions

View File

@ -212,10 +212,15 @@ const (
// BackupStatus captures the current status of a Velero backup.
type BackupStatus struct {
// Version is the backup format version.
// Version is the backup format major version.
// Deprecated: Please see FormatVersion
// +optional
Version int `json:"version,omitempty"`
// FormatVersion is the backup format version, including major, minor, and patch version.
// +optional
FormatVersion string `json:"formatVersion,omitempty"`
// Expiration is when this Backup is eligible for garbage-collection.
// +optional
// +nullable

View File

@ -39,4 +39,11 @@ const (
// CSIFeatureFlag is the feature flag string that defines whether or not CSI features are being used.
CSIFeatureFlag = "EnableCSI"
// PreferredVersionDir is the suffix name of the directory containing the preferred version of the API group
// resource within a Velero backup.
PreferredVersionDir = "-preferredversion"
// APIGroupVersionsFeatureFlag is the feature flag string that defines whether or not to handle multiple API Group Versions
APIGroupVersionsFeatureFlag = "EnableAPIGroupVersions"
)

View File

@ -40,9 +40,13 @@ import (
"github.com/vmware-tanzu/velero/pkg/util/collections"
)
// BackupVersion is the current backup version for Velero.
// BackupVersion is the current backup major version for Velero.
// Deprecated, use BackupFormatVersion
const BackupVersion = 1
// BackupFormatVersion is the current backup version for Velero, including major, minor, and patch.
const BackupFormatVersion = "1.1.0"
// Backupper performs backups.
type Backupper interface {
// Backup takes a backup using the specification in the api.Backup and writes backup and log data
@ -284,7 +288,7 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req
func (kb *kubernetesBackupper) writeBackupVersion(tw *tar.Writer) error {
versionFile := filepath.Join(api.MetadataDir, "version")
versionString := fmt.Sprintf("%d\n", BackupVersion)
versionString := fmt.Sprintf("%s\n", BackupFormatVersion)
hdr := &tar.Header{
Name: versionFile,

View File

@ -99,6 +99,16 @@ func TestBackedUpItemsMatchesTarballContents(t *testing.T) {
}
file = file + "/" + item.name + ".json"
expectedFiles = append(expectedFiles, file)
fileWithVersion := "resources/" + gvkToResource[item.resource]
if item.namespace != "" {
fileWithVersion = fileWithVersion + "/v1-preferredversion/" + "namespaces/" + item.namespace
} else {
file = file + "/cluster"
fileWithVersion = fileWithVersion + "/v1-preferredversion" + "/cluster"
}
fileWithVersion = fileWithVersion + "/" + item.name + ".json"
expectedFiles = append(expectedFiles, fileWithVersion)
}
assertTarballContents(t, backupFile, append(expectedFiles, "metadata/version")...)
@ -135,6 +145,10 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -155,6 +169,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -175,6 +191,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -195,6 +213,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
@ -215,6 +235,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
},
},
{
@ -241,6 +263,10 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -266,6 +292,9 @@ func TestBackupResourceFiltering(t *testing.T) {
"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",
},
},
{
@ -290,6 +319,9 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/persistentvolumes/cluster/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/persistentvolumes/v1-preferredversion/cluster/bar.json",
},
},
{
@ -314,6 +346,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/zoo/raz.json",
"resources/persistentvolumes/cluster/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/persistentvolumes/v1-preferredversion/cluster/bar.json",
},
},
{
@ -341,6 +375,12 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/persistentvolumes/cluster/bar.json",
"resources/persistentvolumes/cluster/baz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
"resources/persistentvolumes/v1-preferredversion/cluster/bar.json",
"resources/persistentvolumes/v1-preferredversion/cluster/baz.json",
},
},
{
@ -365,6 +405,10 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/ns-2/pod-1.json",
"resources/persistentvolumes/cluster/pv-1.json",
"resources/persistentvolumes/cluster/pv-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-2.json",
},
},
{
@ -387,6 +431,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-1.json",
},
},
{
@ -408,6 +454,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-1.json",
},
},
{
@ -432,6 +480,11 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/ns-3/pod-1.json",
"resources/persistentvolumes/cluster/pv-1.json",
"resources/persistentvolumes/cluster/pv-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-3/pod-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-2.json",
},
},
{
@ -454,6 +507,9 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-1.json",
"resources/pods/namespaces/ns-3/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-3/pod-1.json",
},
},
{
@ -477,6 +533,11 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/ns-3/pod-1.json",
"resources/persistentvolumes/cluster/pv-1.json",
"resources/persistentvolumes/cluster/pv-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-3/pod-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-2.json",
},
},
{
@ -499,6 +560,10 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -521,6 +586,10 @@ func TestBackupResourceFiltering(t *testing.T) {
"resources/pods/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -541,6 +610,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -578,6 +649,8 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -599,8 +672,12 @@ func TestBackupResourceFiltering(t *testing.T) {
want: []string{
"resources/pods/namespaces/foo/bar.json",
"resources/pods/namespaces/zoo/raz.json",
"resources/pods/v1-preferredversion/namespaces/foo/bar.json",
"resources/pods/v1-preferredversion/namespaces/zoo/raz.json",
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -614,6 +691,7 @@ func TestBackupResourceFiltering(t *testing.T) {
},
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
},
},
}
@ -668,6 +746,10 @@ func TestCRDInclusion(t *testing.T) {
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/test.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/test.velero.io.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
@ -687,6 +769,7 @@ func TestCRDInclusion(t *testing.T) {
},
want: []string{
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
@ -709,6 +792,10 @@ func TestCRDInclusion(t *testing.T) {
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/test.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/test.velero.io.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
@ -729,6 +816,8 @@ func TestCRDInclusion(t *testing.T) {
want: []string{
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
@ -749,6 +838,7 @@ func TestCRDInclusion(t *testing.T) {
},
want: []string{
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
{
@ -772,6 +862,10 @@ func TestCRDInclusion(t *testing.T) {
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/cluster/test.velero.io.json",
"resources/volumesnapshotlocations.velero.io/namespaces/foo/vsl-1.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/backups.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/volumesnapshotlocations.velero.io.json",
"resources/customresourcedefinitions.apiextensions.k8s.io/v1beta1-preferredversion/cluster/test.velero.io.json",
"resources/volumesnapshotlocations.velero.io/v1-preferredversion/namespaces/foo/vsl-1.json",
},
},
}
@ -818,6 +912,8 @@ func TestBackupResourceCohabitation(t *testing.T) {
want: []string{
"resources/deployments.extensions/namespaces/foo/bar.json",
"resources/deployments.extensions/namespaces/zoo/raz.json",
"resources/deployments.extensions/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.extensions/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
@ -836,6 +932,8 @@ func TestBackupResourceCohabitation(t *testing.T) {
want: []string{
"resources/deployments.apps/namespaces/foo/bar.json",
"resources/deployments.apps/namespaces/zoo/raz.json",
"resources/deployments.apps/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
}
@ -877,7 +975,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
h.backupper.Backup(h.log, backup1, backup1File, nil, nil)
assertTarballContents(t, backup1File, "metadata/version", "resources/deployments.apps/namespaces/ns-1/deploy-1.json")
assertTarballContents(t, backup1File, "metadata/version", "resources/deployments.apps/namespaces/ns-1/deploy-1.json", "resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json")
// run and verify backup 2
backup2 := &Request{
@ -887,7 +985,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
h.backupper.Backup(h.log, backup2, backup2File, nil, nil)
assertTarballContents(t, backup2File, "metadata/version", "resources/deployments.apps/namespaces/ns-1/deploy-1.json")
assertTarballContents(t, backup2File, "metadata/version", "resources/deployments.apps/namespaces/ns-1/deploy-1.json", "resources/deployments.apps/v1-preferredversion/namespaces/ns-1/deploy-1.json")
}
// TestBackupResourceOrdering runs backups of the core API group and ensures that items are backed
@ -1431,6 +1529,9 @@ func TestBackupActionAdditionalItems(t *testing.T) {
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-2.json",
"resources/pods/namespaces/ns-3/pod-3.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-3/pod-3.json",
},
},
{
@ -1457,6 +1558,7 @@ func TestBackupActionAdditionalItems(t *testing.T) {
},
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
},
},
{
@ -1488,6 +1590,9 @@ func TestBackupActionAdditionalItems(t *testing.T) {
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/persistentvolumes/cluster/pv-1.json",
"resources/persistentvolumes/cluster/pv-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-2.json",
},
},
{
@ -1516,6 +1621,7 @@ func TestBackupActionAdditionalItems(t *testing.T) {
},
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
},
},
{
@ -1546,6 +1652,8 @@ func TestBackupActionAdditionalItems(t *testing.T) {
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
},
},
{
@ -1575,6 +1683,8 @@ func TestBackupActionAdditionalItems(t *testing.T) {
want: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/persistentvolumes/cluster/pv-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/persistentvolumes/v1-preferredversion/cluster/pv-2.json",
},
},
@ -1604,6 +1714,8 @@ func TestBackupActionAdditionalItems(t *testing.T) {
want: []string{
"resources/pods/namespaces/ns-2/pod-2.json",
"resources/pods/namespaces/ns-3/pod-3.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-3/pod-3.json",
},
},
}
@ -2194,6 +2306,8 @@ func TestBackupWithHooks(t *testing.T) {
wantBackedUp: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
},
},
{
@ -2243,6 +2357,8 @@ func TestBackupWithHooks(t *testing.T) {
wantBackedUp: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/namespaces/ns-2/pod-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
},
},
{
@ -2297,6 +2413,7 @@ func TestBackupWithHooks(t *testing.T) {
},
wantBackedUp: []string{
"resources/pods/namespaces/ns-1/pod-1.json",
"resources/pods/v1-preferredversion/namespaces/ns-1/pod-1.json",
},
},
{
@ -2348,6 +2465,7 @@ func TestBackupWithHooks(t *testing.T) {
},
wantBackedUp: []string{
"resources/pods/namespaces/ns-2/pod-2.json",
"resources/pods/v1-preferredversion/namespaces/ns-2/pod-2.json",
},
},
}

View File

@ -91,7 +91,7 @@ func (f *defaultItemBackupperFactory) newItemBackupper(
}
type ItemBackupper interface {
backupItem(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error)
backupItem(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource, preferredGVR schema.GroupVersionResource) (bool, error)
}
type defaultItemBackupper struct {
@ -112,7 +112,7 @@ type defaultItemBackupper struct {
// namespaces IncludesExcludes list.
// In addition to the error return, backupItem also returns a bool indicating whether the item
// was actually backed up.
func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource) (bool, error) {
func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtime.Unstructured, groupResource schema.GroupResource, preferredGVR schema.GroupVersionResource) (bool, error) {
metadata, err := meta.Accessor(obj)
if err != nil {
return false, err
@ -254,11 +254,27 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
return false, kubeerrs.NewAggregate(backupErrs)
}
// group version of this object
// Used on filepath to backup up all groups and versions
version := resourceVersion(obj)
// Getting the preferred group version of this resource
preferredVersion := preferredGVR.Version
var filePath string
// API Group version is now part of path of backup as a subdirectory
// it will add a prefix to subdirectory name for the preferred version
versionPath := version
if version == preferredVersion {
versionPath = version + api.PreferredVersionDir
}
if namespace != "" {
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), api.NamespaceScopedDir, namespace, name+".json")
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), versionPath, api.NamespaceScopedDir, namespace, name+".json")
} else {
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), api.ClusterScopedDir, name+".json")
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), versionPath, api.ClusterScopedDir, name+".json")
}
itemBytes, err := json.Marshal(obj.UnstructuredContent())
@ -282,6 +298,33 @@ func (ib *defaultItemBackupper) backupItem(logger logrus.FieldLogger, obj runtim
return false, errors.WithStack(err)
}
// backing up the preferred version backup without API Group version on path - this is for backward compability
if version == preferredVersion {
if namespace != "" {
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), api.NamespaceScopedDir, namespace, name+".json")
} else {
filePath = filepath.Join(api.ResourcesDir, groupResource.String(), api.ClusterScopedDir, name+".json")
}
hdr = &tar.Header{
Name: filePath,
Size: int64(len(itemBytes)),
Typeflag: tar.TypeReg,
Mode: 0755,
ModTime: time.Now(),
}
if err := ib.tarWriter.WriteHeader(hdr); err != nil {
return false, errors.WithStack(err)
}
if _, err := ib.tarWriter.Write(itemBytes); err != nil {
return false, errors.WithStack(err)
}
}
return true, nil
}
@ -352,7 +395,7 @@ func (ib *defaultItemBackupper) executeActions(
return nil, errors.WithStack(err)
}
if _, err = ib.additionalItemBackupper.backupItem(log, additionalItem, gvr.GroupResource()); err != nil {
if _, err = ib.additionalItemBackupper.backupItem(log, additionalItem, gvr.GroupResource(), gvr); err != nil {
return nil, err
}
}
@ -524,3 +567,10 @@ func resourceKey(obj runtime.Unstructured) string {
gvk := obj.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s", gvk.GroupVersion().String(), gvk.Kind)
}
// resourceVersion returns a string representing the object's API Version (e.g.
// v1 if item belongs to apps/v1
func resourceVersion(obj runtime.Unstructured) string {
gvk := obj.GetObjectKind().GroupVersionKind()
return gvk.Version
}

View File

@ -110,6 +110,12 @@ func (rb *defaultResourceBackupper) backupResource(group *metav1.APIResourceList
}
gr := schema.GroupResource{Group: gv.Group, Resource: resource.Name}
// Getting the preferred group version of this resource
preferredGVR, _, err := rb.discoveryHelper.ResourceFor(gr.WithVersion(""))
if err != nil {
return errors.WithStack(err)
}
clusterScoped := !resource.Namespaced
// If the resource we are backing up is NOT namespaces, and it is cluster-scoped, check to see if
@ -194,7 +200,7 @@ func (rb *defaultResourceBackupper) backupResource(group *metav1.APIResourceList
continue
}
if _, err := itemBackupper.backupItem(log, unstructured, gr); err != nil {
if _, err := itemBackupper.backupItem(log, unstructured, gr, preferredGVR); err != nil {
log.WithError(errors.WithStack(err)).Error("Error backing up namespace")
}
}
@ -233,7 +239,7 @@ func (rb *defaultResourceBackupper) backupResource(group *metav1.APIResourceList
// do the backup
for _, item := range unstructuredList.Items {
if rb.backupItem(log, gr, itemBackupper, &item) {
if rb.backupItem(log, gr, itemBackupper, &item, preferredGVR) {
backedUpItem = true
}
}
@ -254,6 +260,7 @@ func (rb *defaultResourceBackupper) backupItem(
gr schema.GroupResource,
itemBackupper ItemBackupper,
unstructured runtime.Unstructured,
preferredGVR schema.GroupVersionResource,
) bool {
metadata, err := meta.Accessor(unstructured)
if err != nil {
@ -271,7 +278,7 @@ func (rb *defaultResourceBackupper) backupItem(
return false
}
backedUpItem, err := itemBackupper.backupItem(log, unstructured, gr)
backedUpItem, err := itemBackupper.backupItem(log, unstructured, gr, preferredGVR)
if aggregate, ok := err.(kubeerrs.Aggregate); ok {
log.Infof("%d errors encountered backup up item", len(aggregate.Errors()))
// log each error separately so we get error location info in the log, and an
@ -324,7 +331,7 @@ func (rb *defaultResourceBackupper) backupCRD(log logrus.FieldLogger, gr schema.
}
log.Infof("Found associated CRD %s to add to backup", gr.String())
rb.backupItem(log, gvr.GroupResource(), itemBackupper, unstructured)
rb.backupItem(log, gvr.GroupResource(), itemBackupper, unstructured, gvr)
}
// getNamespacesToList examines ie and resolves the includes and excludes to a full list of

View File

@ -320,9 +320,12 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup) *pkg
Backup: backup.DeepCopy(), // don't modify items in the cache
}
// set backup version
// set backup major version - deprecated, use Status.FormatVersion
request.Status.Version = pkgbackup.BackupVersion
// set backup major, minor, and patch version
request.Status.FormatVersion = pkgbackup.BackupFormatVersion
if request.Spec.TTL.Duration == 0 {
// set default backup TTL
request.Spec.TTL.Duration = c.defaultBackupTTL

View File

@ -374,6 +374,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
Version: 1,
FormatVersion: "1.1.0",
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
Expiration: &timestamp,
@ -407,6 +408,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
Version: 1,
FormatVersion: "1.1.0",
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
Expiration: &timestamp,
@ -443,6 +445,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
Version: 1,
FormatVersion: "1.1.0",
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
Expiration: &timestamp,
@ -477,6 +480,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
Version: 1,
FormatVersion: "1.1.0",
Expiration: &metav1.Time{now.Add(10 * time.Minute)},
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
@ -511,6 +515,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseCompleted,
Version: 1,
FormatVersion: "1.1.0",
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
Expiration: &timestamp,
@ -547,6 +552,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed,
Version: 1,
FormatVersion: "1.1.0",
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
Expiration: &timestamp,
@ -581,6 +587,7 @@ func TestProcessBackupCompletions(t *testing.T) {
Status: velerov1api.BackupStatus{
Phase: velerov1api.BackupPhaseFailed,
Version: 1,
FormatVersion: "1.1.0",
StartTimestamp: &timestamp,
CompletionTimestamp: &timestamp,
Expiration: &timestamp,

View File

@ -29,6 +29,8 @@ import (
"k8s.io/client-go/discovery"
"k8s.io/client-go/restmapper"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/features"
kcmdutil "github.com/vmware-tanzu/velero/third_party/kubernetes/pkg/kubectl/cmd/util"
)
@ -57,7 +59,11 @@ type Helper interface {
}
type serverResourcesInterface interface {
// ServerPreferredResources() is used to populate Resources() with only Preferred Versions - this is the default
ServerPreferredResources() ([]*metav1.APIResourceList, error)
// ServerGroupsAndResources returns supported groups and resources for *all* groups and versions
// Used to populate Resources() if feature flag is passed
ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)
}
type helper struct {
@ -112,14 +118,28 @@ func (h *helper) Refresh() error {
return errors.WithStack(err)
}
preferredResources, err := refreshServerPreferredResources(h.discoveryClient, h.logger)
if err != nil {
return errors.WithStack(err)
var serverResources []*metav1.APIResourceList
if features.IsEnabled(velerov1api.APIGroupVersionsFeatureFlag) {
// ServerGroupsAndResources returns all APIGroup and APIResouceList - not only preferred versions
_, serverAllResources, err := refreshServerGroupsAndResources(h.discoveryClient, h.logger)
if err != nil {
return errors.WithStack(err)
}
h.logger.Info("The '%s' feature flag was specified, using all API group versions.", velerov1api.APIGroupVersionsFeatureFlag)
serverResources = serverAllResources
} else {
// ServerPreferredResources() returns only preferred APIGroup - this is the default since no feature flag has been passed
serverPreferredResources, err := refreshServerPreferredResources(h.discoveryClient, h.logger)
if err != nil {
return errors.WithStack(err)
}
serverResources = serverPreferredResources
}
h.resources = discovery.FilteredBy(
discovery.ResourcePredicateFunc(filterByVerbs),
preferredResources,
serverResources,
)
sortResources(h.resources)
@ -172,6 +192,19 @@ func refreshServerPreferredResources(discoveryClient serverResourcesInterface, l
return preferredResources, err
}
func refreshServerGroupsAndResources(discoveryClient serverResourcesInterface, logger logrus.FieldLogger) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
serverGroups, serverResources, err := discoveryClient.ServerGroupsAndResources()
if err != nil {
if discoveryErr, ok := err.(*discovery.ErrGroupDiscoveryFailed); ok {
for groupVersion, err := range discoveryErr.Groups {
logger.WithError(err).Warnf("Failed to discover group: %v", groupVersion)
}
return serverGroups, serverResources, nil
}
}
return serverGroups, serverResources, err
}
func filterByVerbs(groupVersion string, r *metav1.APIResource) bool {
return discovery.SupportsAllVerbs{Verbs: []string{"list", "create", "get", "delete"}}.Match(groupVersion, r)
}

View File

@ -151,6 +151,7 @@ func TestRefreshServerPreferredResources(t *testing.T) {
tests := []struct {
name string
resourceList []*metav1.APIResourceList
apiGroup []*metav1.APIGroup
failedGroups map[schema.GroupVersion]error
returnError error
}{
@ -183,7 +184,7 @@ func TestRefreshServerPreferredResources(t *testing.T) {
formatFlag := logging.FormatText
for _, test := range tests {
fakeServer := velerotest.NewFakeServerResourcesInterface(test.resourceList, test.failedGroups, test.returnError)
fakeServer := velerotest.NewFakeServerResourcesInterface(test.resourceList, test.apiGroup, test.failedGroups, test.returnError)
t.Run(test.name, func(t *testing.T) {
resources, err := refreshServerPreferredResources(fakeServer, logging.DefaultLogger(logrus.DebugLevel, formatFlag))
if test.returnError != nil {

File diff suppressed because one or more lines are too long

View File

@ -340,6 +340,10 @@ spec:
format: date-time
nullable: true
type: string
formatVersion:
description: FormatVersion is the backup format version, including major,
minor, and patch version.
type: string
phase:
description: Phase is the current state of the Backup.
enum:
@ -366,7 +370,8 @@ spec:
nullable: true
type: array
version:
description: Version is the backup format version.
description: 'Version is the backup format major version. Deprecated:
Please see FormatVersion'
type: integer
volumeSnapshotsAttempted:
description: VolumeSnapshotsAttempted is the total number of attempted

View File

@ -145,6 +145,7 @@ func (dh *FakeDiscoveryHelper) APIGroups() []metav1.APIGroup {
type FakeServerResourcesInterface struct {
ResourceList []*metav1.APIResourceList
ApiGroup []*metav1.APIGroup
FailedGroups map[schema.GroupVersion]error
ReturnError error
}
@ -159,9 +160,20 @@ func (di *FakeServerResourcesInterface) ServerPreferredResources() ([]*metav1.AP
return di.ResourceList, &discovery.ErrGroupDiscoveryFailed{Groups: di.FailedGroups}
}
func NewFakeServerResourcesInterface(resourceList []*metav1.APIResourceList, failedGroups map[schema.GroupVersion]error, returnError error) *FakeServerResourcesInterface {
func (di *FakeServerResourcesInterface) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
if di.ReturnError != nil {
return di.ApiGroup, di.ResourceList, di.ReturnError
}
if di.FailedGroups == nil || len(di.FailedGroups) == 0 {
return di.ApiGroup, di.ResourceList, nil
}
return di.ApiGroup, di.ResourceList, &discovery.ErrGroupDiscoveryFailed{Groups: di.FailedGroups}
}
func NewFakeServerResourcesInterface(resourceList []*metav1.APIResourceList, apiGroup []*metav1.APIGroup, failedGroups map[schema.GroupVersion]error, returnError error) *FakeServerResourcesInterface {
helper := &FakeServerResourcesInterface{
ResourceList: resourceList,
ApiGroup: apiGroup,
FailedGroups: failedGroups,
ReturnError: returnError,
}

View File

@ -42,6 +42,7 @@ rootBucket/
},
"status": {
"version": 1,
"formatVersion": "1.1.0",
"expiration": "2017-08-01T13:39:15Z",
"phase": "Completed",
"volumeBackups": {
@ -57,7 +58,129 @@ rootBucket/
```
Note that this file includes detailed info about your volume snapshots in the `status.volumeBackups` field, which can be helpful if you want to manually check them in your cloud provider GUI.
## file format version: 1
## Output File Format Versioning
The Velero output file format is intended to be relatively stable, but may change over time in order to support new features.
In order to accommodate this, Velero follows [Semantic Versioning](http://semver.org/) for the file format version.
Minor and patch versions will indicate backwards-compatible changes that previous versions of Velero can restore, including new directories or files.
A major version would indicate that a version of Velero older than the version that created the backup could not restore it, usually because of moved or renamed directories or files.
Major versions of the file format will be incremented with major version releases of Velero.
However, a major version release of Velero does not necessarily mean that the backup format version changed - Velero 3.0 could still use backup file format 2.0, as an example.
## Versions
### File Format Version: 1.1 (Current)
In version 1.1, we have added the support of API groups versions as part of the backup (previously, only the preferred version of each API Groups was backed up). Each resource has one or more sub-directories, one sub-directory for each supported version of the API group. The preferred version API Group of each resource has the suffix "-preferredversion" as part of the sub-directory name. For backward compatibility, we kept the classic directory structure without the API Group version, which sits on the same level as the API Group sub-directory versions.
By default, only the preferred API group of each resource is backed up.
In order to take a backup of all API group versions, you need to run the Velero server with `--features=EnableAPIGroupVersions` feature flag. This is an experimental flag and the restore logic to handle multiple API Group Versions will be added in the future.
When unzipped, a typical backup directory (e.g. `backup1234.tar.gz`) taken with this file format version looks like the following (with the feature flag):
```
resources/
persistentvolumes/
cluster/
pv01.json
...
v1-preferredversion/
cluster/
pv01.json
...
configmaps/
namespaces/
namespace1/
myconfigmap.json
...
namespace2/
...
v1-preferredversion/
namespaces/
namespace1/
myconfigmap.json
...
namespace2/
...
pods/
namespaces/
namespace1/
mypod.json
...
namespace2/
...
v1-preferredversion/
namespaces/
namespace1/
mypod.json
...
namespace2/
...
jobs.batch/
namespaces/
namespace1/
awesome-job.json
...
namespace2/
...
v1-preferredversion/
namespaces/
namespace1/
awesome-job.json
...
namespace2/
...
deployments/
namespaces/
namespace1/
cool-deployment.json
...
namespace2/
...
v1-preferredversion/
namespaces/
namespace1/
cool-deployment.json
...
namespace2/
...
horizontalpodautoscalers.autoscaling/
namespaces/
namespace1/
hpa-to-the-rescue.json
...
namespace2/
...
v1-preferredversion/
namespaces/
namespace1/
hpa-to-the-rescue.json
...
namespace2/
...
v2beta1/
namespaces/
namespace1/
hpa-to-the-rescue.json
...
namespace2/
...
v2beta2/
namespaces/
namespace1/
hpa-to-the-rescue.json
...
namespace2/
...
...
```
### File Format Version: 1
When unzipped, a typical backup directory (e.g. `backup1234.tar.gz`) looks like the following:
@ -97,17 +220,3 @@ resources/
...
...
```
## Output File Format Versioning
The Velero output file format is intended to be relatively stable, but may change over time in order to support new features.
In order to accommodate this, Velero follows [Semantic Versioning](http://semver.org/) for the file format version.
Minor and patch versions will indicate backwards-compatible changes that previous versions of Velero can restore, including new directories or files.
A major version would indicate that a version of Velero older than the version that created the backup could not restore it, usually because of moved or renamed directories or files.
Major versions of the file format will be incremented with major version releases of Velero.
However, a major version release of Velero does not necessarily mean that the backup format version changed - Velero 3.0 could still use backup file format 2.0, as an example.