Merge remote-tracking branch 'upstream/main'

Signed-off-by: MatthieuFin <matthieu2717@gmail.com>
This commit is contained in:
MatthieuFin
2022-02-23 15:03:15 +01:00
442 changed files with 25774 additions and 6017 deletions
+4
View File
@@ -46,4 +46,8 @@ const (
// APIGroupVersionsFeatureFlag is the feature flag string that defines whether or not to handle multiple API Group Versions
APIGroupVersionsFeatureFlag = "EnableAPIGroupVersions"
// UploadProgressFeatureFlag is the feature flag string that defines whether or not upload progress monitoring is enabled
// and whether or not ItemSnapshotters should be invoked
UploadProgressFeatureFlag = "EnableUploadProgress"
)
+2 -1
View File
@@ -25,13 +25,14 @@ type DownloadRequestSpec struct {
}
// DownloadTargetKind represents what type of file to download.
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupResourceList;RestoreLog;RestoreResults
// +kubebuilder:validation:Enum=BackupLog;BackupContents;BackupVolumeSnapshots;BackupItemSnapshots;BackupResourceList;RestoreLog;RestoreResults
type DownloadTargetKind string
const (
DownloadTargetKindBackupLog DownloadTargetKind = "BackupLog"
DownloadTargetKindBackupContents DownloadTargetKind = "BackupContents"
DownloadTargetKindBackupVolumeSnapshots DownloadTargetKind = "BackupVolumeSnapshots"
DownloadTargetKindBackupItemSnapshots DownloadTargetKind = "BackupItemSnapshots"
DownloadTargetKindBackupResourceList DownloadTargetKind = "BackupResourceList"
DownloadTargetKindRestoreLog DownloadTargetKind = "RestoreLog"
DownloadTargetKindRestoreResults DownloadTargetKind = "RestoreResults"
@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
+27 -45
View File
@@ -1,5 +1,5 @@
/*
Copyright the Velero contributors.
Copyright the Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -33,7 +33,6 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
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/schema"
"k8s.io/apimachinery/pkg/types"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
@@ -44,9 +43,11 @@ import (
"github.com/vmware-tanzu/velero/pkg/discovery"
velerov1client "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/typed/velero/v1"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/restic"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
)
@@ -62,6 +63,9 @@ type Backupper interface {
// Backup takes a backup using the specification in the velerov1api.Backup and writes backup and log data
// to the given writers.
Backup(logger logrus.FieldLogger, backup *Request, backupFile io.Writer, actions []velero.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error
BackupWithResolvers(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer,
backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver,
volumeSnapshotterGetter VolumeSnapshotterGetter) error
}
// kubernetesBackupper implements Backupper.
@@ -76,14 +80,6 @@ type kubernetesBackupper struct {
clientPageSize int
}
type resolvedAction struct {
velero.BackupItemAction
resourceIncludesExcludes *collections.IncludesExcludes
namespaceIncludesExcludes *collections.IncludesExcludes
selector labels.Selector
}
func (i *itemKey) String() string {
return fmt.Sprintf("resource=%s,namespace=%s,name=%s", i.resource, i.namespace, i.name)
}
@@ -121,38 +117,6 @@ func NewKubernetesBackupper(
}, nil
}
func resolveActions(actions []velero.BackupItemAction, helper discovery.Helper) ([]resolvedAction, error) {
var resolved []resolvedAction
for _, action := range actions {
resourceSelector, err := action.AppliesTo()
if err != nil {
return nil, err
}
resources := collections.GetResourceIncludesExcludes(helper, resourceSelector.IncludedResources, resourceSelector.ExcludedResources)
namespaces := collections.NewIncludesExcludes().Includes(resourceSelector.IncludedNamespaces...).Excludes(resourceSelector.ExcludedNamespaces...)
selector := labels.Everything()
if resourceSelector.LabelSelector != "" {
if selector, err = labels.Parse(resourceSelector.LabelSelector); err != nil {
return nil, err
}
}
res := resolvedAction{
BackupItemAction: action,
resourceIncludesExcludes: resources,
namespaceIncludesExcludes: namespaces,
selector: selector,
}
resolved = append(resolved, res)
}
return resolved, nil
}
// getNamespaceIncludesExcludes returns an IncludesExcludes list containing which namespaces to
// include and exclude from the backup.
func getNamespaceIncludesExcludes(backup *velerov1api.Backup) *collections.IncludesExcludes {
@@ -205,7 +169,20 @@ type VolumeSnapshotterGetter interface {
// a complete backup failure is returned. Errors that constitute partial failures (i.e. failures to
// back up individual resources that don't prevent the backup from continuing to be processed) are logged
// to the backup log.
func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer, actions []velero.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error {
func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Request, backupFile io.Writer,
actions []velero.BackupItemAction, volumeSnapshotterGetter VolumeSnapshotterGetter) error {
backupItemActions := framework.NewBackupItemActionResolver(actions)
itemSnapshotters := framework.NewItemSnapshotterResolver(nil)
return kb.BackupWithResolvers(log, backupRequest, backupFile, backupItemActions, itemSnapshotters,
volumeSnapshotterGetter)
}
func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger,
backupRequest *Request,
backupFile io.Writer,
backupItemActionResolver framework.BackupItemActionResolver,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
volumeSnapshotterGetter VolumeSnapshotterGetter) error {
gzippedData := gzip.NewWriter(backupFile)
defer gzippedData.Close()
@@ -224,7 +201,7 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req
backupRequest.ResourceIncludesExcludes = collections.GetResourceIncludesExcludes(kb.discoveryHelper, backupRequest.Spec.IncludedResources, backupRequest.Spec.ExcludedResources)
log.Infof("Including resources: %s", backupRequest.ResourceIncludesExcludes.IncludesString())
log.Infof("Excluding resources: %s", backupRequest.ResourceIncludesExcludes.ExcludesString())
log.Infof("Backing up all pod volumes using restic: %t", *backupRequest.Backup.Spec.DefaultVolumesToRestic)
log.Infof("Backing up all pod volumes using Restic: %t", boolptr.IsSetToTrue(backupRequest.Backup.Spec.DefaultVolumesToRestic))
var err error
backupRequest.ResourceHooks, err = getResourceHooks(backupRequest.Spec.Hooks.Resources, kb.discoveryHelper)
@@ -232,7 +209,12 @@ func (kb *kubernetesBackupper) Backup(log logrus.FieldLogger, backupRequest *Req
return err
}
backupRequest.ResolvedActions, err = resolveActions(actions, kb.discoveryHelper)
backupRequest.ResolvedActions, err = backupItemActionResolver.ResolveActions(kb.discoveryHelper)
if err != nil {
return err
}
backupRequest.ResolvedItemSnapshotters, err = itemSnapshotterResolver.ResolveActions(kb.discoveryHelper)
if err != nil {
return err
}
+24
View File
@@ -970,6 +970,30 @@ func TestBackupResourceCohabitation(t *testing.T) {
"resources/deployments.apps/v1-preferredversion/namespaces/zoo/raz.json",
},
},
{
name: "when deployments exist that are not in the cohabitating groups those are backed up along with apps/deployments",
backup: defaultBackup().Result(),
apiResources: []*test.APIResource{
test.VeleroDeployments(
builder.ForTestCR("Deployment", "foo", "bar").Result(),
builder.ForTestCR("Deployment", "zoo", "raz").Result(),
),
test.Deployments(
builder.ForDeployment("foo", "bar").Result(),
builder.ForDeployment("zoo", "raz").Result(),
),
},
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",
"resources/deployments.velero.io/namespaces/foo/bar.json",
"resources/deployments.velero.io/namespaces/zoo/raz.json",
"resources/deployments.velero.io/v1-preferredversion/namespaces/foo/bar.json",
"resources/deployments.velero.io/v1-preferredversion/namespaces/zoo/raz.json",
},
},
}
for _, tc := range tests {
+51 -21
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/pkg/errors"
@@ -29,10 +30,10 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"github.com/vmware-tanzu/velero/internal/hook"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
@@ -304,26 +305,9 @@ func (ib *itemBackupper) executeActions(
metadata metav1.Object,
) (runtime.Unstructured, error) {
for _, action := range ib.backupRequest.ResolvedActions {
if !action.resourceIncludesExcludes.ShouldInclude(groupResource.String()) {
log.Debug("Skipping action because it does not apply to this resource")
if !action.ShouldUse(groupResource, namespace, metadata, log) {
continue
}
if namespace != "" && !action.namespaceIncludesExcludes.ShouldInclude(namespace) {
log.Debug("Skipping action because it does not apply to this namespace")
continue
}
if namespace == "" && !action.namespaceIncludesExcludes.IncludeEverything() {
log.Debug("Skipping action because resource is cluster-scoped and action only applies to specific namespaces")
continue
}
if !action.selector.Matches(labels.Set(metadata.GetLabels())) {
log.Debug("Skipping action because label selector does not match")
continue
}
log.Info("Executing custom action")
updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backupRequest.Backup)
@@ -395,7 +379,13 @@ func (ib *itemBackupper) volumeSnapshotter(snapshotLocation *velerov1api.VolumeS
// on PVs
const (
zoneLabelDeprecated = "failure-domain.beta.kubernetes.io/zone"
zoneLabel = "topology.kubernetes.io/zone"
// this is reused for nodeAffinity requirements
zoneLabel = "topology.kubernetes.io/zone"
awsEbsCsiZoneKey = "topology.ebs.csi.aws.com/zone"
azureCsiZoneKey = "topology.disk.csi.azure.com/zone"
gkeCsiZoneKey = "topology.gke.io/zone"
gkeZoneSeparator = "__"
)
// takePVSnapshot triggers a snapshot for the volume/disk underlying a PersistentVolume if the provided
@@ -432,7 +422,14 @@ func (ib *itemBackupper) takePVSnapshot(obj runtime.Unstructured, log logrus.Fie
log.Infof("label %q is not present on PersistentVolume, checking deprecated label...", zoneLabel)
pvFailureDomainZone, labelFound = pv.Labels[zoneLabelDeprecated]
if !labelFound {
var k string
log.Infof("label %q is not present on PersistentVolume", zoneLabelDeprecated)
k, pvFailureDomainZone = zoneFromPVNodeAffinity(pv, awsEbsCsiZoneKey, azureCsiZoneKey, gkeCsiZoneKey, zoneLabel, zoneLabelDeprecated)
if pvFailureDomainZone != "" {
log.Infof("zone info from nodeAffinity requirements: %s, key: %s", pvFailureDomainZone, k)
} else {
log.Infof("zone info not available in nodeAffinity requirements")
}
}
}
@@ -535,3 +532,36 @@ func resourceVersion(obj runtime.Unstructured) string {
gvk := obj.GetObjectKind().GroupVersionKind()
return gvk.Version
}
// zoneFromPVNodeAffinity iterates the node affinity requirement of a PV to
// get its availability zone, it returns the key merely for logging.
func zoneFromPVNodeAffinity(res *corev1api.PersistentVolume, topologyKeys ...string) (string, string) {
nodeAffinity := res.Spec.NodeAffinity
if nodeAffinity == nil {
return "", ""
}
keySet := sets.NewString(topologyKeys...)
providerGke := false
zones := make([]string, 0)
for _, term := range nodeAffinity.Required.NodeSelectorTerms {
if term.MatchExpressions == nil {
continue
}
for _, exp := range term.MatchExpressions {
if keySet.Has(exp.Key) && exp.Operator == "In" && len(exp.Values) > 0 {
if exp.Key == gkeCsiZoneKey {
providerGke = true
zones = append(zones, exp.Values[0])
} else {
return exp.Key, exp.Values[0]
}
}
}
}
if providerGke {
return gkeCsiZoneKey, strings.Join(zones, gkeZoneSeparator)
}
return "", ""
}
+125
View File
@@ -20,6 +20,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -45,3 +46,127 @@ func Test_resourceKey(t *testing.T) {
})
}
}
func Test_zoneFromPVNodeAffinity(t *testing.T) {
keys := []string{
awsEbsCsiZoneKey,
azureCsiZoneKey,
gkeCsiZoneKey,
zoneLabel,
zoneLabelDeprecated,
}
tests := []struct {
name string
pv *corev1api.PersistentVolume
wantKey string
wantValue string
}{
{
name: "AWS CSI Volume",
pv: builder.ForPersistentVolume("awscsi").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.ebs.csi.aws.com/zone",
"In", "us-east-2c").Result(),
).Result(),
).Result(),
wantKey: "topology.ebs.csi.aws.com/zone",
wantValue: "us-east-2c",
},
{
name: "Azure CSI Volume",
pv: builder.ForPersistentVolume("azurecsi").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.disk.csi.azure.com/zone",
"In", "us-central").Result(),
).Result(),
).Result(),
wantKey: "topology.disk.csi.azure.com/zone",
wantValue: "us-central",
},
{
name: "GCP CSI Volume",
pv: builder.ForPersistentVolume("gcpcsi").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.gke.io/zone",
"In", "us-west1-a").Result(),
).Result(),
).Result(),
wantKey: "topology.gke.io/zone",
wantValue: "us-west1-a",
},
{
name: "AWS CSI Volume with multiple zone value, returns the first",
pv: builder.ForPersistentVolume("awscsi").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.ebs.csi.aws.com/zone",
"In", "us-east-2c", "us-west").Result(),
).Result(),
).Result(),
wantKey: "topology.ebs.csi.aws.com/zone",
wantValue: "us-east-2c",
},
{
name: "Volume with no matching key",
pv: builder.ForPersistentVolume("no-matching-pv").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("some-key",
"In", "us-west").Result(),
).Result(),
).Result(),
wantKey: "",
wantValue: "",
},
{
name: "Volume with multiple valid keys, returns the first match", // it should never happen
pv: builder.ForPersistentVolume("multi-matching-pv").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.disk.csi.azure.com/zone",
"In", "us-central").Result(),
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.ebs.csi.aws.com/zone",
"In", "us-east-2c", "us-west").Result(),
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.ebs.csi.aws.com/zone",
"In", "unknown").Result(),
).Result(),
).Result(),
wantKey: "topology.disk.csi.azure.com/zone",
wantValue: "us-central",
},
{
/* an valid example of node affinity in a GKE's regional PV
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: topology.gke.io/zone
operator: In
values:
- us-central1-a
- matchExpressions:
- key: topology.gke.io/zone
operator: In
values:
- us-central1-c
*/
name: "Volume with multiple valid keys, and provider is gke, returns all valid entries's first zone value",
pv: builder.ForPersistentVolume("multi-matching-pv").NodeAffinityRequired(
builder.ForNodeSelector(
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.gke.io/zone",
"In", "us-central1-c").Result(),
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.gke.io/zone",
"In", "us-east-2c", "us-east-2b").Result(),
*builder.NewNodeSelectorTermBuilder().WithMatchExpression("topology.gke.io/zone",
"In", "europe-north1-a").Result(),
).Result(),
).Result(),
wantKey: "topology.gke.io/zone",
wantValue: "us-central1-c__us-east-2c__europe-north1-a",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k, v := zoneFromPVNodeAffinity(tt.pv, keys...)
assert.Equal(t, tt.wantKey, k)
assert.Equal(t, tt.wantValue, v)
})
}
}
+32 -39
View File
@@ -26,7 +26,7 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"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"
@@ -209,16 +209,18 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
}
if cohabitator, found := r.cohabitatingResources[resource.Name]; found {
if cohabitator.seen {
log.WithFields(
logrus.Fields{
"cohabitatingResource1": cohabitator.groupResource1.String(),
"cohabitatingResource2": cohabitator.groupResource2.String(),
},
).Infof("Skipping resource because it cohabitates and we've already processed it")
return nil, nil
if gv.Group == cohabitator.groupResource1.Group || gv.Group == cohabitator.groupResource2.Group {
if cohabitator.seen {
log.WithFields(
logrus.Fields{
"cohabitatingResource1": cohabitator.groupResource1.String(),
"cohabitatingResource2": cohabitator.groupResource2.String(),
},
).Infof("Skipping resource because it cohabitates and we've already processed it")
return nil, nil
}
cohabitator.seen = true
}
cohabitator.seen = true
}
namespacesToList := getNamespacesToList(r.backupRequest.NamespaceIncludesExcludes)
@@ -293,7 +295,6 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
if selector := r.backupRequest.Spec.LabelSelector; selector != nil {
labelSelector = metav1.FormatLabelSelector(selector)
}
listOptions := metav1.ListOptions{LabelSelector: labelSelector}
log.Info("Listing items")
unstructuredItems := make([]unstructured.Unstructured, 0)
@@ -301,50 +302,42 @@ func (r *itemCollector) getResourceItems(log logrus.FieldLogger, gv schema.Group
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
listFunc := pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
list, err := resourceClient.List(listOptions)
if err != nil {
return nil, err
}
return list, nil
})
listPager := pager.New(listFunc)
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
var items []unstructured.Unstructured
err := listPager.EachListItem(context.Background(), listOptions, func(object runtime.Object) error {
item, isUnstructured := object.(*unstructured.Unstructured)
if !isUnstructured {
// We should never hit this
log.Error("Got type other than Unstructured from pager func")
return nil
list, paginated, err := listPager.List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
log.WithError(errors.WithStack(err)).Error("Error listing resources")
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)
}
items = append(items, *item)
unstructuredItems = append(unstructuredItems, *u)
return nil
})
if statusError, isStatusError := err.(*apierrors.StatusError); isStatusError && statusError.Status().Reason == metav1.StatusReasonExpired {
log.WithError(errors.WithStack(err)).Error("Error paging item list. Falling back on unpaginated list")
unstructuredList, err := resourceClient.List(listOptions)
if err != nil {
log.WithError(errors.WithStack(err)).Error("Error listing items")
continue
}
items = unstructuredList.Items
} else if err != nil {
log.WithError(errors.WithStack(err)).Error("Error paging item list")
if err != nil {
log.WithError(errors.WithStack(err)).Error("unable to understand paginated list")
continue
}
unstructuredItems = append(unstructuredItems, items...)
} else {
// If limit is not positive, do not use paging. Instead, request all items at once
unstructuredList, err := resourceClient.List(metav1.ListOptions{LabelSelector: labelSelector})
unstructuredItems = append(unstructuredItems, unstructuredList.Items...)
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))
+6 -5
View File
@@ -22,6 +22,7 @@ import (
"github.com/vmware-tanzu/velero/internal/hook"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/util/collections"
"github.com/vmware-tanzu/velero/pkg/volume"
)
@@ -42,11 +43,11 @@ type Request struct {
NamespaceIncludesExcludes *collections.IncludesExcludes
ResourceIncludesExcludes *collections.IncludesExcludes
ResourceHooks []hook.ResourceHook
ResolvedActions []resolvedAction
VolumeSnapshots []*volume.Snapshot
PodVolumeBackups []*velerov1api.PodVolumeBackup
BackedUpItems map[itemKey]struct{}
ResolvedActions []framework.BackupItemResolvedAction
ResolvedItemSnapshotters []framework.ItemSnapshotterResolvedAction
VolumeSnapshots []*volume.Snapshot
PodVolumeBackups []*velerov1api.PodVolumeBackup
BackedUpItems map[itemKey]struct{}
}
// BackupResourceList returns the list of backed up resources grouped by the API
+64
View File
@@ -0,0 +1,64 @@
package builder
import corev1api "k8s.io/api/core/v1"
// NodeSelectorBuilder builds NodeSelector objects
type NodeSelectorBuilder struct {
object *corev1api.NodeSelector
}
// ForNodeSelector returns the NodeSelectorBuilder instance with given terms
func ForNodeSelector(term ...corev1api.NodeSelectorTerm) *NodeSelectorBuilder {
return &NodeSelectorBuilder{
object: &corev1api.NodeSelector{
NodeSelectorTerms: term,
},
}
}
// Result returns the built NodeSelector
func (b *NodeSelectorBuilder) Result() *corev1api.NodeSelector {
return b.object
}
// NodeSelectorTermBuilder builds NodeSelectorTerm objects.
type NodeSelectorTermBuilder struct {
object *corev1api.NodeSelectorTerm
}
// NewNodeSelectorTermBuilder initializes an instance of NodeSelectorTermBuilder
func NewNodeSelectorTermBuilder() *NodeSelectorTermBuilder {
return &NodeSelectorTermBuilder{
object: &corev1api.NodeSelectorTerm{
MatchExpressions: make([]corev1api.NodeSelectorRequirement, 0),
MatchFields: make([]corev1api.NodeSelectorRequirement, 0),
},
}
}
// WithMatchExpression appends the MatchExpression to the NodeSelectorTerm
func (ntb *NodeSelectorTermBuilder) WithMatchExpression(key string, op string, values ...string) *NodeSelectorTermBuilder {
req := corev1api.NodeSelectorRequirement{
Key: key,
Operator: corev1api.NodeSelectorOperator(op),
Values: values,
}
ntb.object.MatchExpressions = append(ntb.object.MatchExpressions, req)
return ntb
}
// WithMatchField appends the MatchField to the NodeSelectorTerm
func (ntb *NodeSelectorTermBuilder) WithMatchField(key string, op string, values ...string) *NodeSelectorTermBuilder {
req := corev1api.NodeSelectorRequirement{
Key: key,
Operator: corev1api.NodeSelectorOperator(op),
Values: values,
}
ntb.object.MatchFields = append(ntb.object.MatchFields, req)
return ntb
}
// Result returns the built NodeSelectorTerm
func (ntb *NodeSelectorTermBuilder) Result() *corev1api.NodeSelectorTerm {
return ntb.object
}
+8
View File
@@ -94,3 +94,11 @@ func (b *PersistentVolumeBuilder) StorageClass(name string) *PersistentVolumeBui
b.object.Spec.StorageClassName = name
return b
}
// NodeAffinityRequired sets the PersistentVolume's NodeAffinity Requirement.
func (b *PersistentVolumeBuilder) NodeAffinityRequired(req *corev1api.NodeSelector) *PersistentVolumeBuilder {
b.object.Spec.NodeAffinity = &corev1api.VolumeNodeAffinity{
Required: req,
}
return b
}
+57
View File
@@ -0,0 +1,57 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ServiceBuilder builds Service objects.
type ServiceBuilder struct {
object *corev1api.Service
}
// ForService is the constructor for a ServiceBuilder.
func ForService(ns, name string) *ServiceBuilder {
return &ServiceBuilder{
object: &corev1api.Service{
TypeMeta: metav1.TypeMeta{
APIVersion: corev1api.SchemeGroupVersion.String(),
Kind: "Service",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
},
}
}
// Result returns the built Service.
func (s *ServiceBuilder) Result() *corev1api.Service {
return s.object
}
// ObjectMeta applies functional options to the Service's ObjectMeta.
func (s *ServiceBuilder) ObjectMeta(opts ...ObjectMetaOpt) *ServiceBuilder {
for _, opt := range opts {
opt(s.object)
}
return s
}
+62
View File
@@ -0,0 +1,62 @@
/*
Copyright 2021 the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// StatefulSetBuilder builds StatefulSet objects.
type StatefulSetBuilder struct {
object *appsv1.StatefulSet
}
// ForStatefulSet is the constructor for a StatefulSetBuilder.
func ForStatefulSet(ns, name string) *StatefulSetBuilder {
return &StatefulSetBuilder{
object: &appsv1.StatefulSet{
TypeMeta: metav1.TypeMeta{
APIVersion: appsv1.SchemeGroupVersion.String(),
Kind: "StatefulSet",
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
Spec: appsv1.StatefulSetSpec{
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{},
},
},
}
}
// Result returns the built StatefulSet.
func (b *StatefulSetBuilder) Result() *appsv1.StatefulSet {
return b.object
}
// StorageClass sets the StatefulSet's VolumeClaimTemplates storage class name.
func (b *StatefulSetBuilder) StorageClass(names ...string) *StatefulSetBuilder {
for _, name := range names {
nameTmp := name
b.object.Spec.VolumeClaimTemplates = append(b.object.Spec.VolumeClaimTemplates,
corev1.PersistentVolumeClaim{Spec: corev1.PersistentVolumeClaimSpec{StorageClassName: &nameTmp}})
}
return b
}
+28 -1
View File
@@ -23,7 +23,8 @@ import (
// StorageClassBuilder builds StorageClass objects.
type StorageClassBuilder struct {
object *storagev1api.StorageClass
object *storagev1api.StorageClass
objectSlice []*storagev1api.StorageClass
}
// ForStorageClass is the constructor for a StorageClassBuilder.
@@ -54,3 +55,29 @@ func (b *StorageClassBuilder) ObjectMeta(opts ...ObjectMetaOpt) *StorageClassBui
return b
}
// ForStorageClassSlice is the constructor for a storageClassSlice in StorageClassBuilder.
func ForStorageClassSlice(names ...string) *StorageClassBuilder {
var storageClassSlice []*storagev1api.StorageClass
for _, name := range names {
storageClass := &storagev1api.StorageClass{
TypeMeta: metav1.TypeMeta{
APIVersion: storagev1api.SchemeGroupVersion.String(),
Kind: "StorageClass",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
}
storageClassSlice = append(storageClassSlice, storageClass)
}
return &StorageClassBuilder{
objectSlice: storageClassSlice,
}
}
// SliceResult returns the built StorageClass slice.
func (b *StorageClassBuilder) SliceResult() []*storagev1api.StorageClass {
return b.objectSlice
}
+77
View File
@@ -0,0 +1,77 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package builder
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
// CustomResourceBuilder builds objects based on velero APIVersion CRDs.
type TestCRBuilder struct {
object *TestCR
}
// ForTestCR is the constructor for a TestCRBuilder.
func ForTestCR(crdKind, ns, name string) *TestCRBuilder {
return &TestCRBuilder{
object: &TestCR{
TypeMeta: metav1.TypeMeta{
APIVersion: velerov1api.SchemeGroupVersion.String(),
Kind: crdKind,
},
ObjectMeta: metav1.ObjectMeta{
Namespace: ns,
Name: name,
},
},
}
}
// Result returns the built TestCR.
func (b *TestCRBuilder) Result() *TestCR {
return b.object
}
// ObjectMeta applies functional options to the TestCR's ObjectMeta.
func (b *TestCRBuilder) ObjectMeta(opts ...ObjectMetaOpt) *TestCRBuilder {
for _, opt := range opts {
opt(b.object)
}
return b
}
type TestCR struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`
// +optional
Spec TestCRSpec `json:"spec,omitempty"`
// +optional
Status TestCRStatus `json:"status,omitempty"`
}
type TestCRSpec struct {
}
type TestCRStatus struct {
}
+12 -5
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/tools/cache"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
@@ -37,6 +38,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
veleroclient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
v1 "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
"github.com/vmware-tanzu/velero/pkg/util/collections"
)
const DefaultBackupTTL time.Duration = 30 * 24 * time.Hour
@@ -162,6 +164,11 @@ func (o *CreateOptions) Validate(c *cobra.Command, args []string, f client.Facto
return fmt.Errorf("A backup name is required, unless you are creating based on a schedule.")
}
errs := collections.ValidateNamespaceIncludesExcludes(o.IncludeNamespaces, o.ExcludeNamespaces)
if len(errs) > 0 {
return kubeerrs.NewAggregate(errs)
}
if o.StorageLocation != "" {
location := &velerov1api.BackupStorageLocation{}
if err := client.Get(context.Background(), kbclient.ObjectKey{
@@ -284,11 +291,11 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
return nil
}
// parseOrderedResources converts to map of Kinds to an ordered list of specific resources of that Kind.
// ParseOrderedResources converts to map of Kinds to an ordered list of specific resources of that Kind.
// Resource names in the list are in format 'namespace/resourcename' and separated by commas.
// Key-value pairs in the mapping are separated by semi-colon.
// Ex: 'pods=ns1/pod1,ns1/pod2;persistentvolumeclaims=ns1/pvc4,ns1/pvc8'.
func parseOrderedResources(orderMapStr string) (map[string]string, error) {
func ParseOrderedResources(orderMapStr string) (map[string]string, error) {
entries := strings.Split(orderMapStr, ";")
if len(entries) == 0 {
return nil, fmt.Errorf("Invalid OrderedResources '%s'.", orderMapStr)
@@ -315,7 +322,7 @@ func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, erro
return nil, err
}
if o.Name == "" {
o.Name = schedule.TimestampedName(time.Now())
o.Name = schedule.TimestampedName(time.Now().UTC())
}
backupBuilder = builder.ForBackup(namespace, o.Name).
FromSchedule(schedule)
@@ -330,7 +337,7 @@ func (o *CreateOptions) BuildBackup(namespace string) (*velerov1api.Backup, erro
StorageLocation(o.StorageLocation).
VolumeSnapshotLocations(o.SnapshotLocations...)
if len(o.OrderedResources) > 0 {
orders, err := parseOrderedResources(o.OrderedResources)
orders, err := ParseOrderedResources(o.OrderedResources)
if err != nil {
return nil, err
}
+5 -5
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -34,7 +34,7 @@ func TestCreateOptions_BuildBackup(t *testing.T) {
o := NewCreateOptions()
o.Labels.Set("velero.io/test=true")
o.OrderedResources = "pods=p1,p2;persistentvolumeclaims=pvc1,pvc2"
orders, err := parseOrderedResources(o.OrderedResources)
orders, err := ParseOrderedResources(o.OrderedResources)
assert.NoError(t, err)
backup, err := o.BuildBackup(testNamespace)
@@ -100,10 +100,10 @@ func TestCreateOptions_BuildBackupFromSchedule(t *testing.T) {
}
func TestCreateOptions_OrderedResources(t *testing.T) {
orderedResources, err := parseOrderedResources("pods= ns1/p1; ns1/p2; persistentvolumeclaims=ns2/pvc1, ns2/pvc2")
orderedResources, err := ParseOrderedResources("pods= ns1/p1; ns1/p2; persistentvolumeclaims=ns2/pvc1, ns2/pvc2")
assert.NotNil(t, err)
orderedResources, err = parseOrderedResources("pods= ns1/p1,ns1/p2 ; persistentvolumeclaims=ns2/pvc1,ns2/pvc2")
orderedResources, err = ParseOrderedResources("pods= ns1/p1,ns1/p2 ; persistentvolumeclaims=ns2/pvc1,ns2/pvc2")
assert.NoError(t, err)
expectedResources := map[string]string{
@@ -112,7 +112,7 @@ func TestCreateOptions_OrderedResources(t *testing.T) {
}
assert.Equal(t, orderedResources, expectedResources)
orderedResources, err = parseOrderedResources("pods= ns1/p1,ns1/p2 ; persistentvolumes=pv1,pv2")
orderedResources, err = ParseOrderedResources("pods= ns1/p1,ns1/p2 ; persistentvolumes=pv1,pv2")
assert.NoError(t, err)
expectedMixedResources := map[string]string{
+34 -28
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -78,6 +78,7 @@ func NewCreateOptions() *CreateOptions {
return &CreateOptions{
Credential: flag.NewMap(),
Config: flag.NewMap(),
Labels: flag.NewMap(),
AccessMode: flag.NewEnum(
string(velerov1api.BackupStorageLocationAccessModeReadWrite),
string(velerov1api.BackupStorageLocationAccessModeReadWrite),
@@ -133,39 +134,22 @@ func (o *CreateOptions) Complete(args []string, f client.Factory) error {
return nil
}
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
var backupSyncPeriod, validationFrequency *metav1.Duration
func (o *CreateOptions) BuildBackupStorageLocation(namespace string, setBackupSyncPeriod, setValidationFrequency bool) (*velerov1api.BackupStorageLocation, error) {
var caCertData []byte
if o.CACertFile != "" {
realPath, err := filepath.Abs(o.CACertFile)
if err != nil {
return err
return nil, err
}
caCertData, err = ioutil.ReadFile(realPath)
if err != nil {
return err
return nil, err
}
}
if c.Flags().Changed("backup-sync-period") {
backupSyncPeriod = &metav1.Duration{Duration: o.BackupSyncPeriod}
}
if c.Flags().Changed("validation-frequency") {
validationFrequency = &metav1.Duration{Duration: o.ValidationFrequency}
}
var secretName, secretKey string
for k, v := range o.Credential.Data() {
secretName = k
secretKey = v
break
}
backupStorageLocation := &velerov1api.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: f.Namespace(),
Namespace: namespace,
Name: o.Name,
Labels: o.Labels.Data(),
},
@@ -178,15 +162,37 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
CACert: caCertData,
},
},
Config: o.Config.Data(),
Credential: builder.ForSecretKeySelector(secretName, secretKey).Result(),
Default: o.DefaultBackupStorageLocation,
AccessMode: velerov1api.BackupStorageLocationAccessMode(o.AccessMode.String()),
BackupSyncPeriod: backupSyncPeriod,
ValidationFrequency: validationFrequency,
Config: o.Config.Data(),
Default: o.DefaultBackupStorageLocation,
AccessMode: velerov1api.BackupStorageLocationAccessMode(o.AccessMode.String()),
},
}
if setBackupSyncPeriod {
backupStorageLocation.Spec.BackupSyncPeriod = &metav1.Duration{Duration: o.BackupSyncPeriod}
}
if setValidationFrequency {
backupStorageLocation.Spec.ValidationFrequency = &metav1.Duration{Duration: o.ValidationFrequency}
}
for secretName, secretKey := range o.Credential.Data() {
backupStorageLocation.Spec.Credential = builder.ForSecretKeySelector(secretName, secretKey).Result()
break
}
return backupStorageLocation, nil
}
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
setBackupSyncPeriod := c.Flags().Changed("backup-sync-period")
setValidationFrequency := c.Flags().Changed("validation-frequency")
backupStorageLocation, err := o.BuildBackupStorageLocation(f.Namespace(), setBackupSyncPeriod, setValidationFrequency)
if err != nil {
return err
}
if printed, err := output.PrintWithFormat(c, backupStorageLocation); printed || err != nil {
return err
}
+89
View File
@@ -0,0 +1,89 @@
/*
Copyright the Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package backuplocation
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestBuildBackupStorageLocationSetsNamespace(t *testing.T) {
o := NewCreateOptions()
bsl, err := o.BuildBackupStorageLocation("velero-test-ns", false, false)
assert.NoError(t, err)
assert.Equal(t, "velero-test-ns", bsl.Namespace)
}
func TestBuildBackupStorageLocationSetsSyncPeriod(t *testing.T) {
o := NewCreateOptions()
o.BackupSyncPeriod = 2 * time.Minute
bsl, err := o.BuildBackupStorageLocation("velero-test-ns", false, false)
assert.NoError(t, err)
assert.Nil(t, bsl.Spec.BackupSyncPeriod)
bsl, err = o.BuildBackupStorageLocation("velero-test-ns", true, false)
assert.NoError(t, err)
assert.Equal(t, &metav1.Duration{Duration: 2 * time.Minute}, bsl.Spec.BackupSyncPeriod)
}
func TestBuildBackupStorageLocationSetsValidationFrequency(t *testing.T) {
o := NewCreateOptions()
o.ValidationFrequency = 2 * time.Minute
bsl, err := o.BuildBackupStorageLocation("velero-test-ns", false, false)
assert.NoError(t, err)
assert.Nil(t, bsl.Spec.ValidationFrequency)
bsl, err = o.BuildBackupStorageLocation("velero-test-ns", false, true)
assert.NoError(t, err)
assert.Equal(t, &metav1.Duration{Duration: 2 * time.Minute}, bsl.Spec.ValidationFrequency)
}
func TestBuildBackupStorageLocationSetsCredential(t *testing.T) {
o := NewCreateOptions()
bsl, err := o.BuildBackupStorageLocation("velero-test-ns", false, false)
assert.NoError(t, err)
assert.Nil(t, bsl.Spec.Credential)
setErr := o.Credential.Set("my-secret=key-from-secret")
assert.NoError(t, setErr)
bsl, err = o.BuildBackupStorageLocation("velero-test-ns", false, true)
assert.NoError(t, err)
assert.Equal(t, &v1.SecretKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: "my-secret"},
Key: "key-from-secret",
}, bsl.Spec.Credential)
}
func TestBuildBackupStorageLocationSetsLabels(t *testing.T) {
o := NewCreateOptions()
err := o.Labels.Set("key=value")
assert.NoError(t, err)
bsl, err := o.BuildBackupStorageLocation("velero-test-ns", false, false)
assert.NoError(t, err)
assert.Equal(t, map[string]string{"key": "value"}, bsl.Labels)
}
+61 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright 2020 the Velero contributors.
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -22,7 +22,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
@@ -34,6 +33,8 @@ import (
"github.com/vmware-tanzu/velero/pkg/cmd/cli"
)
const bslLabelKey = "velero.io/storage-location"
// NewDeleteCommand creates and returns a new cobra command for deleting backup-locations.
func NewDeleteCommand(f client.Factory, use string) *cobra.Command {
o := cli.NewDeleteOptions("backup-location")
@@ -120,7 +121,65 @@ func Run(f client.Factory, o *cli.DeleteOptions) error {
continue
}
fmt.Printf("Backup storage location %q deleted successfully.\n", location.Name)
// Delete backups associated with the deleted BSL.
backupList, err := findAssociatedBackups(kbClient, location.Name, f.Namespace())
if err != nil {
errs = append(errs, fmt.Errorf("find backups associated with BSL %q: %w", location.Name, err))
} else if deleteErrs := deleteBackups(kbClient, backupList); deleteErrs != nil {
errs = append(errs, deleteErrs...)
}
// Delete Restic repositories associated with the deleted BSL.
resticRepoList, err := findAssociatedResticRepos(kbClient, location.Name, f.Namespace())
if err != nil {
errs = append(errs, fmt.Errorf("find Restic repositories associated with BSL %q: %w", location.Name, err))
} else if deleteErrs := deleteResticRepos(kbClient, resticRepoList); deleteErrs != nil {
errs = append(errs, deleteErrs...)
}
}
return kubeerrs.NewAggregate(errs)
}
func findAssociatedBackups(client kbclient.Client, bslName, ns string) (velerov1api.BackupList, error) {
var backups velerov1api.BackupList
err := client.List(context.Background(), &backups, &kbclient.ListOptions{
Namespace: ns,
Raw: &metav1.ListOptions{LabelSelector: bslLabelKey + "=" + bslName},
})
return backups, err
}
func findAssociatedResticRepos(client kbclient.Client, bslName, ns string) (velerov1api.ResticRepositoryList, error) {
var repos velerov1api.ResticRepositoryList
err := client.List(context.Background(), &repos, &kbclient.ListOptions{
Namespace: ns,
Raw: &metav1.ListOptions{LabelSelector: bslLabelKey + "=" + bslName},
})
return repos, err
}
func deleteBackups(client kbclient.Client, backups velerov1api.BackupList) []error {
var errs []error
for _, backup := range backups.Items {
if err := client.Delete(context.Background(), &backup, &kbclient.DeleteOptions{}); err != nil {
errs = append(errs, errors.WithStack(fmt.Errorf("delete backup %q associated with deleted BSL: %w", backup.Name, err)))
continue
}
fmt.Printf("Backup associated with deleted BSL(s) %q deleted successfully.\n", backup.Name)
}
return errs
}
func deleteResticRepos(client kbclient.Client, repos velerov1api.ResticRepositoryList) []error {
var errs []error
for _, repo := range repos.Items {
if err := client.Delete(context.Background(), &repo, &kbclient.DeleteOptions{}); err != nil {
errs = append(errs, errors.WithStack(fmt.Errorf("delete Restic repository %q associated with deleted BSL: %w", repo.Name, err)))
continue
}
fmt.Printf("Restic repository associated with deleted BSL(s) %q deleted successfully.\n", repo.Name)
}
return errs
}
+7 -2
View File
@@ -55,10 +55,15 @@ about: Tell us about a problem you are experiencing
**What did you expect to happen:**
**The following information will help us better understand what's going on**:
**The output of the following commands will help us better understand what's going on**:
(Pasting long output into a [GitHub gist](https://gist.github.com) or other pastebin is fine.)
_If you are using velero v1.7.0+:_
Please use ` + "`velero debug --backup <backupname> --restore <restorename>` " +
`to generate the support bundle, and attach to this issue, more options please refer to ` +
"`velero debug --help` " + `
_If you are using earlier versions:_
Please provide the output of the following commands (Pasting long output into a [GitHub gist](https://gist.github.com) or other pastebin is fine.)
- ` + "`kubectl logs deployment/velero -n velero`" + `
- ` + "`velero backup describe <backupname>` or `kubectl get backup/<backupname> -n velero -o yaml`" + `
- ` + "`velero backup logs <backupname>`" + `
+14 -9
View File
@@ -1,26 +1,31 @@
def capture_backup_logs(namespace):
def capture_backup_logs(cmd, namespace):
if args.backup:
log("Collecting log for backup: {}".format(args.backup))
backupLogsCmd = "velero --namespace={} backup logs {}".format(namespace, args.backup)
log("Collecting log and information for backup: {}".format(args.backup))
backupDescCmd = "{} --namespace={} backup describe {} --details".format(cmd, namespace, args.backup)
capture_local(cmd=backupDescCmd, file_name="backup_describe_{}.txt".format(args.backup))
backupLogsCmd = "{} --namespace={} backup logs {}".format(cmd, namespace, args.backup)
capture_local(cmd=backupLogsCmd, file_name="backup_{}.log".format(args.backup))
def capture_restore_logs(namespace):
def capture_restore_logs(cmd, namespace):
if args.restore:
log("Collecting log for restore: {}".format(args.restore))
restoreLogsCmd = "velero --namespace={} restore logs {}".format(namespace, args.restore)
log("Collecting log and information for restore: {}".format(args.restore))
restoreDescCmd = "{} --namespace={} restore describe {} --details".format(cmd, namespace, args.restore)
capture_local(cmd=restoreDescCmd, file_name="restore_describe_{}.txt".format(args.restore))
restoreLogsCmd = "{} --namespace={} restore logs {}".format(cmd, namespace, args.restore)
capture_local(cmd=restoreLogsCmd, file_name="restore_{}.log".format(args.restore))
ns = args.namespace if args.namespace else "velero"
output = args.output if args.output else "bundle.tar.gz"
cmd = args.cmd if args.cmd else "velero"
# Working dir for writing during script execution
crshd = crashd_config(workdir="./velero-bundle")
set_defaults(kube_config(path=args.kubeconfig, cluster_context=args.kubecontext))
log("Collecting velero resources in namespace: {}". format(ns))
kube_capture(what="objects", namespaces=[ns], groups=['velero.io'])
capture_local(cmd="velero version -n {}".format(ns), file_name="version.txt")
capture_local(cmd="{} version -n {}".format(cmd, ns), file_name="version.txt")
log("Collecting velero deployment logs in namespace: {}". format(ns))
kube_capture(what="logs", namespaces=[ns])
capture_backup_logs(ns)
capture_restore_logs(ns)
capture_backup_logs(cmd, ns)
capture_restore_logs(cmd, ns)
archive(output_file=output, source_paths=[crshd.workdir])
log("Generated debug information bundle: {}".format(output))
+7
View File
@@ -42,6 +42,8 @@ import (
var scriptBytes []byte
type option struct {
// currCmd the velero command
currCmd string
// workdir for crashd will be $baseDir/velero-debug
baseDir string
// the namespace where velero server is installed
@@ -74,6 +76,7 @@ func (o *option) asCrashdArgs() string {
func (o *option) asCrashdArgMap() exec.ArgMap {
return exec.ArgMap{
"cmd": o.currCmd,
"output": o.outputPath,
"namespace": o.namespace,
"basedir": o.baseDir,
@@ -100,6 +103,10 @@ func (o *option) complete(f client.Factory, fs *pflag.FlagSet) error {
o.baseDir = tmpDir
o.namespace = f.Namespace()
kp, kc := kubeconfigAndContext(fs)
o.currCmd, err = os.Executable()
if err != nil {
return err
}
o.kubeconfigPath, err = filepath.Abs(kp)
if err != nil {
return fmt.Errorf("invalid kubeconfig path: %s, %v", kp, err)
+1 -34
View File
@@ -25,11 +25,9 @@ import (
"time"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/vmware-tanzu/velero/internal/velero"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
@@ -37,7 +35,6 @@ import (
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
velerodiscovery "github.com/vmware-tanzu/velero/pkg/discovery"
"github.com/vmware-tanzu/velero/pkg/install"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
)
@@ -72,7 +69,6 @@ type InstallOptions struct {
Plugins flag.StringArray
NoDefaultBackupLocation bool
CRDsOnly bool
CRDsVersion string
CACertFile string
Features string
DefaultVolumesToRestic bool
@@ -107,7 +103,6 @@ func (o *InstallOptions) BindFlags(flags *pflag.FlagSet) {
flags.DurationVar(&o.DefaultResticMaintenanceFrequency, "default-restic-prune-frequency", o.DefaultResticMaintenanceFrequency, "How often 'restic prune' is run for restic repositories by default. Optional.")
flags.Var(&o.Plugins, "plugins", "Plugin container images to install into the Velero Deployment")
flags.BoolVar(&o.CRDsOnly, "crds-only", o.CRDsOnly, "Only generate CustomResourceDefinition resources. Useful for updating CRDs for an existing Velero install.")
flags.StringVar(&o.CRDsVersion, "crds-version", o.CRDsVersion, "The version to generate CustomResourceDefinition resources if Velero can't discover the Kubernetes preferred CRD API version. Optional.")
flags.StringVar(&o.CACertFile, "cacert", o.CACertFile, "File containing a certificate bundle to use when verifying TLS connections to the object store. Optional.")
flags.StringVar(&o.Features, "features", o.Features, "Comma separated list of Velero feature flags to be set on the Velero deployment and the restic daemonset, if restic is enabled")
flags.BoolVar(&o.DefaultVolumesToRestic, "default-volumes-to-restic", o.DefaultVolumesToRestic, "Bool flag to configure Velero server to use restic by default to backup all pod volumes on all backups. Optional.")
@@ -134,7 +129,6 @@ func NewInstallOptions() *InstallOptions {
UseVolumeSnapshots: true,
NoDefaultBackupLocation: false,
CRDsOnly: false,
CRDsVersion: "v1",
DefaultVolumesToRestic: false,
}
}
@@ -193,7 +187,6 @@ func (o *InstallOptions) AsVeleroOptions() (*install.VeleroOptions, error) {
NoDefaultBackupLocation: o.NoDefaultBackupLocation,
CACertData: caCertData,
Features: strings.Split(o.Features, ","),
CRDsVersion: o.CRDsVersion,
DefaultVolumesToRestic: o.DefaultVolumesToRestic,
}, nil
}
@@ -254,30 +247,9 @@ This is useful as a starting point for more customized installations.
// Run executes a command in the context of the provided arguments.
func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error {
// Find the kube-apiserver group apiextensions.k8s.io preferred API version
clientset, err := f.KubeClient()
if err == nil {
// kubeconfig available
discoveryHelper, err := velerodiscovery.NewHelper(clientset.Discovery(), &logrus.Logger{})
if err == nil {
// kubernetes apiserver available
gvr, _, err := discoveryHelper.ResourceFor(
schema.GroupVersionResource{
Group: "apiextensions.k8s.io",
Resource: "customresourcedefinitions",
})
if err != nil {
return err
}
// Update the group apiextensions.k8s.io preferred API version
o.CRDsVersion = gvr.Version
}
}
var resources *unstructured.UnstructuredList
if o.CRDsOnly {
resources = install.AllCRDs(o.CRDsVersion)
resources = install.AllCRDs()
} else {
vo, err := o.AsVeleroOptions()
if err != nil {
@@ -348,11 +320,6 @@ func (o *InstallOptions) Validate(c *cobra.Command, args []string, f client.Fact
return err
}
// Check the CRD version is valid.
if o.CRDsVersion != "v1beta1" && o.CRDsVersion != "v1" {
return errors.Errorf("CRD version must be v1beta1 or v1")
}
// If we're only installing CRDs, we can skip the rest of the validation.
if o.CRDsOnly {
return nil
+2
View File
@@ -32,6 +32,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
v1 "k8s.io/api/core/v1"
storagev1api "k8s.io/api/storage/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
@@ -151,6 +152,7 @@ func newResticServer(logger logrus.FieldLogger, factory client.Factory, metricAd
velerov1api.AddToScheme(scheme)
v1.AddToScheme(scheme)
storagev1api.AddToScheme(scheme)
mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{
Scheme: scheme,
})
+10
View File
@@ -111,11 +111,20 @@ func (o *CreateOptions) Complete(args []string, f client.Factory) error {
}
func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
var orders map[string]string
veleroClient, err := f.Client()
if err != nil {
return err
}
if len(o.BackupOptions.OrderedResources) > 0 {
orders, err = backup.ParseOrderedResources(o.BackupOptions.OrderedResources)
if err != nil {
return err
}
}
schedule := &api.Schedule{
ObjectMeta: metav1.ObjectMeta{
Namespace: f.Namespace(),
@@ -135,6 +144,7 @@ func (o *CreateOptions) Run(c *cobra.Command, f client.Factory) error {
StorageLocation: o.BackupOptions.StorageLocation,
VolumeSnapshotLocations: o.BackupOptions.SnapshotLocations,
DefaultVolumesToRestic: o.BackupOptions.DefaultVolumesToRestic.Value,
OrderedResources: orders,
},
Schedule: o.Schedule,
UseOwnerReferencesInBackup: &o.UseOwnerReferencesInBackup,
+5
View File
@@ -55,6 +55,7 @@ func NewCommand(f client.Factory) *cobra.Command {
RegisterRestoreItemAction("velero.io/crd-preserve-fields", newCRDV1PreserveUnknownFieldsItemAction).
RegisterRestoreItemAction("velero.io/change-pvc-node-selector", newChangePVCNodeSelectorItemAction(f)).
RegisterRestoreItemAction("velero.io/apiservice", newAPIServiceRestoreItemAction).
RegisterRestoreItemAction("velero.io/admission-webhook-configuration", newAdmissionWebhookConfigurationAction).
Serve()
},
}
@@ -202,3 +203,7 @@ func newChangePVCNodeSelectorItemAction(f client.Factory) veleroplugin.HandlerIn
func newAPIServiceRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
return restore.NewAPIServiceAction(logger), nil
}
func newAdmissionWebhookConfigurationAction(logger logrus.FieldLogger) (interface{}, error) {
return restore.NewAdmissionWebhookConfigurationAction(logger), nil
}
+5 -1
View File
@@ -27,6 +27,7 @@ import (
"strings"
"time"
"github.com/bombsimon/logrusr"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
@@ -302,8 +303,11 @@ func newServer(f client.Factory, config serverConfig, logger *logrus.Logger) (*s
velerov1api.AddToScheme(scheme)
corev1api.AddToScheme(scheme)
ctrl.SetLogger(logrusr.NewLogger(logger))
mgr, err := ctrl.NewManager(clientConfig, ctrl.Options{
Scheme: scheme,
Scheme: scheme,
Namespace: f.Namespace(),
})
if err != nil {
cancelFunc()
+12 -3
View File
@@ -1,5 +1,5 @@
/*
Copyright the Velero contributors.
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -53,6 +53,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/metrics"
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
"github.com/vmware-tanzu/velero/pkg/util/collections"
"github.com/vmware-tanzu/velero/pkg/util/encode"
@@ -424,7 +425,7 @@ func (c *backupController) prepareBackupRequest(backup *velerov1api.Backup) *pkg
}
// validate the included/excluded namespaces
for _, err := range collections.ValidateIncludesExcludes(request.Spec.IncludedNamespaces, request.Spec.ExcludedNamespaces) {
for _, err := range collections.ValidateNamespaceIncludesExcludes(request.Spec.IncludedNamespaces, request.Spec.ExcludedNamespaces) {
request.Status.ValidationErrors = append(request.Status.ValidationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
}
@@ -569,6 +570,10 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error {
if err != nil {
return err
}
itemSnapshotters, err := pluginManager.GetItemSnapshotters()
if err != nil {
return err
}
backupLog.Info("Setting up backup store to check for backup existence")
backupStore, err := c.backupStoreGetter.Get(backup.StorageLocation, pluginManager, backupLog)
@@ -586,8 +591,12 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error {
return errors.Errorf("backup already exists in object storage")
}
backupItemActionsResolver := framework.NewBackupItemActionResolver(actions)
itemSnapshottersResolver := framework.NewItemSnapshotterResolver(itemSnapshotters)
var fatalErrs []error
if err := c.backupper.Backup(backupLog, backup, backupFile, actions, pluginManager); err != nil {
if err := c.backupper.BackupWithResolvers(backupLog, backup, backupFile, backupItemActionsResolver,
itemSnapshottersResolver, pluginManager); err != nil {
fatalErrs = append(fatalErrs, err)
}
+10
View File
@@ -47,6 +47,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/persistence"
persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
@@ -63,6 +64,13 @@ func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *pkgbackup.Requ
return args.Error(0)
}
func (b *fakeBackupper) BackupWithResolvers(logger logrus.FieldLogger, backup *pkgbackup.Request, backupFile io.Writer,
backupItemActionResolver framework.BackupItemActionResolver, itemSnapshotterResolver framework.ItemSnapshotterResolver,
volumeSnapshotterGetter pkgbackup.VolumeSnapshotterGetter) error {
args := b.Called(logger, backup, backupFile, backupItemActionResolver, itemSnapshotterResolver, volumeSnapshotterGetter)
return args.Error(0)
}
func defaultBackup() *builder.BackupBuilder {
return builder.ForBackup(velerov1api.DefaultNamespace, "backup-1")
}
@@ -825,7 +833,9 @@ func TestProcessBackupCompletions(t *testing.T) {
pluginManager.On("GetBackupItemActions").Return(nil, nil)
pluginManager.On("CleanupClients").Return(nil)
pluginManager.On("GetItemSnapshotters").Return(nil, nil)
backupper.On("Backup", mock.Anything, mock.Anything, mock.Anything, []velero.BackupItemAction(nil), pluginManager).Return(nil)
backupper.On("BackupWithResolvers", mock.Anything, mock.Anything, mock.Anything, framework.BackupItemActionResolver{}, framework.ItemSnapshotterResolver{}, pluginManager).Return(nil)
backupStore.On("BackupExists", test.backupLocation.Spec.StorageType.ObjectStorage.Bucket, test.backup.Name).Return(test.backupExists, test.existenceCheckError)
// Ensure we have a CompletionTimestamp when uploading and that the backup name matches the backup in the object store.
+1 -1
View File
@@ -291,7 +291,7 @@ func (c *backupDeletionController) processRequest(req *velerov1api.DeleteBackupR
backupStore, err := c.backupStoreGetter.Get(location, pluginManager, log)
if err != nil {
errs = append(errs, err.Error())
return errors.Wrap(err, "error getting the backup store")
}
actions, err := pluginManager.GetDeleteItemActions()
@@ -1,5 +1,5 @@
/*
Copyright the Velero contributors.
Copyright The Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import (
"context"
"fmt"
"io/ioutil"
"strings"
"testing"
"time"
@@ -36,19 +37,20 @@ import (
core "k8s.io/client-go/testing"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks"
"github.com/vmware-tanzu/velero/pkg/volume"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
pkgbackup "github.com/vmware-tanzu/velero/pkg/backup"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned/fake"
informers "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions"
"github.com/vmware-tanzu/velero/pkg/metrics"
persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/plugin/velero/mocks"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/volume"
)
func TestBackupDeletionControllerProcessQueueItem(t *testing.T) {
@@ -183,6 +185,29 @@ func setupBackupDeletionControllerTest(t *testing.T, objects ...runtime.Object)
}
func TestBackupDeletionControllerProcessRequest(t *testing.T) {
t.Run("failed to get backup store", func(t *testing.T) {
backup := builder.ForBackup(velerov1api.DefaultNamespace, "foo").StorageLocation("default").Result()
location := &velerov1api.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: backup.Namespace,
Name: backup.Spec.StorageLocation,
},
Spec: velerov1api.BackupStorageLocationSpec{
Provider: "objStoreProvider",
StorageType: velerov1api.StorageType{
ObjectStorage: &velerov1api.ObjectStorageLocation{
Bucket: "bucket",
},
},
},
}
td := setupBackupDeletionControllerTest(t, location, backup)
td.controller.backupStoreGetter = &fakeErrorBackupStoreGetter{}
err := td.controller.processRequest(td.req)
assert.NotNil(t, err)
assert.True(t, strings.HasPrefix(err.Error(), "error getting the backup store"))
})
t.Run("missing spec.backupName", func(t *testing.T) {
td := setupBackupDeletionControllerTest(t)
td.req.Spec.BackupName = ""
+1 -1
View File
@@ -312,7 +312,7 @@ func (c *backupSyncController) run() {
c.deleteOrphanedBackups(location.Name, backupStoreBackups, log)
// update the location's last-synced time field
statusPatch := client.MergeFrom(location.DeepCopyObject())
statusPatch := client.MergeFrom(location.DeepCopy())
location.Status.LastSyncedTime = &metav1.Time{Time: time.Now().UTC()}
if err := c.kbClient.Status().Patch(context.Background(), &location, statusPatch); err != nil {
log.WithError(errors.WithStack(err)).Error("Error patching backup location's last-synced time")
@@ -208,7 +208,7 @@ func (c *podVolumeBackupController) processBackup(req *velerov1api.PodVolumeBack
return c.fail(req, errors.Wrap(err, "error getting pod").Error(), log)
}
volumeDir, err := kube.GetVolumeDirectory(pod, req.Spec.Volume, c.pvcLister, c.pvLister)
volumeDir, err := kube.GetVolumeDirectory(log, pod, req.Spec.Volume, c.pvcLister, c.pvLister, c.kbClient)
if err != nil {
log.WithError(err).Error("Error getting volume directory name")
return c.fail(req, errors.Wrap(err, "error getting volume directory name").Error(), log)
@@ -297,7 +297,7 @@ func (c *podVolumeRestoreController) processRestore(req *velerov1api.PodVolumeRe
return c.failRestore(req, errors.Wrap(err, "error getting pod").Error(), log)
}
volumeDir, err := kube.GetVolumeDirectory(pod, req.Spec.Volume, c.pvcLister, c.pvLister)
volumeDir, err := kube.GetVolumeDirectory(log, pod, req.Spec.Volume, c.pvcLister, c.pvLister, c.kbClient)
if err != nil {
log.WithError(err).Error("Error getting volume directory name")
return c.failRestore(req, errors.Wrap(err, "error getting volume directory name").Error(), log)
+10 -1
View File
@@ -47,6 +47,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/metrics"
"github.com/vmware-tanzu/velero/pkg/persistence"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
pkgrestore "github.com/vmware-tanzu/velero/pkg/restore"
"github.com/vmware-tanzu/velero/pkg/util/collections"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
@@ -443,6 +444,13 @@ func (c *restoreController) runValidatedRestore(restore *api.Restore, info backu
if err != nil {
return errors.Wrap(err, "error getting restore item actions")
}
actionsResolver := framework.NewRestoreItemActionResolver(actions)
itemSnapshotters, err := pluginManager.GetItemSnapshotters()
if err != nil {
return errors.Wrap(err, "error getting item snapshotters")
}
snapshotItemResolver := framework.NewItemSnapshotterResolver(itemSnapshotters)
backupFile, err := downloadToTempFile(restore.Spec.BackupName, info.backupStore, restoreLog)
if err != nil {
@@ -476,7 +484,8 @@ func (c *restoreController) runValidatedRestore(restore *api.Restore, info backu
VolumeSnapshots: volumeSnapshots,
BackupReader: backupFile,
}
restoreWarnings, restoreErrors := c.restorer.Restore(restoreReq, actions, c.snapshotLocationLister, pluginManager)
restoreWarnings, restoreErrors := c.restorer.RestoreWithResolvers(restoreReq, actionsResolver, snapshotItemResolver,
c.snapshotLocationLister, pluginManager)
restoreLog.Info("restore completed")
// re-instantiate the backup store because credentials could have changed since the original
+19 -1
View File
@@ -44,8 +44,10 @@ import (
"github.com/vmware-tanzu/velero/pkg/metrics"
persistencemocks "github.com/vmware-tanzu/velero/pkg/persistence/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/clientmgmt"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
pluginmocks "github.com/vmware-tanzu/velero/pkg/plugin/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
pkgrestore "github.com/vmware-tanzu/velero/pkg/restore"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/util/logging"
@@ -505,7 +507,8 @@ func TestProcessQueueItem(t *testing.T) {
if test.expectedRestorerCall != nil {
backupStore.On("GetBackupContents", test.backup.Name).Return(ioutil.NopCloser(bytes.NewReader([]byte("hello world"))), nil)
restorer.On("Restore", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(warnings, errors)
restorer.On("RestoreWithResolvers", mock.Anything, mock.Anything, mock.Anything, mock.Anything,
mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(warnings, errors)
backupStore.On("PutRestoreLog", test.backup.Name, test.restore.Name, mock.Anything).Return(test.putRestoreLogErr)
@@ -545,6 +548,7 @@ func TestProcessQueueItem(t *testing.T) {
if test.restore != nil {
pluginManager.On("GetRestoreItemActions").Return(nil, nil)
pluginManager.On("GetItemSnapshotters").Return([]isv1.ItemSnapshotter{}, nil)
pluginManager.On("CleanupClients")
}
@@ -858,3 +862,17 @@ func (r *fakeRestorer) Restore(
return res.Get(0).(pkgrestore.Result), res.Get(1).(pkgrestore.Result)
}
func (r *fakeRestorer) RestoreWithResolvers(req pkgrestore.Request,
resolver framework.RestoreItemActionResolver,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter pkgrestore.VolumeSnapshotterGetter,
) (pkgrestore.Result, pkgrestore.Result) {
res := r.Called(req.Log, req.Restore, req.Backup, req.BackupReader, resolver, itemSnapshotterResolver,
snapshotLocationLister, volumeSnapshotterGetter)
r.calledWithArg = *req.Restore
return res.Get(0).(pkgrestore.Result), res.Get(1).(pkgrestore.Result)
}
+2 -2
View File
@@ -276,11 +276,11 @@ func (c *scheduleController) submitBackupIfDue(item *api.Schedule, cronSchedule
}
func getNextRunTime(schedule *api.Schedule, cronSchedule cron.Schedule, asOf time.Time) (bool, time.Time) {
// get the latest run time (if the schedule hasn't run yet, this will be the zero value which will trigger
// an immediate backup)
var lastBackupTime time.Time
if schedule.Status.LastBackup != nil {
lastBackupTime = schedule.Status.LastBackup.Time
} else {
lastBackupTime = schedule.CreationTimestamp.Time
}
nextRunTime := cronSchedule.Next(lastBackupTime)
+6 -3
View File
@@ -274,7 +274,7 @@ func TestGetNextRunTime(t *testing.T) {
{
name: "first run",
schedule: defaultSchedule(),
expectedDue: true,
expectedDue: false,
expectedNextRunTimeOffset: "5m",
},
{
@@ -319,6 +319,9 @@ func TestGetNextRunTime(t *testing.T) {
require.NoError(t, err, "unable to parse test.lastRanOffset: %v", err)
test.schedule.Status.LastBackup = &metav1.Time{Time: testClock.Now().Add(-offsetDuration)}
test.schedule.CreationTimestamp = *test.schedule.Status.LastBackup
} else {
test.schedule.CreationTimestamp = metav1.Time{Time: testClock.Now()}
}
nextRunTimeOffset, err := time.ParseDuration(test.expectedNextRunTimeOffset)
@@ -326,11 +329,11 @@ func TestGetNextRunTime(t *testing.T) {
panic(err)
}
// calculate expected next run time (if the schedule hasn't run yet, this
// will be the zero value which will trigger an immediate backup)
var baseTime time.Time
if test.lastRanOffset != "" {
baseTime = test.schedule.Status.LastBackup.Time
} else {
baseTime = test.schedule.CreationTimestamp.Time
}
expectedNextRunTime := baseTime.Add(nextRunTimeOffset)
+8
View File
@@ -18,6 +18,7 @@ package controller
import (
"context"
"fmt"
"path/filepath"
"testing"
"time"
@@ -130,6 +131,13 @@ func (t *testEnvironment) stop() error {
return env.Stop()
}
type fakeErrorBackupStoreGetter struct {
}
func (f *fakeErrorBackupStoreGetter) Get(*velerov1api.BackupStorageLocation, persistence.ObjectStoreGetter, logrus.FieldLogger) (persistence.BackupStore, error) {
return nil, fmt.Errorf("some error")
}
type fakeSingleObjectBackupStoreGetter struct {
store persistence.BackupStore
}
@@ -74,7 +74,10 @@ func (c *Clientset) Tracker() testing.ObjectTracker {
return c.tracker
}
var _ clientset.Interface = &Clientset{}
var (
_ clientset.Interface = &Clientset{}
_ testing.FakeClient = &Clientset{}
)
// VeleroV1 retrieves the VeleroV1Client
func (c *Clientset) VeleroV1() velerov1.VeleroV1Interface {
@@ -29,7 +29,7 @@ import (
var scheme = runtime.NewScheme()
var codecs = serializer.NewCodecFactory(scheme)
var parameterCodec = runtime.NewParameterCodec(scheme)
var localSchemeBuilder = runtime.SchemeBuilder{
velerov1.AddToScheme,
}
@@ -26,8 +26,10 @@ import (
)
// BackupLister helps list Backups.
// All objects returned here must be treated as read-only.
type BackupLister interface {
// List lists all Backups in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.Backup, err error)
// Backups returns an object that can list and get Backups.
Backups(namespace string) BackupNamespaceLister
@@ -58,10 +60,13 @@ func (s *backupLister) Backups(namespace string) BackupNamespaceLister {
}
// BackupNamespaceLister helps list and get Backups.
// All objects returned here must be treated as read-only.
type BackupNamespaceLister interface {
// List lists all Backups in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.Backup, err error)
// Get retrieves the Backup from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.Backup, error)
BackupNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// BackupStorageLocationLister helps list BackupStorageLocations.
// All objects returned here must be treated as read-only.
type BackupStorageLocationLister interface {
// List lists all BackupStorageLocations in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.BackupStorageLocation, err error)
// BackupStorageLocations returns an object that can list and get BackupStorageLocations.
BackupStorageLocations(namespace string) BackupStorageLocationNamespaceLister
@@ -58,10 +60,13 @@ func (s *backupStorageLocationLister) BackupStorageLocations(namespace string) B
}
// BackupStorageLocationNamespaceLister helps list and get BackupStorageLocations.
// All objects returned here must be treated as read-only.
type BackupStorageLocationNamespaceLister interface {
// List lists all BackupStorageLocations in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.BackupStorageLocation, err error)
// Get retrieves the BackupStorageLocation from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.BackupStorageLocation, error)
BackupStorageLocationNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// DeleteBackupRequestLister helps list DeleteBackupRequests.
// All objects returned here must be treated as read-only.
type DeleteBackupRequestLister interface {
// List lists all DeleteBackupRequests in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.DeleteBackupRequest, err error)
// DeleteBackupRequests returns an object that can list and get DeleteBackupRequests.
DeleteBackupRequests(namespace string) DeleteBackupRequestNamespaceLister
@@ -58,10 +60,13 @@ func (s *deleteBackupRequestLister) DeleteBackupRequests(namespace string) Delet
}
// DeleteBackupRequestNamespaceLister helps list and get DeleteBackupRequests.
// All objects returned here must be treated as read-only.
type DeleteBackupRequestNamespaceLister interface {
// List lists all DeleteBackupRequests in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.DeleteBackupRequest, err error)
// Get retrieves the DeleteBackupRequest from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.DeleteBackupRequest, error)
DeleteBackupRequestNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// DownloadRequestLister helps list DownloadRequests.
// All objects returned here must be treated as read-only.
type DownloadRequestLister interface {
// List lists all DownloadRequests in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.DownloadRequest, err error)
// DownloadRequests returns an object that can list and get DownloadRequests.
DownloadRequests(namespace string) DownloadRequestNamespaceLister
@@ -58,10 +60,13 @@ func (s *downloadRequestLister) DownloadRequests(namespace string) DownloadReque
}
// DownloadRequestNamespaceLister helps list and get DownloadRequests.
// All objects returned here must be treated as read-only.
type DownloadRequestNamespaceLister interface {
// List lists all DownloadRequests in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.DownloadRequest, err error)
// Get retrieves the DownloadRequest from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.DownloadRequest, error)
DownloadRequestNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// PodVolumeBackupLister helps list PodVolumeBackups.
// All objects returned here must be treated as read-only.
type PodVolumeBackupLister interface {
// List lists all PodVolumeBackups in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.PodVolumeBackup, err error)
// PodVolumeBackups returns an object that can list and get PodVolumeBackups.
PodVolumeBackups(namespace string) PodVolumeBackupNamespaceLister
@@ -58,10 +60,13 @@ func (s *podVolumeBackupLister) PodVolumeBackups(namespace string) PodVolumeBack
}
// PodVolumeBackupNamespaceLister helps list and get PodVolumeBackups.
// All objects returned here must be treated as read-only.
type PodVolumeBackupNamespaceLister interface {
// List lists all PodVolumeBackups in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.PodVolumeBackup, err error)
// Get retrieves the PodVolumeBackup from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.PodVolumeBackup, error)
PodVolumeBackupNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// PodVolumeRestoreLister helps list PodVolumeRestores.
// All objects returned here must be treated as read-only.
type PodVolumeRestoreLister interface {
// List lists all PodVolumeRestores in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.PodVolumeRestore, err error)
// PodVolumeRestores returns an object that can list and get PodVolumeRestores.
PodVolumeRestores(namespace string) PodVolumeRestoreNamespaceLister
@@ -58,10 +60,13 @@ func (s *podVolumeRestoreLister) PodVolumeRestores(namespace string) PodVolumeRe
}
// PodVolumeRestoreNamespaceLister helps list and get PodVolumeRestores.
// All objects returned here must be treated as read-only.
type PodVolumeRestoreNamespaceLister interface {
// List lists all PodVolumeRestores in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.PodVolumeRestore, err error)
// Get retrieves the PodVolumeRestore from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.PodVolumeRestore, error)
PodVolumeRestoreNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// ResticRepositoryLister helps list ResticRepositories.
// All objects returned here must be treated as read-only.
type ResticRepositoryLister interface {
// List lists all ResticRepositories in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.ResticRepository, err error)
// ResticRepositories returns an object that can list and get ResticRepositories.
ResticRepositories(namespace string) ResticRepositoryNamespaceLister
@@ -58,10 +60,13 @@ func (s *resticRepositoryLister) ResticRepositories(namespace string) ResticRepo
}
// ResticRepositoryNamespaceLister helps list and get ResticRepositories.
// All objects returned here must be treated as read-only.
type ResticRepositoryNamespaceLister interface {
// List lists all ResticRepositories in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.ResticRepository, err error)
// Get retrieves the ResticRepository from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.ResticRepository, error)
ResticRepositoryNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// RestoreLister helps list Restores.
// All objects returned here must be treated as read-only.
type RestoreLister interface {
// List lists all Restores in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.Restore, err error)
// Restores returns an object that can list and get Restores.
Restores(namespace string) RestoreNamespaceLister
@@ -58,10 +60,13 @@ func (s *restoreLister) Restores(namespace string) RestoreNamespaceLister {
}
// RestoreNamespaceLister helps list and get Restores.
// All objects returned here must be treated as read-only.
type RestoreNamespaceLister interface {
// List lists all Restores in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.Restore, err error)
// Get retrieves the Restore from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.Restore, error)
RestoreNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// ScheduleLister helps list Schedules.
// All objects returned here must be treated as read-only.
type ScheduleLister interface {
// List lists all Schedules in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.Schedule, err error)
// Schedules returns an object that can list and get Schedules.
Schedules(namespace string) ScheduleNamespaceLister
@@ -58,10 +60,13 @@ func (s *scheduleLister) Schedules(namespace string) ScheduleNamespaceLister {
}
// ScheduleNamespaceLister helps list and get Schedules.
// All objects returned here must be treated as read-only.
type ScheduleNamespaceLister interface {
// List lists all Schedules in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.Schedule, err error)
// Get retrieves the Schedule from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.Schedule, error)
ScheduleNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// ServerStatusRequestLister helps list ServerStatusRequests.
// All objects returned here must be treated as read-only.
type ServerStatusRequestLister interface {
// List lists all ServerStatusRequests in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.ServerStatusRequest, err error)
// ServerStatusRequests returns an object that can list and get ServerStatusRequests.
ServerStatusRequests(namespace string) ServerStatusRequestNamespaceLister
@@ -58,10 +60,13 @@ func (s *serverStatusRequestLister) ServerStatusRequests(namespace string) Serve
}
// ServerStatusRequestNamespaceLister helps list and get ServerStatusRequests.
// All objects returned here must be treated as read-only.
type ServerStatusRequestNamespaceLister interface {
// List lists all ServerStatusRequests in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.ServerStatusRequest, err error)
// Get retrieves the ServerStatusRequest from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.ServerStatusRequest, error)
ServerStatusRequestNamespaceListerExpansion
}
@@ -26,8 +26,10 @@ import (
)
// VolumeSnapshotLocationLister helps list VolumeSnapshotLocations.
// All objects returned here must be treated as read-only.
type VolumeSnapshotLocationLister interface {
// List lists all VolumeSnapshotLocations in the indexer.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.VolumeSnapshotLocation, err error)
// VolumeSnapshotLocations returns an object that can list and get VolumeSnapshotLocations.
VolumeSnapshotLocations(namespace string) VolumeSnapshotLocationNamespaceLister
@@ -58,10 +60,13 @@ func (s *volumeSnapshotLocationLister) VolumeSnapshotLocations(namespace string)
}
// VolumeSnapshotLocationNamespaceLister helps list and get VolumeSnapshotLocations.
// All objects returned here must be treated as read-only.
type VolumeSnapshotLocationNamespaceLister interface {
// List lists all VolumeSnapshotLocations in the indexer for a given namespace.
// Objects returned here must be treated as read-only.
List(selector labels.Selector) (ret []*v1.VolumeSnapshotLocation, err error)
// Get retrieves the VolumeSnapshotLocation from the indexer for a given namespace and name.
// Objects returned here must be treated as read-only.
Get(name string) (*v1.VolumeSnapshotLocation, error)
VolumeSnapshotLocationNamespaceListerExpansion
}
+1
View File
@@ -50,6 +50,7 @@ var kindToResource = map[string]string{
"Deployment": "deployments",
"DaemonSet": "daemonsets",
"Secret": "secrets",
"ConfigMap": "configmaps",
"BackupStorageLocation": "backupstoragelocations",
"VolumeSnapshotLocation": "volumesnapshotlocations",
}
+5 -15
View File
@@ -27,7 +27,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
v1crds "github.com/vmware-tanzu/velero/config/crd/v1/crds"
v1beta1crds "github.com/vmware-tanzu/velero/config/crd/v1beta1/crds"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
@@ -216,26 +215,17 @@ type VeleroOptions struct {
NoDefaultBackupLocation bool
CACertData []byte
Features []string
CRDsVersion string
DefaultVolumesToRestic bool
}
func AllCRDs(perferredAPIVersion string) *unstructured.UnstructuredList {
func AllCRDs() *unstructured.UnstructuredList {
resources := new(unstructured.UnstructuredList)
// Set the GVK so that the serialization framework outputs the list properly
resources.SetGroupVersionKind(schema.GroupVersionKind{Group: "", Version: "v1", Kind: "List"})
switch perferredAPIVersion {
case "v1beta1":
for _, crd := range v1beta1crds.CRDs {
crd.SetLabels(Labels())
appendUnstructured(resources, crd)
}
case "v1":
for _, crd := range v1crds.CRDs {
crd.SetLabels(Labels())
appendUnstructured(resources, crd)
}
for _, crd := range v1crds.CRDs {
crd.SetLabels(Labels())
appendUnstructured(resources, crd)
}
return resources
@@ -244,7 +234,7 @@ func AllCRDs(perferredAPIVersion string) *unstructured.UnstructuredList {
// AllResources returns a list of all resources necessary to install Velero, in the appropriate order, into a Kubernetes cluster.
// Items are unstructured, since there are different data types returned.
func AllResources(o *VeleroOptions) *unstructured.UnstructuredList {
resources := AllCRDs(o.CRDsVersion)
resources := AllCRDs()
ns := Namespace(o.Namespace)
appendUnstructured(resources, ns)
+4
View File
@@ -283,3 +283,7 @@ func (_m *BackupStore) GetCSIVolumeSnapshotContents(backup string) ([]*snapshotv
panic("Not implemented")
return nil, nil
}
func (_m *BackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) {
panic("implement me")
}
+26 -7
View File
@@ -44,6 +44,7 @@ type BackupInfo struct {
Log,
PodVolumeBackups,
VolumeSnapshots,
ItemSnapshots,
BackupResourceList,
CSIVolumeSnapshots,
CSIVolumeSnapshotContents io.Reader
@@ -58,6 +59,7 @@ type BackupStore interface {
PutBackup(info BackupInfo) error
GetBackupMetadata(name string) (*velerov1api.Backup, error)
GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error)
GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error)
GetPodVolumeBackups(name string) ([]*velerov1api.PodVolumeBackup, error)
GetBackupContents(name string) (io.ReadCloser, error)
@@ -231,13 +233,6 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error {
s.logger.WithError(err).WithField("backup", info.Name).Error("Error uploading log file")
}
if info.Metadata == nil {
// If we don't have metadata, something failed, and there's no point in continuing. An object
// storage bucket that is missing the metadata file can't be restored, nor can its logs be
// viewed.
return nil
}
if err := seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupMetadataKey(info.Name), info.Metadata); err != nil {
// failure to upload metadata file is a hard-stop
return err
@@ -253,6 +248,7 @@ func (s *objectBackupStore) PutBackup(info BackupInfo) error {
var backupObjs = map[string]io.Reader{
s.layout.getPodVolumeBackupsKey(info.Name): info.PodVolumeBackups,
s.layout.getBackupVolumeSnapshotsKey(info.Name): info.VolumeSnapshots,
s.layout.getItemSnapshotsKey(info.Name): info.ItemSnapshots,
s.layout.getBackupResourceListKey(info.Name): info.BackupResourceList,
s.layout.getCSIVolumeSnapshotKey(info.Name): info.CSIVolumeSnapshots,
s.layout.getCSIVolumeSnapshotContentsKey(info.Name): info.CSIVolumeSnapshotContents,
@@ -324,6 +320,27 @@ func (s *objectBackupStore) GetBackupVolumeSnapshots(name string) ([]*volume.Sna
return volumeSnapshots, nil
}
func (s *objectBackupStore) GetItemSnapshots(name string) ([]*volume.ItemSnapshot, error) {
// if the itemsnapshots file doesn't exist, we don't want to return an error, since
// a legacy backup or a backup with no snapshots would not have this file, so check for
// its existence before attempting to get its contents.
res, err := tryGet(s.objectStore, s.bucket, s.layout.getItemSnapshotsKey(name))
if err != nil {
return nil, err
}
if res == nil {
return nil, nil
}
defer res.Close()
var itemSnapshots []*volume.ItemSnapshot
if err := decode(res, &itemSnapshots); err != nil {
return nil, err
}
return itemSnapshots, nil
}
// tryGet returns the object with the given key if it exists, nil if it does not exist,
// or an error if it was unable to check existence or get the object.
func tryGet(objectStore velero.ObjectStore, bucket, key string) (io.ReadCloser, error) {
@@ -473,6 +490,8 @@ func (s *objectBackupStore) GetDownloadURL(target velerov1api.DownloadTarget) (s
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupLogKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupVolumeSnapshots:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupVolumeSnapshotsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupItemSnapshots:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getItemSnapshotsKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindBackupResourceList:
return s.objectStore.CreateSignedURL(s.bucket, s.layout.getBackupResourceListKey(target.Name), DownloadURLTTL)
case velerov1api.DownloadTargetKindRestoreLog:
+4
View File
@@ -88,6 +88,10 @@ func (l *ObjectStoreLayout) getBackupVolumeSnapshotsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumesnapshots.json.gz", backup))
}
func (l *ObjectStoreLayout) getItemSnapshotsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-itemsnapshots.json.gz", backup))
}
func (l *ObjectStoreLayout) getBackupResourceListKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-resource-list.json.gz", backup))
}
+73 -10
View File
@@ -223,6 +223,7 @@ func TestPutBackup(t *testing.T) {
log io.Reader
podVolumeBackup io.Reader
snapshots io.Reader
itemSnapshots io.Reader
resourceList io.Reader
expectedErr string
expectedKeys []string
@@ -234,6 +235,7 @@ func TestPutBackup(t *testing.T) {
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
expectedKeys: []string{
@@ -242,6 +244,7 @@ func TestPutBackup(t *testing.T) {
"backups/backup-1/backup-1-logs.gz",
"backups/backup-1/backup-1-podvolumebackups.json.gz",
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"backups/backup-1/backup-1-itemsnapshots.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
},
},
@@ -253,6 +256,7 @@ func TestPutBackup(t *testing.T) {
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
expectedKeys: []string{
@@ -261,6 +265,7 @@ func TestPutBackup(t *testing.T) {
"prefix-1/backups/backup-1/backup-1-logs.gz",
"prefix-1/backups/backup-1/backup-1-podvolumebackups.json.gz",
"prefix-1/backups/backup-1/backup-1-volumesnapshots.json.gz",
"prefix-1/backups/backup-1/backup-1-itemsnapshots.json.gz",
"prefix-1/backups/backup-1/backup-1-resource-list.json.gz",
},
},
@@ -271,19 +276,21 @@ func TestPutBackup(t *testing.T) {
log: newStringReadSeeker("log"),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
{
name: "error on data upload deletes metadata",
metadata: newStringReadSeeker("metadata"),
contents: new(errorReader),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
name: "error on data upload deletes metadata",
metadata: newStringReadSeeker("metadata"),
contents: new(errorReader),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
{
name: "error on log upload is ok",
@@ -292,6 +299,7 @@ func TestPutBackup(t *testing.T) {
log: new(errorReader),
podVolumeBackup: newStringReadSeeker("podVolumeBackup"),
snapshots: newStringReadSeeker("snapshots"),
itemSnapshots: newStringReadSeeker("itemSnapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
expectedKeys: []string{
@@ -299,11 +307,12 @@ func TestPutBackup(t *testing.T) {
"backups/backup-1/backup-1.tar.gz",
"backups/backup-1/backup-1-podvolumebackups.json.gz",
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"backups/backup-1/backup-1-itemsnapshots.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
},
},
{
name: "don't upload data when metadata is nil",
name: "data should be uploaded even when metadata is nil",
metadata: nil,
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
@@ -311,7 +320,13 @@ func TestPutBackup(t *testing.T) {
snapshots: newStringReadSeeker("snapshots"),
resourceList: newStringReadSeeker("resourceList"),
expectedErr: "",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
expectedKeys: []string{
"backups/backup-1/backup-1.tar.gz",
"backups/backup-1/backup-1-logs.gz",
"backups/backup-1/backup-1-podvolumebackups.json.gz",
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"backups/backup-1/backup-1-resource-list.json.gz",
},
},
}
@@ -326,6 +341,7 @@ func TestPutBackup(t *testing.T) {
Log: tc.log,
PodVolumeBackups: tc.podVolumeBackup,
VolumeSnapshots: tc.snapshots,
ItemSnapshots: tc.itemSnapshots,
BackupResourceList: tc.resourceList,
}
err := harness.PutBackup(backupInfo)
@@ -426,6 +442,48 @@ func TestGetBackupVolumeSnapshots(t *testing.T) {
assert.EqualValues(t, snapshots, res)
}
func TestGetItemSnapshots(t *testing.T) {
harness := newObjectBackupStoreTestHarness("test-bucket", "")
// volumesnapshots file not found should not error
harness.objectStore.PutObject(harness.bucket, "backups/test-backup/velero-backup.json", newStringReadSeeker("foo"))
res, err := harness.GetItemSnapshots("test-backup")
assert.NoError(t, err)
assert.Nil(t, res)
// volumesnapshots file containing invalid data should error
harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemsnapshots.json.gz", newStringReadSeeker("foo"))
res, err = harness.GetItemSnapshots("test-backup")
assert.NotNil(t, err)
// volumesnapshots file containing gzipped json data should return correctly
snapshots := []*volume.ItemSnapshot{
{
Spec: volume.ItemSnapshotSpec{
BackupName: "test-backup",
ResourceIdentifier: "item-1",
},
},
{
Spec: volume.ItemSnapshotSpec{
BackupName: "test-backup",
ResourceIdentifier: "item-2",
},
},
}
obj := new(bytes.Buffer)
gzw := gzip.NewWriter(obj)
require.NoError(t, json.NewEncoder(gzw).Encode(snapshots))
require.NoError(t, gzw.Close())
require.NoError(t, harness.objectStore.PutObject(harness.bucket, "backups/test-backup/test-backup-itemsnapshots.json.gz", obj))
res, err = harness.GetItemSnapshots("test-backup")
assert.NoError(t, err)
assert.EqualValues(t, snapshots, res)
}
func TestGetBackupContents(t *testing.T) {
harness := newObjectBackupStoreTestHarness("test-bucket", "")
@@ -506,6 +564,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "backups/my-backup/my-backup.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "backups/my-backup/my-backup-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/my-backup/my-backup-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/my-backup/my-backup-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "backups/my-backup/my-backup-resource-list.json.gz",
},
},
@@ -517,6 +576,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "velero-backups/backups/my-backup/my-backup.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "velero-backups/backups/my-backup/my-backup-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "velero-backups/backups/my-backup/my-backup-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "velero-backups/backups/my-backup/my-backup-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "velero-backups/backups/my-backup/my-backup-resource-list.json.gz",
},
},
@@ -527,6 +587,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "backups/b-cool-20170913154901-20170913154902/b-cool-20170913154901-20170913154902-resource-list.json.gz",
},
},
@@ -537,6 +598,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "backups/my-backup-20170913154901/my-backup-20170913154901.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "backups/my-backup-20170913154901/my-backup-20170913154901-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "backups/my-backup-20170913154901/my-backup-20170913154901-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "backups/my-backup-20170913154901/my-backup-20170913154901-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "backups/my-backup-20170913154901/my-backup-20170913154901-resource-list.json.gz",
},
},
@@ -548,6 +610,7 @@ func TestGetDownloadURL(t *testing.T) {
velerov1api.DownloadTargetKindBackupContents: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901.tar.gz",
velerov1api.DownloadTargetKindBackupLog: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-logs.gz",
velerov1api.DownloadTargetKindBackupVolumeSnapshots: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-volumesnapshots.json.gz",
velerov1api.DownloadTargetKindBackupItemSnapshots: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-itemsnapshots.json.gz",
velerov1api.DownloadTargetKindBackupResourceList: "velero-backups/backups/my-backup-20170913154901/my-backup-20170913154901-resource-list.json.gz",
},
},
+1
View File
@@ -73,6 +73,7 @@ func (b *clientBuilder) clientConfig() *hcplugin.ClientConfig {
string(framework.PluginKindPluginLister): &framework.PluginListerPlugin{},
string(framework.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(framework.ClientLogger(b.clientLogger)),
string(framework.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(framework.ClientLogger(b.clientLogger)),
string(framework.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(framework.ClientLogger(b.clientLogger)),
},
Logger: b.pluginLogger,
Cmd: exec.Command(b.commandName, b.commandArgs...),
@@ -66,6 +66,7 @@ func TestClientConfig(t *testing.T) {
string(framework.PluginKindPluginLister): &framework.PluginListerPlugin{},
string(framework.PluginKindRestoreItemAction): framework.NewRestoreItemActionPlugin(framework.ClientLogger(logger)),
string(framework.PluginKindDeleteItemAction): framework.NewDeleteItemActionPlugin(framework.ClientLogger(logger)),
string(framework.PluginKindItemSnapshotter): framework.NewItemSnapshotterPlugin(framework.ClientLogger(logger)),
},
Logger: cb.pluginLogger,
Cmd: exec.Command(cb.commandName, cb.commandArgs...),
+35
View File
@@ -18,6 +18,7 @@ package clientmgmt
import (
"fmt"
"io"
"log"
hclog "github.com/hashicorp/go-hclog"
@@ -162,3 +163,37 @@ func (l *logrusAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.L
func (l *logrusAdapter) SetLevel(_ hclog.Level) {
return
}
// ImpliedArgs returns With key/value pairs
func (l *logrusAdapter) ImpliedArgs() []interface{} {
panic("not implemented")
}
// Args are alternating key, val pairs
// keys must be strings
// vals can be any type, but display is implementation specific
// Emit a message and key/value pairs at a provided log level
func (l *logrusAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
switch level {
case hclog.Trace:
l.Trace(msg, args...)
case hclog.Debug:
l.Debug(msg, args...)
case hclog.Info:
l.Info(msg, args...)
case hclog.Warn:
l.Warn(msg, args...)
case hclog.Error:
l.Error(msg, args...)
}
}
// Returns the Name of the logger
func (l *logrusAdapter) Name() string {
return l.name
}
// Return a value that conforms to io.Writer, which can be passed into log.SetOutput()
func (l *logrusAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
panic("not implemented")
}
+39
View File
@@ -20,6 +20,8 @@ import (
"strings"
"sync"
v1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
"github.com/sirupsen/logrus"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
@@ -52,6 +54,12 @@ type Manager interface {
// GetDeleteItemAction returns the delete item action plugin for name.
GetDeleteItemAction(name string) (velero.DeleteItemAction, error)
// GetItemSnapshotter returns the item snapshotter plugin for name
GetItemSnapshotter(name string) (v1.ItemSnapshotter, error)
// GetItemSnapshotters returns all item snapshotter plugins
GetItemSnapshotters() ([]v1.ItemSnapshotter, error)
// CleanupClients terminates all of the Manager's running plugin processes.
CleanupClients()
}
@@ -256,6 +264,37 @@ func (m *manager) GetDeleteItemAction(name string) (velero.DeleteItemAction, err
return r, nil
}
func (m *manager) GetItemSnapshotter(name string) (v1.ItemSnapshotter, error) {
name = sanitizeName(name)
restartableProcess, err := m.getRestartableProcess(framework.PluginKindItemSnapshotter, name)
if err != nil {
return nil, err
}
r := newRestartableItemSnapshotter(name, restartableProcess)
return r, nil
}
func (m *manager) GetItemSnapshotters() ([]v1.ItemSnapshotter, error) {
list := m.registry.List(framework.PluginKindItemSnapshotter)
actions := make([]v1.ItemSnapshotter, 0, len(list))
for i := range list {
id := list[i]
r, err := m.GetItemSnapshotter(id.Name)
if err != nil {
return nil, err
}
actions = append(actions, r)
}
return actions, nil
}
// sanitizeName adds "velero.io" to legacy plugins that weren't namespaced.
func sanitizeName(name string) string {
// Backwards compatibility with non-namespaced Velero plugins, following principle of least surprise
@@ -0,0 +1,131 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clientmgmt
import (
"context"
"github.com/pkg/errors"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
type restartableItemSnapshotter struct {
key kindAndName
sharedPluginProcess RestartableProcess
}
// newRestartableItemSnapshotter returns a new newRestartableItemSnapshotter.
func newRestartableItemSnapshotter(name string, sharedPluginProcess RestartableProcess) *restartableItemSnapshotter {
r := &restartableItemSnapshotter{
key: kindAndName{kind: framework.PluginKindItemSnapshotter, name: name},
sharedPluginProcess: sharedPluginProcess,
}
return r
}
// getItemSnapshotter returns the item snapshotter for this restartableItemSnapshotter. It does *not* restart the
// plugin process.
func (r *restartableItemSnapshotter) getItemSnapshotter() (isv1.ItemSnapshotter, error) {
plugin, err := r.sharedPluginProcess.getByKindAndName(r.key)
if err != nil {
return nil, err
}
itemSnapshotter, ok := plugin.(isv1.ItemSnapshotter)
if !ok {
return nil, errors.Errorf("%T is not an ItemSnapshotter!", plugin)
}
return itemSnapshotter, nil
}
// getDelegate restarts the plugin process (if needed) and returns the item snapshotter for this restartableItemSnapshotter.
func (r *restartableItemSnapshotter) getDelegate() (isv1.ItemSnapshotter, error) {
if err := r.sharedPluginProcess.resetIfNeeded(); err != nil {
return nil, err
}
return r.getItemSnapshotter()
}
func (r *restartableItemSnapshotter) Init(config map[string]string) error {
delegate, err := r.getDelegate()
if err != nil {
return err
}
return delegate.Init(config)
}
// AppliesTo restarts the plugin's process if needed, then delegates the call.
func (r *restartableItemSnapshotter) AppliesTo() (velero.ResourceSelector, error) {
delegate, err := r.getDelegate()
if err != nil {
return velero.ResourceSelector{}, err
}
return delegate.AppliesTo()
}
func (r *restartableItemSnapshotter) AlsoHandles(input *isv1.AlsoHandlesInput) ([]velero.ResourceIdentifier, error) {
delegate, err := r.getDelegate()
if err != nil {
return nil, err
}
return delegate.AlsoHandles(input)
}
func (r *restartableItemSnapshotter) SnapshotItem(ctx context.Context, input *isv1.SnapshotItemInput) (*isv1.SnapshotItemOutput, error) {
delegate, err := r.getDelegate()
if err != nil {
return nil, err
}
return delegate.SnapshotItem(ctx, input)
}
func (r *restartableItemSnapshotter) Progress(input *isv1.ProgressInput) (*isv1.ProgressOutput, error) {
delegate, err := r.getDelegate()
if err != nil {
return nil, err
}
return delegate.Progress(input)
}
func (r *restartableItemSnapshotter) DeleteSnapshot(ctx context.Context, input *isv1.DeleteSnapshotInput) error {
delegate, err := r.getDelegate()
if err != nil {
return err
}
return delegate.DeleteSnapshot(ctx, input)
}
func (r *restartableItemSnapshotter) CreateItemFromSnapshot(ctx context.Context, input *isv1.CreateItemInput) (*isv1.CreateItemOutput, error) {
delegate, err := r.getDelegate()
if err != nil {
return nil, err
}
return delegate.CreateItemFromSnapshot(ctx, input)
}
@@ -0,0 +1,233 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package clientmgmt
import (
"context"
"testing"
"time"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
v1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1/mocks"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
func TestRestartableGetItemSnapshotter(t *testing.T) {
tests := []struct {
name string
plugin interface{}
getError error
expectedError string
}{
{
name: "error getting by kind and name",
getError: errors.Errorf("get error"),
expectedError: "get error",
},
{
name: "wrong type",
plugin: 3,
expectedError: "int is not an ItemSnapshotter!",
},
{
name: "happy path",
plugin: new(mocks.ItemSnapshotter),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := new(mockRestartableProcess)
defer p.AssertExpectations(t)
name := "pvc"
key := kindAndName{kind: framework.PluginKindItemSnapshotter, name: name}
p.On("getByKindAndName", key).Return(tc.plugin, tc.getError)
r := newRestartableItemSnapshotter(name, p)
a, err := r.getItemSnapshotter()
if tc.expectedError != "" {
assert.EqualError(t, err, tc.expectedError)
return
}
require.NoError(t, err)
assert.Equal(t, tc.plugin, a)
})
}
}
func TestRestartableItemSnapshotterGetDelegate(t *testing.T) {
p := new(mockRestartableProcess)
defer p.AssertExpectations(t)
// Reset error
p.On("resetIfNeeded").Return(errors.Errorf("reset error")).Once()
name := "pvc"
r := newRestartableItemSnapshotter(name, p)
a, err := r.getDelegate()
assert.Nil(t, a)
assert.EqualError(t, err, "reset error")
// Happy path
p.On("resetIfNeeded").Return(nil)
expected := new(mocks.ItemSnapshotter)
key := kindAndName{kind: framework.PluginKindItemSnapshotter, name: name}
p.On("getByKindAndName", key).Return(expected, nil)
a, err = r.getDelegate()
assert.NoError(t, err)
assert.Equal(t, expected, a)
}
func TestRestartableItemSnasphotterDelegatedFunctions(t *testing.T) {
b := new(v1.Backup)
pv := &unstructured.Unstructured{
Object: map[string]interface{}{
"color": "blue",
},
}
sii := &isv1.SnapshotItemInput{
Item: pv,
Params: nil,
Backup: b,
}
ctx := context.Background()
pvToReturn := &unstructured.Unstructured{
Object: map[string]interface{}{
"color": "green",
},
}
additionalItems := []velero.ResourceIdentifier{
{
GroupResource: schema.GroupResource{Group: "velero.io", Resource: "backups"},
},
}
sio := &isv1.SnapshotItemOutput{
UpdatedItem: pvToReturn,
SnapshotID: "",
SnapshotMetadata: nil,
AdditionalItems: additionalItems,
HandledItems: nil,
}
cii := &isv1.CreateItemInput{
SnapshottedItem: nil,
SnapshotID: "",
ItemFromBackup: nil,
SnapshotMetadata: nil,
Params: nil,
Restore: nil,
}
cio := &isv1.CreateItemOutput{
UpdatedItem: nil,
AdditionalItems: nil,
SkipRestore: false,
}
pi := &isv1.ProgressInput{
ItemID: velero.ResourceIdentifier{},
SnapshotID: "",
Backup: nil,
}
po := &isv1.ProgressOutput{
Phase: isv1.SnapshotPhaseInProgress,
Err: "",
ItemsCompleted: 0,
ItemsToComplete: 0,
Started: time.Time{},
Updated: time.Time{},
}
dsi := &isv1.DeleteSnapshotInput{
SnapshotID: "",
ItemFromBackup: nil,
SnapshotMetadata: nil,
Params: nil,
}
runRestartableDelegateTests(
t,
framework.PluginKindItemSnapshotter,
func(key kindAndName, p RestartableProcess) interface{} {
return &restartableItemSnapshotter{
key: key,
sharedPluginProcess: p,
}
},
func() mockable {
return new(mocks.ItemSnapshotter)
},
restartableDelegateTest{
function: "Init",
inputs: []interface{}{map[string]string{}},
expectedErrorOutputs: []interface{}{errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")},
},
restartableDelegateTest{
function: "AppliesTo",
inputs: []interface{}{},
expectedErrorOutputs: []interface{}{velero.ResourceSelector{}, errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{velero.ResourceSelector{IncludedNamespaces: []string{"a"}}, errors.Errorf("delegate error")},
},
restartableDelegateTest{
function: "AlsoHandles",
inputs: []interface{}{&isv1.AlsoHandlesInput{}},
expectedErrorOutputs: []interface{}{[]velero.ResourceIdentifier([]velero.ResourceIdentifier(nil)), errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{[]velero.ResourceIdentifier([]velero.ResourceIdentifier(nil)), errors.Errorf("delegate error")},
},
restartableDelegateTest{
function: "SnapshotItem",
inputs: []interface{}{ctx, sii},
expectedErrorOutputs: []interface{}{nil, errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{sio, errors.Errorf("delegate error")},
},
restartableDelegateTest{
function: "CreateItemFromSnapshot",
inputs: []interface{}{ctx, cii},
expectedErrorOutputs: []interface{}{nil, errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{cio, errors.Errorf("delegate error")},
},
restartableDelegateTest{
function: "Progress",
inputs: []interface{}{pi},
expectedErrorOutputs: []interface{}{nil, errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{po, errors.Errorf("delegate error")},
},
restartableDelegateTest{
function: "DeleteSnapshot",
inputs: []interface{}{ctx, dsi},
expectedErrorOutputs: []interface{}{errors.Errorf("reset error")},
expectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")},
},
)
}
+242
View File
@@ -0,0 +1,242 @@
/*
Copyright the Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
"github.com/vmware-tanzu/velero/pkg/discovery"
"github.com/vmware-tanzu/velero/pkg/util/collections"
)
/*
Velero has a variety of Actions that can be executed on Kubernetes resources. The Actions (BackupItemAction, RestoreItemAction
and others) implement the Applicable interface which returns a ResourceSelector for the Action. The ResourceSelector
can specify namespaces, resource names and labels to include or exclude. The ResourceSelector is resolved into lists
of namespaces and resources present in the backup to be matched against. These lists and the label selector are then used to
decide whether or not the ResolvedAction should be used for a particular resource.
*/
// ResolvedAction is an action that has had the namespaces, resources names and labels to include or exclude resolved
type ResolvedAction interface {
// ShouldUse returns true if the resolved namespaces, resource names and labels match those passed in the parameters.
// metadata is optional and may be nil
ShouldUse(groupResource schema.GroupResource, namespace string, metadata metav1.Object,
log logrus.FieldLogger) bool
}
// resolvedAction is a core struct that holds the resolved namespaces, resource names and labels
type resolvedAction struct {
ResourceIncludesExcludes *collections.IncludesExcludes
NamespaceIncludesExcludes *collections.IncludesExcludes
Selector labels.Selector
}
func (recv resolvedAction) ShouldUse(groupResource schema.GroupResource, namespace string, metadata metav1.Object,
log logrus.FieldLogger) bool {
if !recv.ResourceIncludesExcludes.ShouldInclude(groupResource.String()) {
log.Debug("Skipping action because it does not apply to this resource")
return false
}
if namespace != "" && !recv.NamespaceIncludesExcludes.ShouldInclude(namespace) {
log.Debug("Skipping action because it does not apply to this namespace")
return false
}
if namespace == "" && !recv.NamespaceIncludesExcludes.IncludeEverything() {
log.Debug("Skipping action because resource is cluster-scoped and action only applies to specific namespaces")
return false
}
if metadata != nil && !recv.Selector.Matches(labels.Set(metadata.GetLabels())) {
log.Debug("Skipping action because label selector does not match")
return false
}
return true
}
// resolveAction resolves the resources, namespaces and selector into fully-qualified versions
func resolveAction(helper discovery.Helper, action velero.Applicable) (resources *collections.IncludesExcludes,
namespaces *collections.IncludesExcludes, selector labels.Selector, err error) {
resourceSelector, err := action.AppliesTo()
if err != nil {
return nil, nil, nil, err
}
resources = collections.GetResourceIncludesExcludes(helper, resourceSelector.IncludedResources, resourceSelector.ExcludedResources)
namespaces = collections.NewIncludesExcludes().Includes(resourceSelector.IncludedNamespaces...).Excludes(resourceSelector.ExcludedNamespaces...)
selector = labels.Everything()
if resourceSelector.LabelSelector != "" {
if selector, err = labels.Parse(resourceSelector.LabelSelector); err != nil {
return nil, nil, nil, err
}
}
return
}
type BackupItemResolvedAction struct {
velero.BackupItemAction
resolvedAction
}
func NewBackupItemActionResolver(actions []velero.BackupItemAction) BackupItemActionResolver {
return BackupItemActionResolver{
actions: actions,
}
}
func NewRestoreItemActionResolver(actions []velero.RestoreItemAction) RestoreItemActionResolver {
return RestoreItemActionResolver{
actions: actions,
}
}
func NewDeleteItemActionResolver(actions []velero.DeleteItemAction) DeleteItemActionResolver {
return DeleteItemActionResolver{
actions: actions,
}
}
func NewItemSnapshotterResolver(actions []isv1.ItemSnapshotter) ItemSnapshotterResolver {
return ItemSnapshotterResolver{
actions: actions,
}
}
type ActionResolver interface {
ResolveAction(helper discovery.Helper, action velero.Applicable) (ResolvedAction, error)
}
type BackupItemActionResolver struct {
actions []velero.BackupItemAction
}
func (recv BackupItemActionResolver) ResolveActions(helper discovery.Helper) ([]BackupItemResolvedAction, error) {
var resolved []BackupItemResolvedAction
for _, action := range recv.actions {
resources, namespaces, selector, err := resolveAction(helper, action)
if err != nil {
return nil, err
}
res := BackupItemResolvedAction{
BackupItemAction: action,
resolvedAction: resolvedAction{
ResourceIncludesExcludes: resources,
NamespaceIncludesExcludes: namespaces,
Selector: selector,
},
}
resolved = append(resolved, res)
}
return resolved, nil
}
type RestoreItemResolvedAction struct {
velero.RestoreItemAction
resolvedAction
}
type RestoreItemActionResolver struct {
actions []velero.RestoreItemAction
}
func (recv RestoreItemActionResolver) ResolveActions(helper discovery.Helper) ([]RestoreItemResolvedAction, error) {
var resolved []RestoreItemResolvedAction
for _, action := range recv.actions {
resources, namespaces, selector, err := resolveAction(helper, action)
if err != nil {
return nil, err
}
res := RestoreItemResolvedAction{
RestoreItemAction: action,
resolvedAction: resolvedAction{
ResourceIncludesExcludes: resources,
NamespaceIncludesExcludes: namespaces,
Selector: selector,
},
}
resolved = append(resolved, res)
}
return resolved, nil
}
type DeleteItemResolvedAction struct {
velero.DeleteItemAction
resolvedAction
}
type DeleteItemActionResolver struct {
actions []velero.DeleteItemAction
}
func (recv DeleteItemActionResolver) ResolveActions(helper discovery.Helper) ([]DeleteItemResolvedAction, error) {
var resolved []DeleteItemResolvedAction
for _, action := range recv.actions {
resources, namespaces, selector, err := resolveAction(helper, action)
if err != nil {
return nil, err
}
res := DeleteItemResolvedAction{
DeleteItemAction: action,
resolvedAction: resolvedAction{
ResourceIncludesExcludes: resources,
NamespaceIncludesExcludes: namespaces,
Selector: selector,
},
}
resolved = append(resolved, res)
}
return resolved, nil
}
type ItemSnapshotterResolvedAction struct {
isv1.ItemSnapshotter
resolvedAction
}
type ItemSnapshotterResolver struct {
actions []isv1.ItemSnapshotter
}
func (recv ItemSnapshotterResolver) ResolveActions(helper discovery.Helper) ([]ItemSnapshotterResolvedAction, error) {
var resolved []ItemSnapshotterResolvedAction
for _, action := range recv.actions {
resources, namespaces, selector, err := resolveAction(helper, action)
if err != nil {
return nil, err
}
res := ItemSnapshotterResolvedAction{
ItemSnapshotter: action,
resolvedAction: resolvedAction{
ResourceIncludesExcludes: resources,
NamespaceIncludesExcludes: namespaces,
Selector: selector,
},
}
resolved = append(resolved, res)
}
return resolved, nil
}
@@ -0,0 +1,93 @@
/*
Copyright the Velero Contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"testing"
"k8s.io/apimachinery/pkg/labels"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
type mockApplicable struct {
selector velero.ResourceSelector
}
func (recv mockApplicable) AppliesTo() (velero.ResourceSelector, error) {
return recv.selector, nil
}
func TestActionResolverNamespace(t *testing.T) {
discoveryHelper := velerotest.NewFakeDiscoveryHelper(false, map[schema.GroupVersionResource]schema.GroupVersionResource{})
namespaceMatchApplicable := mockApplicable{
selector: velero.ResourceSelector{
IncludedNamespaces: []string{"default"},
},
}
resources, namespaces, selector, err := resolveAction(discoveryHelper, namespaceMatchApplicable)
require.NoError(t, err)
require.Equal(t, []string{"default"}, namespaces.GetIncludes())
require.Empty(t, namespaces.GetExcludes())
require.Empty(t, resources.GetIncludes())
require.Empty(t, resources.GetExcludes())
require.True(t, selector.Empty())
}
func TestActionResolverResource(t *testing.T) {
pvGVR := schema.GroupVersionResource{
Group: "",
Version: "v1",
Resource: "persistentvolumes",
}
discoveryHelper := velerotest.NewFakeDiscoveryHelper(false, map[schema.GroupVersionResource]schema.GroupVersionResource{pvGVR: pvGVR})
namespaceMatchApplicable := mockApplicable{
selector: velero.ResourceSelector{
IncludedResources: []string{"persistentvolumes"},
},
}
resources, namespaces, selector, err := resolveAction(discoveryHelper, namespaceMatchApplicable)
require.NoError(t, err)
require.Empty(t, namespaces.GetIncludes())
require.Empty(t, namespaces.GetExcludes())
require.True(t, resources.ShouldInclude("persistentvolumes"))
require.Empty(t, resources.GetExcludes())
require.True(t, selector.Empty())
}
func TestActionResolverLabel(t *testing.T) {
discoveryHelper := velerotest.NewFakeDiscoveryHelper(false, map[schema.GroupVersionResource]schema.GroupVersionResource{})
namespaceMatchApplicable := mockApplicable{
selector: velero.ResourceSelector{
LabelSelector: "myLabel=true",
},
}
checkLabel, err := labels.ConvertSelectorToLabelsMap("myLabel=true")
require.NoError(t, err)
resources, namespaces, selector, err := resolveAction(discoveryHelper, namespaceMatchApplicable)
require.NoError(t, err)
require.Empty(t, namespaces.GetIncludes())
require.Empty(t, namespaces.GetExcludes())
require.Empty(t, resources.GetIncludes())
require.Empty(t, resources.GetExcludes())
require.True(t, selector.Matches(checkLabel))
}
+44
View File
@@ -0,0 +1,44 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
plugin "github.com/hashicorp/go-plugin"
"golang.org/x/net/context"
"google.golang.org/grpc"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
)
// ItemSnapshotterPlugin is an implementation of go-plugin's Plugin
// interface with support for gRPC for the ItemSnapshotter
// interface.
type ItemSnapshotterPlugin struct {
plugin.NetRPCUnsupportedPlugin
*pluginBase
}
// GRPCClient returns a clientDispenser for ItemSnapshotter gRPC clients.
func (p *ItemSnapshotterPlugin) GRPCClient(_ context.Context, _ *plugin.GRPCBroker, clientConn *grpc.ClientConn) (interface{}, error) {
return newClientDispenser(p.clientLogger, clientConn, newItemSnapshotterGRPCClient), nil
}
// GRPCServer registers an ItemSnapshotter gRPC server.
func (p *ItemSnapshotterPlugin) GRPCServer(_ *plugin.GRPCBroker, server *grpc.Server) error {
proto.RegisterItemSnapshotterServer(server, &ItemSnapshotterGRPCServer{mux: p.serverMux})
return nil
}
@@ -0,0 +1,240 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"context"
"encoding/json"
"time"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
"github.com/pkg/errors"
"google.golang.org/grpc"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// NewItemSnapshotterPlugin constructs a ItemSnapshotterPlugin.
func NewItemSnapshotterPlugin(options ...PluginOption) *ItemSnapshotterPlugin {
return &ItemSnapshotterPlugin{
pluginBase: newPluginBase(options...),
}
}
func newItemSnapshotterGRPCClient(base *clientBase, clientConn *grpc.ClientConn) interface{} {
return &ItemSnapshotterGRPCClient{
clientBase: base,
grpcClient: proto.NewItemSnapshotterClient(clientConn),
}
}
// ItemSnapshotterGRPCClient implements the ItemSnapshotter interface and uses a
// gRPC client to make calls to the plugin server.
type ItemSnapshotterGRPCClient struct {
*clientBase
grpcClient proto.ItemSnapshotterClient
}
func (recv ItemSnapshotterGRPCClient) Init(config map[string]string) error {
req := &proto.ItemSnapshotterInitRequest{
Plugin: recv.plugin,
Config: config,
}
_, err := recv.grpcClient.Init(context.Background(), req)
return err
}
func (recv ItemSnapshotterGRPCClient) AppliesTo() (velero.ResourceSelector, error) {
req := &proto.ItemSnapshotterAppliesToRequest{
Plugin: recv.plugin,
}
res, err := recv.grpcClient.AppliesTo(context.Background(), req)
if err != nil {
return velero.ResourceSelector{}, fromGRPCError(err)
}
if res.ResourceSelector == nil {
return velero.ResourceSelector{}, nil
}
return velero.ResourceSelector{
IncludedNamespaces: res.ResourceSelector.IncludedNamespaces,
ExcludedNamespaces: res.ResourceSelector.ExcludedNamespaces,
IncludedResources: res.ResourceSelector.IncludedResources,
ExcludedResources: res.ResourceSelector.ExcludedResources,
LabelSelector: res.ResourceSelector.Selector,
}, nil
}
func (recv ItemSnapshotterGRPCClient) AlsoHandles(input *isv1.AlsoHandlesInput) ([]velero.ResourceIdentifier, error) {
itemJSON, err := json.Marshal(input.Item.UnstructuredContent())
if err != nil {
return nil, errors.WithStack(err)
}
backupJSON, err := json.Marshal(input.Backup)
if err != nil {
return nil, errors.WithStack(err)
}
req := &proto.AlsoHandlesRequest{
Plugin: recv.plugin,
Item: itemJSON,
Backup: backupJSON,
}
res, err := recv.grpcClient.AlsoHandles(context.Background(), req)
if err != nil {
return nil, errors.WithStack(err)
}
handledItems := unpackResourceIdentifiers(res.HandledItems)
return handledItems, nil
}
func (recv ItemSnapshotterGRPCClient) SnapshotItem(ctx context.Context, input *isv1.SnapshotItemInput) (*isv1.SnapshotItemOutput, error) {
itemJSON, err := json.Marshal(input.Item.UnstructuredContent())
if err != nil {
return nil, errors.WithStack(err)
}
backupJSON, err := json.Marshal(input.Backup)
if err != nil {
return nil, errors.WithStack(err)
}
req := &proto.SnapshotItemRequest{
Plugin: recv.plugin,
Item: itemJSON,
Backup: backupJSON,
}
res, err := recv.grpcClient.SnapshotItem(ctx, req)
if err != nil {
return nil, errors.WithStack(err)
}
var updatedItem unstructured.Unstructured
if err := json.Unmarshal(res.Item, &updatedItem); err != nil {
return nil, errors.WithStack(err)
}
additionalItems := unpackResourceIdentifiers(res.AdditionalItems)
handledItems := unpackResourceIdentifiers(res.HandledItems)
sio := isv1.SnapshotItemOutput{
UpdatedItem: &updatedItem,
SnapshotID: res.SnapshotID,
SnapshotMetadata: res.SnapshotMetadata,
AdditionalItems: additionalItems,
HandledItems: handledItems,
}
return &sio, nil
}
func (recv ItemSnapshotterGRPCClient) Progress(input *isv1.ProgressInput) (*isv1.ProgressOutput, error) {
backupJSON, err := json.Marshal(input.Backup)
if err != nil {
return nil, errors.WithStack(err)
}
req := &proto.ProgressRequest{
Plugin: recv.plugin,
ItemID: resourceIdentifierToProto(input.ItemID),
SnapshotID: input.SnapshotID,
Backup: backupJSON,
}
res, err := recv.grpcClient.Progress(context.Background(), req)
if err != nil {
return nil, errors.WithStack(err)
}
// Validate phase
phase, err := isv1.SnapshotPhaseFromString(res.Phase)
if err != nil {
return nil, errors.WithStack(err)
}
up := isv1.ProgressOutput{
Phase: phase,
Err: res.Err,
ItemsCompleted: res.ItemsCompleted,
ItemsToComplete: res.ItemsToComplete,
Started: time.Unix(res.Started, res.StartedNano),
Updated: time.Unix(res.Updated, res.UpdatedNano),
}
return &up, nil
}
func (recv ItemSnapshotterGRPCClient) DeleteSnapshot(ctx context.Context, input *isv1.DeleteSnapshotInput) error {
req := &proto.DeleteItemSnapshotRequest{
Plugin: recv.plugin,
Params: input.Params,
SnapshotID: input.SnapshotID,
}
_, err := recv.grpcClient.DeleteSnapshot(ctx, req) // Returns Empty as first arg so just ignore
if err != nil {
return errors.WithStack(err)
}
return nil
}
func (recv ItemSnapshotterGRPCClient) CreateItemFromSnapshot(ctx context.Context, input *isv1.CreateItemInput) (*isv1.CreateItemOutput, error) {
itemJSON, err := json.Marshal(input.SnapshottedItem.UnstructuredContent())
if err != nil {
return nil, errors.WithStack(err)
}
itemFromBackupJSON, err := json.Marshal(input.ItemFromBackup.UnstructuredContent())
if err != nil {
return nil, errors.WithStack(err)
}
restoreJSON, err := json.Marshal(input.Restore)
if err != nil {
return nil, errors.WithStack(err)
}
req := &proto.CreateItemFromSnapshotRequest{
Plugin: recv.plugin,
Item: itemJSON,
SnapshotID: input.SnapshotID,
ItemFromBackup: itemFromBackupJSON,
SnapshotMetadata: input.SnapshotMetadata,
Params: input.Params,
Restore: restoreJSON,
}
res, err := recv.grpcClient.CreateItemFromSnapshot(ctx, req)
if err != nil {
return nil, errors.WithStack(err)
}
var updatedItem unstructured.Unstructured
if err := json.Unmarshal(res.Item, &updatedItem); err != nil {
return nil, errors.WithStack(err)
}
additionalItems := unpackResourceIdentifiers(res.AdditionalItems)
cio := isv1.CreateItemOutput{
UpdatedItem: &updatedItem,
AdditionalItems: additionalItems,
SkipRestore: res.SkipRestore,
}
return &cio, nil
}
@@ -0,0 +1,311 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"context"
"encoding/json"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/pkg/errors"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
)
// ItemSnapshotterGRPCServer implements the proto-generated ItemSnapshotterServer interface, and accepts
// gRPC calls and forwards them to an implementation of the pluggable interface.
type ItemSnapshotterGRPCServer struct {
mux *serverMux
}
func (recv *ItemSnapshotterGRPCServer) getImpl(name string) (isv1.ItemSnapshotter, error) {
impl, err := recv.mux.getHandler(name)
if err != nil {
return nil, err
}
itemAction, ok := impl.(isv1.ItemSnapshotter)
if !ok {
return nil, errors.Errorf("%T is not an item snapshotter", impl)
}
return itemAction, nil
}
func (recv *ItemSnapshotterGRPCServer) Init(c context.Context, req *proto.ItemSnapshotterInitRequest) (response *proto.Empty, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
err = impl.Init(req.Config)
if err != nil {
return nil, newGRPCError(err)
}
return &proto.Empty{}, nil
}
func (recv *ItemSnapshotterGRPCServer) AppliesTo(ctx context.Context, req *proto.ItemSnapshotterAppliesToRequest) (response *proto.ItemSnapshotterAppliesToResponse, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
resourceSelector, err := impl.AppliesTo()
if err != nil {
return nil, newGRPCError(err)
}
return &proto.ItemSnapshotterAppliesToResponse{
&proto.ResourceSelector{
IncludedNamespaces: resourceSelector.IncludedNamespaces,
ExcludedNamespaces: resourceSelector.ExcludedNamespaces,
IncludedResources: resourceSelector.IncludedResources,
ExcludedResources: resourceSelector.ExcludedResources,
Selector: resourceSelector.LabelSelector,
},
}, nil
}
func (recv *ItemSnapshotterGRPCServer) AlsoHandles(ctx context.Context, req *proto.AlsoHandlesRequest) (res *proto.AlsoHandlesResponse, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
var item unstructured.Unstructured
var backup api.Backup
if err := json.Unmarshal(req.Item, &item); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
if err := json.Unmarshal(req.Backup, &backup); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
ahi := isv1.AlsoHandlesInput{
Item: &item,
Backup: &backup,
}
alsoHandles, err := impl.AlsoHandles(&ahi)
if err != nil {
return nil, newGRPCError(err)
}
res = &proto.AlsoHandlesResponse{}
for _, item := range alsoHandles {
res.HandledItems = append(res.HandledItems, resourceIdentifierToProto(item))
}
return res, nil
}
func (recv *ItemSnapshotterGRPCServer) SnapshotItem(ctx context.Context, req *proto.SnapshotItemRequest) (res *proto.SnapshotItemResponse, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
var item unstructured.Unstructured
var backup api.Backup
if err := json.Unmarshal(req.Item, &item); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
if err := json.Unmarshal(req.Backup, &backup); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
sii := isv1.SnapshotItemInput{
Item: &item,
Params: req.Params,
Backup: &backup,
}
sio, err := impl.SnapshotItem(ctx, &sii)
// If the plugin implementation returned a nil updatedItem (meaning no modifications), reset updatedItem to the
// original item.
var updatedItemJSON []byte
if sio.UpdatedItem == nil {
updatedItemJSON = req.Item
} else {
updatedItemJSON, err = json.Marshal(sio.UpdatedItem.UnstructuredContent())
if err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
}
res = &proto.SnapshotItemResponse{
Item: updatedItemJSON,
SnapshotID: sio.SnapshotID,
SnapshotMetadata: sio.SnapshotMetadata,
}
res.AdditionalItems = packResourceIdentifiers(sio.AdditionalItems)
res.HandledItems = packResourceIdentifiers(sio.HandledItems)
return res, err
}
func (recv *ItemSnapshotterGRPCServer) Progress(ctx context.Context, req *proto.ProgressRequest) (res *proto.ProgressResponse, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
var backup api.Backup
if err := json.Unmarshal(req.Backup, &backup); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
sipi := &isv1.ProgressInput{
ItemID: protoToResourceIdentifier(req.ItemID),
SnapshotID: req.SnapshotID,
Backup: &backup,
}
sipo, err := impl.Progress(sipi)
if err != nil {
return nil, newGRPCError(err)
}
res = &proto.ProgressResponse{
Phase: string(sipo.Phase),
ItemsCompleted: sipo.ItemsCompleted,
ItemsToComplete: sipo.ItemsToComplete,
Started: sipo.Started.Unix(),
StartedNano: sipo.Started.UnixNano(),
Updated: sipo.Updated.Unix(),
UpdatedNano: sipo.Updated.UnixNano(),
Err: sipo.Err,
}
return res, nil
}
func (recv *ItemSnapshotterGRPCServer) DeleteSnapshot(ctx context.Context, req *proto.DeleteItemSnapshotRequest) (empty *proto.Empty, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
var itemFromBackup unstructured.Unstructured
if err := json.Unmarshal(req.ItemFromBackup, &itemFromBackup); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
disi := isv1.DeleteSnapshotInput{
SnapshotID: req.SnapshotID,
ItemFromBackup: &itemFromBackup,
SnapshotMetadata: req.Metadata,
Params: req.Params,
}
err = impl.DeleteSnapshot(ctx, &disi)
if err != nil {
return nil, newGRPCError(err)
}
return
}
func (recv *ItemSnapshotterGRPCServer) CreateItemFromSnapshot(ctx context.Context, req *proto.CreateItemFromSnapshotRequest) (res *proto.CreateItemFromSnapshotResponse, err error) {
defer func() {
if recoveredErr := handlePanic(recover()); recoveredErr != nil {
err = recoveredErr
}
}()
impl, err := recv.getImpl(req.Plugin)
if err != nil {
return nil, newGRPCError(err)
}
var snapshottedItem unstructured.Unstructured
if err := json.Unmarshal(req.Item, &snapshottedItem); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
var itemFromBackup unstructured.Unstructured
if err := json.Unmarshal(req.Item, &itemFromBackup); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
var restore api.Restore
if err := json.Unmarshal(req.Restore, &restore); err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
cii := isv1.CreateItemInput{
SnapshottedItem: &snapshottedItem,
SnapshotID: req.SnapshotID,
ItemFromBackup: &itemFromBackup,
SnapshotMetadata: req.SnapshotMetadata,
Params: req.Params,
Restore: &restore,
}
cio, err := impl.CreateItemFromSnapshot(ctx, &cii)
if err != nil {
return nil, newGRPCError(err)
}
var updatedItemJSON []byte
if cio.UpdatedItem == nil {
updatedItemJSON = req.Item
} else {
updatedItemJSON, err = json.Marshal(cio.UpdatedItem.UnstructuredContent())
if err != nil {
return nil, newGRPCError(errors.WithStack(err))
}
}
res = &proto.CreateItemFromSnapshotResponse{
Item: updatedItemJSON,
SkipRestore: cio.SkipRestore,
}
res.AdditionalItems = packResourceIdentifiers(cio.AdditionalItems)
return
}
+4
View File
@@ -41,6 +41,9 @@ const (
// PluginKindDeleteItemAction represents a delete item action plugin.
PluginKindDeleteItemAction PluginKind = "DeleteItemAction"
// PluginKindItemSnapshotter represents an item snapshotter plugin
PluginKindItemSnapshotter PluginKind = "ItemSnapshotter"
// PluginKindPluginLister represents a plugin lister plugin.
PluginKindPluginLister PluginKind = "PluginLister"
)
@@ -54,5 +57,6 @@ func AllPluginKinds() map[string]PluginKind {
allPluginKinds[PluginKindBackupItemAction.String()] = PluginKindBackupItemAction
allPluginKinds[PluginKindRestoreItemAction.String()] = PluginKindRestoreItemAction
allPluginKinds[PluginKindDeleteItemAction.String()] = PluginKindDeleteItemAction
allPluginKinds[PluginKindItemSnapshotter.String()] = PluginKindItemSnapshotter
return allPluginKinds
}
@@ -30,6 +30,7 @@ func TestPluginImplementationsAreGRPCPlugins(t *testing.T) {
new(ObjectStorePlugin),
new(PluginListerPlugin),
new(RestoreItemActionPlugin),
new(ItemSnapshotterPlugin),
}
for _, impl := range pluginImpls {
+19
View File
@@ -74,6 +74,10 @@ type Server interface {
// RegisterDeleteItemActions registers multiple Delete item actions.
RegisterDeleteItemActions(map[string]HandlerInitializer) Server
RegisterItemSnapshotter(pluginName string, initializer HandlerInitializer) Server
// RegisterItemSnapshotters registers multiple Item Snapshotters
RegisterItemSnapshotters(map[string]HandlerInitializer) Server
// Server runs the plugin server.
Serve()
}
@@ -89,6 +93,7 @@ type server struct {
objectStore *ObjectStorePlugin
restoreItemAction *RestoreItemActionPlugin
deleteItemAction *DeleteItemActionPlugin
itemSnapshotter *ItemSnapshotterPlugin
}
// NewServer returns a new Server
@@ -105,6 +110,7 @@ func NewServer() Server {
objectStore: NewObjectStorePlugin(serverLogger(log)),
restoreItemAction: NewRestoreItemActionPlugin(serverLogger(log)),
deleteItemAction: NewDeleteItemActionPlugin(serverLogger(log)),
itemSnapshotter: NewItemSnapshotterPlugin(serverLogger(log)),
}
}
@@ -177,6 +183,17 @@ func (s *server) RegisterDeleteItemActions(m map[string]HandlerInitializer) Serv
return s
}
func (s *server) RegisterItemSnapshotter(name string, initializer HandlerInitializer) Server {
s.itemSnapshotter.register(name, initializer)
return s
}
func (s *server) RegisterItemSnapshotters(m map[string]HandlerInitializer) Server {
for name := range m {
s.RegisterItemSnapshotter(name, m[name])
}
return s
}
// getNames returns a list of PluginIdentifiers registered with plugin.
func getNames(command string, kind PluginKind, plugin Interface) []PluginIdentifier {
var pluginIdentifiers []PluginIdentifier
@@ -206,6 +223,7 @@ func (s *server) Serve() {
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindObjectStore, s.objectStore)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindRestoreItemAction, s.restoreItemAction)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindDeleteItemAction, s.deleteItemAction)...)
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindItemSnapshotter, s.itemSnapshotter)...)
pluginLister := NewPluginLister(pluginIdentifiers...)
@@ -218,6 +236,7 @@ func (s *server) Serve() {
string(PluginKindPluginLister): NewPluginListerPlugin(pluginLister),
string(PluginKindRestoreItemAction): s.restoreItemAction,
string(PluginKindDeleteItemAction): s.deleteItemAction,
string(PluginKindItemSnapshotter): s.itemSnapshotter,
},
GRPCServer: plugin.DefaultGRPCServer,
})
+58
View File
@@ -0,0 +1,58 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"k8s.io/apimachinery/pkg/runtime/schema"
proto "github.com/vmware-tanzu/velero/pkg/plugin/generated"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
func packResourceIdentifiers(resourcesIDs []velero.ResourceIdentifier) (protoIDs []*proto.ResourceIdentifier) {
for _, item := range resourcesIDs {
protoIDs = append(protoIDs, resourceIdentifierToProto(item))
}
return
}
func unpackResourceIdentifiers(protoIDs []*proto.ResourceIdentifier) (resourceIDs []velero.ResourceIdentifier) {
for _, itm := range protoIDs {
resourceIDs = append(resourceIDs, protoToResourceIdentifier(itm))
}
return
}
func protoToResourceIdentifier(proto *proto.ResourceIdentifier) velero.ResourceIdentifier {
return velero.ResourceIdentifier{
GroupResource: schema.GroupResource{
Group: proto.Group,
Resource: proto.Resource,
},
Namespace: proto.Namespace,
Name: proto.Name,
}
}
func resourceIdentifierToProto(id velero.ResourceIdentifier) *proto.ResourceIdentifier {
return &proto.ResourceIdentifier{
Group: id.Group,
Resource: id.Resource,
Namespace: id.Namespace,
Name: id.Name,
}
}
@@ -7,6 +7,7 @@ Package generated is a generated protocol buffer package.
It is generated from these files:
BackupItemAction.proto
DeleteItemAction.proto
ItemSnapshotter.proto
ObjectStore.proto
PluginLister.proto
RestoreItemAction.proto
@@ -21,6 +22,18 @@ It has these top-level messages:
DeleteItemActionExecuteRequest
DeleteItemActionAppliesToRequest
DeleteItemActionAppliesToResponse
ItemSnapshotterAppliesToRequest
ItemSnapshotterAppliesToResponse
AlsoHandlesRequest
AlsoHandlesResponse
SnapshotItemRequest
SnapshotItemResponse
ProgressRequest
ProgressResponse
DeleteItemSnapshotRequest
CreateItemFromSnapshotRequest
CreateItemFromSnapshotResponse
ItemSnapshotterInitRequest
PutObjectRequest
ObjectExistsRequest
ObjectExistsResponse
+819
View File
@@ -0,0 +1,819 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: ItemSnapshotter.proto
package generated
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
type ItemSnapshotterAppliesToRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
}
func (m *ItemSnapshotterAppliesToRequest) Reset() { *m = ItemSnapshotterAppliesToRequest{} }
func (m *ItemSnapshotterAppliesToRequest) String() string { return proto.CompactTextString(m) }
func (*ItemSnapshotterAppliesToRequest) ProtoMessage() {}
func (*ItemSnapshotterAppliesToRequest) Descriptor() ([]byte, []int) {
return fileDescriptor2, []int{0}
}
func (m *ItemSnapshotterAppliesToRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
type ItemSnapshotterAppliesToResponse struct {
ResourceSelector *ResourceSelector `protobuf:"bytes,1,opt,name=ResourceSelector" json:"ResourceSelector,omitempty"`
}
func (m *ItemSnapshotterAppliesToResponse) Reset() { *m = ItemSnapshotterAppliesToResponse{} }
func (m *ItemSnapshotterAppliesToResponse) String() string { return proto.CompactTextString(m) }
func (*ItemSnapshotterAppliesToResponse) ProtoMessage() {}
func (*ItemSnapshotterAppliesToResponse) Descriptor() ([]byte, []int) {
return fileDescriptor2, []int{1}
}
func (m *ItemSnapshotterAppliesToResponse) GetResourceSelector() *ResourceSelector {
if m != nil {
return m.ResourceSelector
}
return nil
}
type AlsoHandlesRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"`
}
func (m *AlsoHandlesRequest) Reset() { *m = AlsoHandlesRequest{} }
func (m *AlsoHandlesRequest) String() string { return proto.CompactTextString(m) }
func (*AlsoHandlesRequest) ProtoMessage() {}
func (*AlsoHandlesRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} }
func (m *AlsoHandlesRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
func (m *AlsoHandlesRequest) GetItem() []byte {
if m != nil {
return m.Item
}
return nil
}
func (m *AlsoHandlesRequest) GetBackup() []byte {
if m != nil {
return m.Backup
}
return nil
}
type AlsoHandlesResponse struct {
HandledItems []*ResourceIdentifier `protobuf:"bytes,1,rep,name=handledItems" json:"handledItems,omitempty"`
}
func (m *AlsoHandlesResponse) Reset() { *m = AlsoHandlesResponse{} }
func (m *AlsoHandlesResponse) String() string { return proto.CompactTextString(m) }
func (*AlsoHandlesResponse) ProtoMessage() {}
func (*AlsoHandlesResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} }
func (m *AlsoHandlesResponse) GetHandledItems() []*ResourceIdentifier {
if m != nil {
return m.HandledItems
}
return nil
}
type SnapshotItemRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
Params map[string]string `protobuf:"bytes,3,rep,name=params" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Backup []byte `protobuf:"bytes,4,opt,name=backup,proto3" json:"backup,omitempty"`
}
func (m *SnapshotItemRequest) Reset() { *m = SnapshotItemRequest{} }
func (m *SnapshotItemRequest) String() string { return proto.CompactTextString(m) }
func (*SnapshotItemRequest) ProtoMessage() {}
func (*SnapshotItemRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{4} }
func (m *SnapshotItemRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
func (m *SnapshotItemRequest) GetItem() []byte {
if m != nil {
return m.Item
}
return nil
}
func (m *SnapshotItemRequest) GetParams() map[string]string {
if m != nil {
return m.Params
}
return nil
}
func (m *SnapshotItemRequest) GetBackup() []byte {
if m != nil {
return m.Backup
}
return nil
}
type SnapshotItemResponse struct {
Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"`
SnapshotID string `protobuf:"bytes,2,opt,name=snapshotID" json:"snapshotID,omitempty"`
SnapshotMetadata map[string]string `protobuf:"bytes,3,rep,name=snapshotMetadata" json:"snapshotMetadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
AdditionalItems []*ResourceIdentifier `protobuf:"bytes,4,rep,name=additionalItems" json:"additionalItems,omitempty"`
HandledItems []*ResourceIdentifier `protobuf:"bytes,5,rep,name=handledItems" json:"handledItems,omitempty"`
}
func (m *SnapshotItemResponse) Reset() { *m = SnapshotItemResponse{} }
func (m *SnapshotItemResponse) String() string { return proto.CompactTextString(m) }
func (*SnapshotItemResponse) ProtoMessage() {}
func (*SnapshotItemResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} }
func (m *SnapshotItemResponse) GetItem() []byte {
if m != nil {
return m.Item
}
return nil
}
func (m *SnapshotItemResponse) GetSnapshotID() string {
if m != nil {
return m.SnapshotID
}
return ""
}
func (m *SnapshotItemResponse) GetSnapshotMetadata() map[string]string {
if m != nil {
return m.SnapshotMetadata
}
return nil
}
func (m *SnapshotItemResponse) GetAdditionalItems() []*ResourceIdentifier {
if m != nil {
return m.AdditionalItems
}
return nil
}
func (m *SnapshotItemResponse) GetHandledItems() []*ResourceIdentifier {
if m != nil {
return m.HandledItems
}
return nil
}
type ProgressRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
ItemID *ResourceIdentifier `protobuf:"bytes,2,opt,name=itemID" json:"itemID,omitempty"`
SnapshotID string `protobuf:"bytes,3,opt,name=snapshotID" json:"snapshotID,omitempty"`
Backup []byte `protobuf:"bytes,4,opt,name=backup,proto3" json:"backup,omitempty"`
}
func (m *ProgressRequest) Reset() { *m = ProgressRequest{} }
func (m *ProgressRequest) String() string { return proto.CompactTextString(m) }
func (*ProgressRequest) ProtoMessage() {}
func (*ProgressRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{6} }
func (m *ProgressRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
func (m *ProgressRequest) GetItemID() *ResourceIdentifier {
if m != nil {
return m.ItemID
}
return nil
}
func (m *ProgressRequest) GetSnapshotID() string {
if m != nil {
return m.SnapshotID
}
return ""
}
func (m *ProgressRequest) GetBackup() []byte {
if m != nil {
return m.Backup
}
return nil
}
type ProgressResponse struct {
Phase string `protobuf:"bytes,1,opt,name=phase" json:"phase,omitempty"`
ItemsCompleted int64 `protobuf:"varint,2,opt,name=itemsCompleted" json:"itemsCompleted,omitempty"`
ItemsToComplete int64 `protobuf:"varint,3,opt,name=itemsToComplete" json:"itemsToComplete,omitempty"`
Started int64 `protobuf:"varint,4,opt,name=started" json:"started,omitempty"`
StartedNano int64 `protobuf:"varint,5,opt,name=startedNano" json:"startedNano,omitempty"`
Updated int64 `protobuf:"varint,6,opt,name=updated" json:"updated,omitempty"`
UpdatedNano int64 `protobuf:"varint,7,opt,name=updatedNano" json:"updatedNano,omitempty"`
Err string `protobuf:"bytes,8,opt,name=err" json:"err,omitempty"`
}
func (m *ProgressResponse) Reset() { *m = ProgressResponse{} }
func (m *ProgressResponse) String() string { return proto.CompactTextString(m) }
func (*ProgressResponse) ProtoMessage() {}
func (*ProgressResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} }
func (m *ProgressResponse) GetPhase() string {
if m != nil {
return m.Phase
}
return ""
}
func (m *ProgressResponse) GetItemsCompleted() int64 {
if m != nil {
return m.ItemsCompleted
}
return 0
}
func (m *ProgressResponse) GetItemsToComplete() int64 {
if m != nil {
return m.ItemsToComplete
}
return 0
}
func (m *ProgressResponse) GetStarted() int64 {
if m != nil {
return m.Started
}
return 0
}
func (m *ProgressResponse) GetStartedNano() int64 {
if m != nil {
return m.StartedNano
}
return 0
}
func (m *ProgressResponse) GetUpdated() int64 {
if m != nil {
return m.Updated
}
return 0
}
func (m *ProgressResponse) GetUpdatedNano() int64 {
if m != nil {
return m.UpdatedNano
}
return 0
}
func (m *ProgressResponse) GetErr() string {
if m != nil {
return m.Err
}
return ""
}
type DeleteItemSnapshotRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
SnapshotID string `protobuf:"bytes,2,opt,name=snapshotID" json:"snapshotID,omitempty"`
ItemFromBackup []byte `protobuf:"bytes,3,opt,name=itemFromBackup,proto3" json:"itemFromBackup,omitempty"`
Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Params map[string]string `protobuf:"bytes,5,rep,name=params" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
}
func (m *DeleteItemSnapshotRequest) Reset() { *m = DeleteItemSnapshotRequest{} }
func (m *DeleteItemSnapshotRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteItemSnapshotRequest) ProtoMessage() {}
func (*DeleteItemSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} }
func (m *DeleteItemSnapshotRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
func (m *DeleteItemSnapshotRequest) GetSnapshotID() string {
if m != nil {
return m.SnapshotID
}
return ""
}
func (m *DeleteItemSnapshotRequest) GetItemFromBackup() []byte {
if m != nil {
return m.ItemFromBackup
}
return nil
}
func (m *DeleteItemSnapshotRequest) GetMetadata() map[string]string {
if m != nil {
return m.Metadata
}
return nil
}
func (m *DeleteItemSnapshotRequest) GetParams() map[string]string {
if m != nil {
return m.Params
}
return nil
}
type CreateItemFromSnapshotRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
SnapshotID string `protobuf:"bytes,3,opt,name=snapshotID" json:"snapshotID,omitempty"`
ItemFromBackup []byte `protobuf:"bytes,4,opt,name=itemFromBackup,proto3" json:"itemFromBackup,omitempty"`
SnapshotMetadata map[string]string `protobuf:"bytes,5,rep,name=snapshotMetadata" json:"snapshotMetadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Params map[string]string `protobuf:"bytes,6,rep,name=params" json:"params,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Restore []byte `protobuf:"bytes,7,opt,name=restore,proto3" json:"restore,omitempty"`
}
func (m *CreateItemFromSnapshotRequest) Reset() { *m = CreateItemFromSnapshotRequest{} }
func (m *CreateItemFromSnapshotRequest) String() string { return proto.CompactTextString(m) }
func (*CreateItemFromSnapshotRequest) ProtoMessage() {}
func (*CreateItemFromSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} }
func (m *CreateItemFromSnapshotRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
func (m *CreateItemFromSnapshotRequest) GetItem() []byte {
if m != nil {
return m.Item
}
return nil
}
func (m *CreateItemFromSnapshotRequest) GetSnapshotID() string {
if m != nil {
return m.SnapshotID
}
return ""
}
func (m *CreateItemFromSnapshotRequest) GetItemFromBackup() []byte {
if m != nil {
return m.ItemFromBackup
}
return nil
}
func (m *CreateItemFromSnapshotRequest) GetSnapshotMetadata() map[string]string {
if m != nil {
return m.SnapshotMetadata
}
return nil
}
func (m *CreateItemFromSnapshotRequest) GetParams() map[string]string {
if m != nil {
return m.Params
}
return nil
}
func (m *CreateItemFromSnapshotRequest) GetRestore() []byte {
if m != nil {
return m.Restore
}
return nil
}
type CreateItemFromSnapshotResponse struct {
Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"`
AdditionalItems []*ResourceIdentifier `protobuf:"bytes,2,rep,name=additionalItems" json:"additionalItems,omitempty"`
SkipRestore bool `protobuf:"varint,3,opt,name=skipRestore" json:"skipRestore,omitempty"`
}
func (m *CreateItemFromSnapshotResponse) Reset() { *m = CreateItemFromSnapshotResponse{} }
func (m *CreateItemFromSnapshotResponse) String() string { return proto.CompactTextString(m) }
func (*CreateItemFromSnapshotResponse) ProtoMessage() {}
func (*CreateItemFromSnapshotResponse) Descriptor() ([]byte, []int) {
return fileDescriptor2, []int{10}
}
func (m *CreateItemFromSnapshotResponse) GetItem() []byte {
if m != nil {
return m.Item
}
return nil
}
func (m *CreateItemFromSnapshotResponse) GetAdditionalItems() []*ResourceIdentifier {
if m != nil {
return m.AdditionalItems
}
return nil
}
func (m *CreateItemFromSnapshotResponse) GetSkipRestore() bool {
if m != nil {
return m.SkipRestore
}
return false
}
type ItemSnapshotterInitRequest struct {
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
Config map[string]string `protobuf:"bytes,2,rep,name=config" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
}
func (m *ItemSnapshotterInitRequest) Reset() { *m = ItemSnapshotterInitRequest{} }
func (m *ItemSnapshotterInitRequest) String() string { return proto.CompactTextString(m) }
func (*ItemSnapshotterInitRequest) ProtoMessage() {}
func (*ItemSnapshotterInitRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{11} }
func (m *ItemSnapshotterInitRequest) GetPlugin() string {
if m != nil {
return m.Plugin
}
return ""
}
func (m *ItemSnapshotterInitRequest) GetConfig() map[string]string {
if m != nil {
return m.Config
}
return nil
}
func init() {
proto.RegisterType((*ItemSnapshotterAppliesToRequest)(nil), "generated.ItemSnapshotterAppliesToRequest")
proto.RegisterType((*ItemSnapshotterAppliesToResponse)(nil), "generated.ItemSnapshotterAppliesToResponse")
proto.RegisterType((*AlsoHandlesRequest)(nil), "generated.AlsoHandlesRequest")
proto.RegisterType((*AlsoHandlesResponse)(nil), "generated.AlsoHandlesResponse")
proto.RegisterType((*SnapshotItemRequest)(nil), "generated.SnapshotItemRequest")
proto.RegisterType((*SnapshotItemResponse)(nil), "generated.SnapshotItemResponse")
proto.RegisterType((*ProgressRequest)(nil), "generated.ProgressRequest")
proto.RegisterType((*ProgressResponse)(nil), "generated.ProgressResponse")
proto.RegisterType((*DeleteItemSnapshotRequest)(nil), "generated.DeleteItemSnapshotRequest")
proto.RegisterType((*CreateItemFromSnapshotRequest)(nil), "generated.CreateItemFromSnapshotRequest")
proto.RegisterType((*CreateItemFromSnapshotResponse)(nil), "generated.CreateItemFromSnapshotResponse")
proto.RegisterType((*ItemSnapshotterInitRequest)(nil), "generated.ItemSnapshotterInitRequest")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for ItemSnapshotter service
type ItemSnapshotterClient interface {
Init(ctx context.Context, in *ItemSnapshotterInitRequest, opts ...grpc.CallOption) (*Empty, error)
AppliesTo(ctx context.Context, in *ItemSnapshotterAppliesToRequest, opts ...grpc.CallOption) (*ItemSnapshotterAppliesToResponse, error)
AlsoHandles(ctx context.Context, in *AlsoHandlesRequest, opts ...grpc.CallOption) (*AlsoHandlesResponse, error)
SnapshotItem(ctx context.Context, in *SnapshotItemRequest, opts ...grpc.CallOption) (*SnapshotItemResponse, error)
Progress(ctx context.Context, in *ProgressRequest, opts ...grpc.CallOption) (*ProgressResponse, error)
DeleteSnapshot(ctx context.Context, in *DeleteItemSnapshotRequest, opts ...grpc.CallOption) (*Empty, error)
CreateItemFromSnapshot(ctx context.Context, in *CreateItemFromSnapshotRequest, opts ...grpc.CallOption) (*CreateItemFromSnapshotResponse, error)
}
type itemSnapshotterClient struct {
cc *grpc.ClientConn
}
func NewItemSnapshotterClient(cc *grpc.ClientConn) ItemSnapshotterClient {
return &itemSnapshotterClient{cc}
}
func (c *itemSnapshotterClient) Init(ctx context.Context, in *ItemSnapshotterInitRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/Init", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *itemSnapshotterClient) AppliesTo(ctx context.Context, in *ItemSnapshotterAppliesToRequest, opts ...grpc.CallOption) (*ItemSnapshotterAppliesToResponse, error) {
out := new(ItemSnapshotterAppliesToResponse)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/AppliesTo", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *itemSnapshotterClient) AlsoHandles(ctx context.Context, in *AlsoHandlesRequest, opts ...grpc.CallOption) (*AlsoHandlesResponse, error) {
out := new(AlsoHandlesResponse)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/AlsoHandles", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *itemSnapshotterClient) SnapshotItem(ctx context.Context, in *SnapshotItemRequest, opts ...grpc.CallOption) (*SnapshotItemResponse, error) {
out := new(SnapshotItemResponse)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/SnapshotItem", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *itemSnapshotterClient) Progress(ctx context.Context, in *ProgressRequest, opts ...grpc.CallOption) (*ProgressResponse, error) {
out := new(ProgressResponse)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/Progress", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *itemSnapshotterClient) DeleteSnapshot(ctx context.Context, in *DeleteItemSnapshotRequest, opts ...grpc.CallOption) (*Empty, error) {
out := new(Empty)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/DeleteSnapshot", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *itemSnapshotterClient) CreateItemFromSnapshot(ctx context.Context, in *CreateItemFromSnapshotRequest, opts ...grpc.CallOption) (*CreateItemFromSnapshotResponse, error) {
out := new(CreateItemFromSnapshotResponse)
err := grpc.Invoke(ctx, "/generated.ItemSnapshotter/CreateItemFromSnapshot", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for ItemSnapshotter service
type ItemSnapshotterServer interface {
Init(context.Context, *ItemSnapshotterInitRequest) (*Empty, error)
AppliesTo(context.Context, *ItemSnapshotterAppliesToRequest) (*ItemSnapshotterAppliesToResponse, error)
AlsoHandles(context.Context, *AlsoHandlesRequest) (*AlsoHandlesResponse, error)
SnapshotItem(context.Context, *SnapshotItemRequest) (*SnapshotItemResponse, error)
Progress(context.Context, *ProgressRequest) (*ProgressResponse, error)
DeleteSnapshot(context.Context, *DeleteItemSnapshotRequest) (*Empty, error)
CreateItemFromSnapshot(context.Context, *CreateItemFromSnapshotRequest) (*CreateItemFromSnapshotResponse, error)
}
func RegisterItemSnapshotterServer(s *grpc.Server, srv ItemSnapshotterServer) {
s.RegisterService(&_ItemSnapshotter_serviceDesc, srv)
}
func _ItemSnapshotter_Init_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ItemSnapshotterInitRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).Init(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/Init",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).Init(ctx, req.(*ItemSnapshotterInitRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ItemSnapshotter_AppliesTo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ItemSnapshotterAppliesToRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).AppliesTo(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/AppliesTo",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).AppliesTo(ctx, req.(*ItemSnapshotterAppliesToRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ItemSnapshotter_AlsoHandles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AlsoHandlesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).AlsoHandles(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/AlsoHandles",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).AlsoHandles(ctx, req.(*AlsoHandlesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ItemSnapshotter_SnapshotItem_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SnapshotItemRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).SnapshotItem(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/SnapshotItem",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).SnapshotItem(ctx, req.(*SnapshotItemRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ItemSnapshotter_Progress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ProgressRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).Progress(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/Progress",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).Progress(ctx, req.(*ProgressRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ItemSnapshotter_DeleteSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteItemSnapshotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).DeleteSnapshot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/DeleteSnapshot",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).DeleteSnapshot(ctx, req.(*DeleteItemSnapshotRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ItemSnapshotter_CreateItemFromSnapshot_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateItemFromSnapshotRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ItemSnapshotterServer).CreateItemFromSnapshot(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/generated.ItemSnapshotter/CreateItemFromSnapshot",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ItemSnapshotterServer).CreateItemFromSnapshot(ctx, req.(*CreateItemFromSnapshotRequest))
}
return interceptor(ctx, in, info, handler)
}
var _ItemSnapshotter_serviceDesc = grpc.ServiceDesc{
ServiceName: "generated.ItemSnapshotter",
HandlerType: (*ItemSnapshotterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Init",
Handler: _ItemSnapshotter_Init_Handler,
},
{
MethodName: "AppliesTo",
Handler: _ItemSnapshotter_AppliesTo_Handler,
},
{
MethodName: "AlsoHandles",
Handler: _ItemSnapshotter_AlsoHandles_Handler,
},
{
MethodName: "SnapshotItem",
Handler: _ItemSnapshotter_SnapshotItem_Handler,
},
{
MethodName: "Progress",
Handler: _ItemSnapshotter_Progress_Handler,
},
{
MethodName: "DeleteSnapshot",
Handler: _ItemSnapshotter_DeleteSnapshot_Handler,
},
{
MethodName: "CreateItemFromSnapshot",
Handler: _ItemSnapshotter_CreateItemFromSnapshot_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "ItemSnapshotter.proto",
}
func init() { proto.RegisterFile("ItemSnapshotter.proto", fileDescriptor2) }
var fileDescriptor2 = []byte{
// 887 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x5f, 0x8f, 0xdb, 0x44,
0x10, 0x97, 0xe3, 0x5c, 0xee, 0x6e, 0x12, 0x7a, 0xd1, 0xf6, 0x5a, 0x19, 0x57, 0xbd, 0x46, 0x16,
0xa0, 0x50, 0xa4, 0x08, 0x0e, 0x2a, 0x51, 0x90, 0x40, 0xd7, 0xb4, 0xf4, 0x4e, 0x2a, 0xa5, 0xf2,
0xf5, 0xa1, 0xaf, 0xdb, 0x78, 0x9b, 0x98, 0xd8, 0x5e, 0xb3, 0xbb, 0x41, 0xba, 0x77, 0x5e, 0x79,
0xe7, 0x8d, 0xef, 0xc1, 0xf7, 0x40, 0xe2, 0x1b, 0xf0, 0x2d, 0x10, 0xda, 0x3f, 0x0e, 0x1b, 0xc7,
0x3e, 0xfb, 0xee, 0x78, 0xf3, 0xce, 0xce, 0xcc, 0xfe, 0x66, 0x7e, 0xb3, 0xb3, 0x63, 0xb8, 0x73,
0x26, 0x48, 0x7a, 0x9e, 0xe1, 0x9c, 0x2f, 0xa8, 0x10, 0x84, 0x4d, 0x72, 0x46, 0x05, 0x45, 0xfb,
0x73, 0x92, 0x11, 0x86, 0x05, 0x89, 0xfc, 0xc1, 0xf9, 0x02, 0x33, 0x12, 0xe9, 0x8d, 0xe0, 0x31,
0x3c, 0x28, 0x59, 0x9c, 0xe4, 0x79, 0x12, 0x13, 0xfe, 0x9a, 0x86, 0xe4, 0xa7, 0x15, 0xe1, 0x02,
0xdd, 0x85, 0x5e, 0x9e, 0xac, 0xe6, 0x71, 0xe6, 0x39, 0x23, 0x67, 0xbc, 0x1f, 0x9a, 0x55, 0xb0,
0x84, 0x51, 0xbd, 0x29, 0xcf, 0x69, 0xc6, 0x09, 0x7a, 0x0e, 0xc3, 0x90, 0x70, 0xba, 0x62, 0x33,
0x72, 0x4e, 0x12, 0x32, 0x13, 0x94, 0x29, 0x2f, 0xfd, 0xe3, 0x7b, 0x93, 0x35, 0xa4, 0x49, 0x59,
0x25, 0xdc, 0x32, 0x0a, 0xde, 0x00, 0x3a, 0x49, 0x38, 0x3d, 0xc5, 0x59, 0x94, 0x10, 0xde, 0x00,
0x0d, 0x21, 0xe8, 0xc6, 0x82, 0xa4, 0x5e, 0x67, 0xe4, 0x8c, 0x07, 0xa1, 0xfa, 0x96, 0xba, 0x6f,
0xf1, 0x6c, 0xb9, 0xca, 0x3d, 0x57, 0x49, 0xcd, 0x2a, 0x78, 0x03, 0xb7, 0x37, 0x3c, 0x1b, 0xe4,
0x27, 0x30, 0x58, 0x28, 0x51, 0x24, 0x83, 0xe4, 0x9e, 0x33, 0x72, 0xc7, 0xfd, 0xe3, 0xfb, 0x15,
0xa8, 0xcf, 0x22, 0x92, 0x89, 0xf8, 0x5d, 0x4c, 0x58, 0xb8, 0x61, 0x12, 0xfc, 0xe5, 0xc0, 0xed,
0x22, 0x3b, 0x52, 0x72, 0x1d, 0xd4, 0x4f, 0xa0, 0x97, 0x63, 0x86, 0x53, 0xee, 0xb9, 0x0a, 0xc0,
0x43, 0x0b, 0x40, 0x85, 0xef, 0xc9, 0x2b, 0xa5, 0xfc, 0x2c, 0x13, 0xec, 0x22, 0x34, 0x96, 0x56,
0xe4, 0x5d, 0x3b, 0x72, 0xff, 0x31, 0xf4, 0x2d, 0x75, 0x34, 0x04, 0x77, 0x49, 0x2e, 0x0c, 0x26,
0xf9, 0x89, 0x0e, 0x61, 0xe7, 0x67, 0x9c, 0xac, 0x88, 0x42, 0xb4, 0x1f, 0xea, 0xc5, 0x57, 0x9d,
0x2f, 0x9d, 0xe0, 0x9f, 0x0e, 0x1c, 0x6e, 0x1e, 0x6f, 0xd2, 0x56, 0xc4, 0xe0, 0x58, 0x31, 0x1c,
0x01, 0xf0, 0x42, 0xf7, 0xa9, 0xf1, 0x65, 0x49, 0x10, 0x86, 0x61, 0xb1, 0xfa, 0x9e, 0x08, 0x1c,
0x61, 0x81, 0x4d, 0xb4, 0x8f, 0x6a, 0xa3, 0xd5, 0xc7, 0xad, 0x85, 0x85, 0x9d, 0x0e, 0x7c, 0xcb,
0x1d, 0x7a, 0x0e, 0x07, 0x38, 0x8a, 0x62, 0x11, 0xd3, 0x0c, 0x27, 0x9a, 0xd0, 0x6e, 0x1b, 0x42,
0xcb, 0x56, 0x5b, 0x65, 0xb1, 0x73, 0xe5, 0xb2, 0xf0, 0xa7, 0x70, 0xa7, 0x12, 0xf6, 0x95, 0x08,
0xf8, 0xcd, 0x81, 0x83, 0x57, 0x8c, 0xce, 0x19, 0xe1, 0x8d, 0xb7, 0xe1, 0x11, 0xf4, 0x24, 0x0f,
0x26, 0xf7, 0x8d, 0x68, 0x8d, 0x72, 0x89, 0x36, 0x77, 0x8b, 0xb6, 0x9a, 0xb2, 0x0a, 0x7e, 0xe9,
0xc0, 0xf0, 0x3f, 0x68, 0xa6, 0x2e, 0x0e, 0x61, 0x27, 0x5f, 0x60, 0x4e, 0x0c, 0x34, 0xbd, 0x40,
0x1f, 0xc1, 0x2d, 0x79, 0x18, 0x9f, 0xd2, 0x34, 0x4f, 0x88, 0x20, 0x91, 0x42, 0xe8, 0x86, 0x25,
0x29, 0x1a, 0xc3, 0x81, 0x92, 0xbc, 0xa6, 0x85, 0x4c, 0xe1, 0x71, 0xc3, 0xb2, 0x18, 0x79, 0xb0,
0xcb, 0x05, 0x66, 0xd2, 0x55, 0x57, 0x69, 0x14, 0x4b, 0x34, 0x82, 0xbe, 0xf9, 0x7c, 0x89, 0x33,
0xea, 0xed, 0xa8, 0x5d, 0x5b, 0x24, 0x6d, 0x57, 0x79, 0x24, 0xd3, 0xe2, 0xf5, 0xb4, 0xad, 0x59,
0x4a, 0x5b, 0xf3, 0xa9, 0x6c, 0x77, 0xb5, 0xad, 0x25, 0x92, 0xdc, 0x11, 0xc6, 0xbc, 0x3d, 0xcd,
0x1d, 0x61, 0x2c, 0xf8, 0xd5, 0x85, 0xf7, 0x9f, 0x12, 0x09, 0xca, 0xee, 0x92, 0x4d, 0x5c, 0x35,
0xdd, 0x15, 0x93, 0xb1, 0xef, 0x18, 0x4d, 0x9f, 0xd8, 0xdd, 0xac, 0x24, 0x45, 0x2f, 0x61, 0x2f,
0x2d, 0xee, 0x92, 0xae, 0xf4, 0x63, 0x8b, 0xf5, 0x5a, 0x5c, 0x93, 0xcd, 0x8b, 0xb4, 0xf6, 0x81,
0x4e, 0xd7, 0x7d, 0x48, 0x57, 0xfc, 0xa7, 0xad, 0xbc, 0x55, 0x74, 0x23, 0xff, 0x6b, 0x78, 0xef,
0xda, 0x65, 0x7f, 0x93, 0x96, 0xf5, 0xb7, 0x0b, 0xf7, 0xa7, 0x8c, 0x60, 0x8d, 0x54, 0xa6, 0xaa,
0x2d, 0x27, 0x55, 0x7d, 0xb9, 0xe9, 0x72, 0x6c, 0xf3, 0xd4, 0xad, 0xe4, 0xe9, 0xc7, 0x8a, 0xde,
0xa7, 0x33, 0xfc, 0x8d, 0x95, 0xe1, 0x4b, 0x71, 0xb7, 0x6e, 0x82, 0x2f, 0xd6, 0x1c, 0xf6, 0xd4,
0x09, 0x5f, 0xb4, 0x3e, 0xa1, 0xea, 0x55, 0xf1, 0x60, 0x97, 0x11, 0x2e, 0x28, 0x23, 0xea, 0x3e,
0x0c, 0xc2, 0x62, 0xf9, 0xbf, 0x34, 0xb8, 0x9b, 0x30, 0xfd, 0xbb, 0x03, 0x47, 0x75, 0xf1, 0x5c,
0xf2, 0x4c, 0x55, 0xbc, 0x11, 0x9d, 0x6b, 0xbd, 0x11, 0xb2, 0xd3, 0x2c, 0xe3, 0x3c, 0x34, 0xd9,
0x91, 0xc5, 0xb1, 0x17, 0xda, 0xa2, 0xe0, 0x0f, 0x07, 0xfc, 0xd2, 0xec, 0x74, 0x96, 0xc5, 0x8d,
0x85, 0x78, 0x06, 0xbd, 0x19, 0xcd, 0xde, 0xc5, 0x73, 0x03, 0xec, 0x33, 0x0b, 0x58, 0xbd, 0xbb,
0xc9, 0x54, 0xd9, 0x18, 0xf6, 0xb4, 0x03, 0x99, 0x5e, 0x4b, 0x7c, 0x95, 0xf4, 0x1e, 0xff, 0xd9,
0x85, 0x83, 0xd2, 0x69, 0xe8, 0x5b, 0xe8, 0xca, 0x13, 0xd1, 0x87, 0xad, 0x10, 0xf9, 0x43, 0x4b,
0xed, 0x59, 0x9a, 0x8b, 0x0b, 0x14, 0xc1, 0xfe, 0x7a, 0x7a, 0x44, 0x0f, 0xeb, 0xbd, 0x94, 0xa7,
0x53, 0xff, 0x93, 0x56, 0xba, 0x86, 0xf6, 0x17, 0xd0, 0xb7, 0x66, 0x3d, 0x64, 0x13, 0xbb, 0x3d,
0x5d, 0xfa, 0x47, 0x75, 0xdb, 0xc6, 0xdb, 0x0f, 0x30, 0xb0, 0x87, 0x12, 0x74, 0x74, 0xf9, 0x6c,
0xe6, 0x3f, 0x68, 0x98, 0x66, 0xd0, 0x14, 0xf6, 0x8a, 0x87, 0x13, 0xf9, 0x96, 0x72, 0xe9, 0xa1,
0xf7, 0xef, 0x55, 0xee, 0x19, 0x27, 0xa7, 0x70, 0x4b, 0x37, 0xe4, 0xe2, 0x08, 0xf4, 0x41, 0x9b,
0x5e, 0x5d, 0xc1, 0x49, 0x0a, 0x77, 0xab, 0xaf, 0x11, 0x1a, 0xb7, 0xed, 0x1c, 0xfe, 0xc7, 0x2d,
0x34, 0x35, 0xf0, 0xb7, 0x3d, 0xf5, 0x47, 0xf2, 0xf9, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x51,
0xd9, 0x74, 0xbb, 0xc3, 0x0c, 0x00, 0x00,
}
+15 -15
View File
@@ -27,7 +27,7 @@ type PutObjectRequest struct {
func (m *PutObjectRequest) Reset() { *m = PutObjectRequest{} }
func (m *PutObjectRequest) String() string { return proto.CompactTextString(m) }
func (*PutObjectRequest) ProtoMessage() {}
func (*PutObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
func (*PutObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
func (m *PutObjectRequest) GetPlugin() string {
if m != nil {
@@ -66,7 +66,7 @@ type ObjectExistsRequest struct {
func (m *ObjectExistsRequest) Reset() { *m = ObjectExistsRequest{} }
func (m *ObjectExistsRequest) String() string { return proto.CompactTextString(m) }
func (*ObjectExistsRequest) ProtoMessage() {}
func (*ObjectExistsRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} }
func (*ObjectExistsRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} }
func (m *ObjectExistsRequest) GetPlugin() string {
if m != nil {
@@ -96,7 +96,7 @@ type ObjectExistsResponse struct {
func (m *ObjectExistsResponse) Reset() { *m = ObjectExistsResponse{} }
func (m *ObjectExistsResponse) String() string { return proto.CompactTextString(m) }
func (*ObjectExistsResponse) ProtoMessage() {}
func (*ObjectExistsResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{2} }
func (*ObjectExistsResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{2} }
func (m *ObjectExistsResponse) GetExists() bool {
if m != nil {
@@ -114,7 +114,7 @@ type GetObjectRequest struct {
func (m *GetObjectRequest) Reset() { *m = GetObjectRequest{} }
func (m *GetObjectRequest) String() string { return proto.CompactTextString(m) }
func (*GetObjectRequest) ProtoMessage() {}
func (*GetObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} }
func (*GetObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{3} }
func (m *GetObjectRequest) GetPlugin() string {
if m != nil {
@@ -144,7 +144,7 @@ type Bytes struct {
func (m *Bytes) Reset() { *m = Bytes{} }
func (m *Bytes) String() string { return proto.CompactTextString(m) }
func (*Bytes) ProtoMessage() {}
func (*Bytes) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{4} }
func (*Bytes) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{4} }
func (m *Bytes) GetData() []byte {
if m != nil {
@@ -163,7 +163,7 @@ type ListCommonPrefixesRequest struct {
func (m *ListCommonPrefixesRequest) Reset() { *m = ListCommonPrefixesRequest{} }
func (m *ListCommonPrefixesRequest) String() string { return proto.CompactTextString(m) }
func (*ListCommonPrefixesRequest) ProtoMessage() {}
func (*ListCommonPrefixesRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} }
func (*ListCommonPrefixesRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{5} }
func (m *ListCommonPrefixesRequest) GetPlugin() string {
if m != nil {
@@ -200,7 +200,7 @@ type ListCommonPrefixesResponse struct {
func (m *ListCommonPrefixesResponse) Reset() { *m = ListCommonPrefixesResponse{} }
func (m *ListCommonPrefixesResponse) String() string { return proto.CompactTextString(m) }
func (*ListCommonPrefixesResponse) ProtoMessage() {}
func (*ListCommonPrefixesResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{6} }
func (*ListCommonPrefixesResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{6} }
func (m *ListCommonPrefixesResponse) GetPrefixes() []string {
if m != nil {
@@ -218,7 +218,7 @@ type ListObjectsRequest struct {
func (m *ListObjectsRequest) Reset() { *m = ListObjectsRequest{} }
func (m *ListObjectsRequest) String() string { return proto.CompactTextString(m) }
func (*ListObjectsRequest) ProtoMessage() {}
func (*ListObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} }
func (*ListObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{7} }
func (m *ListObjectsRequest) GetPlugin() string {
if m != nil {
@@ -248,7 +248,7 @@ type ListObjectsResponse struct {
func (m *ListObjectsResponse) Reset() { *m = ListObjectsResponse{} }
func (m *ListObjectsResponse) String() string { return proto.CompactTextString(m) }
func (*ListObjectsResponse) ProtoMessage() {}
func (*ListObjectsResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} }
func (*ListObjectsResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{8} }
func (m *ListObjectsResponse) GetKeys() []string {
if m != nil {
@@ -266,7 +266,7 @@ type DeleteObjectRequest struct {
func (m *DeleteObjectRequest) Reset() { *m = DeleteObjectRequest{} }
func (m *DeleteObjectRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteObjectRequest) ProtoMessage() {}
func (*DeleteObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{9} }
func (*DeleteObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{9} }
func (m *DeleteObjectRequest) GetPlugin() string {
if m != nil {
@@ -299,7 +299,7 @@ type CreateSignedURLRequest struct {
func (m *CreateSignedURLRequest) Reset() { *m = CreateSignedURLRequest{} }
func (m *CreateSignedURLRequest) String() string { return proto.CompactTextString(m) }
func (*CreateSignedURLRequest) ProtoMessage() {}
func (*CreateSignedURLRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{10} }
func (*CreateSignedURLRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{10} }
func (m *CreateSignedURLRequest) GetPlugin() string {
if m != nil {
@@ -336,7 +336,7 @@ type CreateSignedURLResponse struct {
func (m *CreateSignedURLResponse) Reset() { *m = CreateSignedURLResponse{} }
func (m *CreateSignedURLResponse) String() string { return proto.CompactTextString(m) }
func (*CreateSignedURLResponse) ProtoMessage() {}
func (*CreateSignedURLResponse) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{11} }
func (*CreateSignedURLResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{11} }
func (m *CreateSignedURLResponse) GetUrl() string {
if m != nil {
@@ -353,7 +353,7 @@ type ObjectStoreInitRequest struct {
func (m *ObjectStoreInitRequest) Reset() { *m = ObjectStoreInitRequest{} }
func (m *ObjectStoreInitRequest) String() string { return proto.CompactTextString(m) }
func (*ObjectStoreInitRequest) ProtoMessage() {}
func (*ObjectStoreInitRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{12} }
func (*ObjectStoreInitRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{12} }
func (m *ObjectStoreInitRequest) GetPlugin() string {
if m != nil {
@@ -750,9 +750,9 @@ var _ObjectStore_serviceDesc = grpc.ServiceDesc{
Metadata: "ObjectStore.proto",
}
func init() { proto.RegisterFile("ObjectStore.proto", fileDescriptor2) }
func init() { proto.RegisterFile("ObjectStore.proto", fileDescriptor3) }
var fileDescriptor2 = []byte{
var fileDescriptor3 = []byte{
// 577 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0xcd, 0x6e, 0xd3, 0x40,
0x10, 0xd6, 0xc6, 0x69, 0x54, 0x4f, 0x22, 0x61, 0xb6, 0x55, 0x30, 0x2e, 0x94, 0xb0, 0x02, 0x29,
+4 -4
View File
@@ -26,7 +26,7 @@ type PluginIdentifier struct {
func (m *PluginIdentifier) Reset() { *m = PluginIdentifier{} }
func (m *PluginIdentifier) String() string { return proto.CompactTextString(m) }
func (*PluginIdentifier) ProtoMessage() {}
func (*PluginIdentifier) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
func (*PluginIdentifier) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
func (m *PluginIdentifier) GetCommand() string {
if m != nil {
@@ -56,7 +56,7 @@ type ListPluginsResponse struct {
func (m *ListPluginsResponse) Reset() { *m = ListPluginsResponse{} }
func (m *ListPluginsResponse) String() string { return proto.CompactTextString(m) }
func (*ListPluginsResponse) ProtoMessage() {}
func (*ListPluginsResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} }
func (*ListPluginsResponse) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{1} }
func (m *ListPluginsResponse) GetPlugins() []*PluginIdentifier {
if m != nil {
@@ -142,9 +142,9 @@ var _PluginLister_serviceDesc = grpc.ServiceDesc{
Metadata: "PluginLister.proto",
}
func init() { proto.RegisterFile("PluginLister.proto", fileDescriptor3) }
func init() { proto.RegisterFile("PluginLister.proto", fileDescriptor4) }
var fileDescriptor3 = []byte{
var fileDescriptor4 = []byte{
// 201 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x0a, 0xc8, 0x29, 0x4d,
0xcf, 0xcc, 0xf3, 0xc9, 0x2c, 0x2e, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2,
+6 -6
View File
@@ -28,7 +28,7 @@ func (m *RestoreItemActionExecuteRequest) Reset() { *m = RestoreItemActi
func (m *RestoreItemActionExecuteRequest) String() string { return proto.CompactTextString(m) }
func (*RestoreItemActionExecuteRequest) ProtoMessage() {}
func (*RestoreItemActionExecuteRequest) Descriptor() ([]byte, []int) {
return fileDescriptor4, []int{0}
return fileDescriptor5, []int{0}
}
func (m *RestoreItemActionExecuteRequest) GetPlugin() string {
@@ -69,7 +69,7 @@ func (m *RestoreItemActionExecuteResponse) Reset() { *m = RestoreItemAct
func (m *RestoreItemActionExecuteResponse) String() string { return proto.CompactTextString(m) }
func (*RestoreItemActionExecuteResponse) ProtoMessage() {}
func (*RestoreItemActionExecuteResponse) Descriptor() ([]byte, []int) {
return fileDescriptor4, []int{1}
return fileDescriptor5, []int{1}
}
func (m *RestoreItemActionExecuteResponse) GetItem() []byte {
@@ -101,7 +101,7 @@ func (m *RestoreItemActionAppliesToRequest) Reset() { *m = RestoreItemAc
func (m *RestoreItemActionAppliesToRequest) String() string { return proto.CompactTextString(m) }
func (*RestoreItemActionAppliesToRequest) ProtoMessage() {}
func (*RestoreItemActionAppliesToRequest) Descriptor() ([]byte, []int) {
return fileDescriptor4, []int{2}
return fileDescriptor5, []int{2}
}
func (m *RestoreItemActionAppliesToRequest) GetPlugin() string {
@@ -119,7 +119,7 @@ func (m *RestoreItemActionAppliesToResponse) Reset() { *m = RestoreItemA
func (m *RestoreItemActionAppliesToResponse) String() string { return proto.CompactTextString(m) }
func (*RestoreItemActionAppliesToResponse) ProtoMessage() {}
func (*RestoreItemActionAppliesToResponse) Descriptor() ([]byte, []int) {
return fileDescriptor4, []int{3}
return fileDescriptor5, []int{3}
}
func (m *RestoreItemActionAppliesToResponse) GetResourceSelector() *ResourceSelector {
@@ -241,9 +241,9 @@ var _RestoreItemAction_serviceDesc = grpc.ServiceDesc{
Metadata: "RestoreItemAction.proto",
}
func init() { proto.RegisterFile("RestoreItemAction.proto", fileDescriptor4) }
func init() { proto.RegisterFile("RestoreItemAction.proto", fileDescriptor5) }
var fileDescriptor4 = []byte{
var fileDescriptor5 = []byte{
// 332 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xdd, 0x4e, 0xc2, 0x30,
0x14, 0x4e, 0x81, 0x80, 0x1c, 0x88, 0x3f, 0xbd, 0xd0, 0x06, 0x63, 0x9c, 0xbb, 0x30, 0xc4, 0x1f,
+7 -7
View File
@@ -18,7 +18,7 @@ type Empty struct {
func (m *Empty) Reset() { *m = Empty{} }
func (m *Empty) String() string { return proto.CompactTextString(m) }
func (*Empty) ProtoMessage() {}
func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} }
func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{0} }
type Stack struct {
Frames []*StackFrame `protobuf:"bytes,1,rep,name=frames" json:"frames,omitempty"`
@@ -27,7 +27,7 @@ type Stack struct {
func (m *Stack) Reset() { *m = Stack{} }
func (m *Stack) String() string { return proto.CompactTextString(m) }
func (*Stack) ProtoMessage() {}
func (*Stack) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{1} }
func (*Stack) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{1} }
func (m *Stack) GetFrames() []*StackFrame {
if m != nil {
@@ -45,7 +45,7 @@ type StackFrame struct {
func (m *StackFrame) Reset() { *m = StackFrame{} }
func (m *StackFrame) String() string { return proto.CompactTextString(m) }
func (*StackFrame) ProtoMessage() {}
func (*StackFrame) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{2} }
func (*StackFrame) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{2} }
func (m *StackFrame) GetFile() string {
if m != nil {
@@ -78,7 +78,7 @@ type ResourceIdentifier struct {
func (m *ResourceIdentifier) Reset() { *m = ResourceIdentifier{} }
func (m *ResourceIdentifier) String() string { return proto.CompactTextString(m) }
func (*ResourceIdentifier) ProtoMessage() {}
func (*ResourceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{3} }
func (*ResourceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{3} }
func (m *ResourceIdentifier) GetGroup() string {
if m != nil {
@@ -119,7 +119,7 @@ type ResourceSelector struct {
func (m *ResourceSelector) Reset() { *m = ResourceSelector{} }
func (m *ResourceSelector) String() string { return proto.CompactTextString(m) }
func (*ResourceSelector) ProtoMessage() {}
func (*ResourceSelector) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{4} }
func (*ResourceSelector) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{4} }
func (m *ResourceSelector) GetIncludedNamespaces() []string {
if m != nil {
@@ -164,9 +164,9 @@ func init() {
proto.RegisterType((*ResourceSelector)(nil), "generated.ResourceSelector")
}
func init() { proto.RegisterFile("Shared.proto", fileDescriptor5) }
func init() { proto.RegisterFile("Shared.proto", fileDescriptor6) }
var fileDescriptor5 = []byte{
var fileDescriptor6 = []byte{
// 294 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x92, 0xc1, 0x4e, 0xb5, 0x30,
0x10, 0x85, 0xc3, 0x05, 0xee, 0xff, 0x33, 0xba, 0xd0, 0x46, 0x93, 0xc6, 0xb8, 0x20, 0xac, 0x58,
+14 -14
View File
@@ -28,7 +28,7 @@ type CreateVolumeRequest struct {
func (m *CreateVolumeRequest) Reset() { *m = CreateVolumeRequest{} }
func (m *CreateVolumeRequest) String() string { return proto.CompactTextString(m) }
func (*CreateVolumeRequest) ProtoMessage() {}
func (*CreateVolumeRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{0} }
func (*CreateVolumeRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{0} }
func (m *CreateVolumeRequest) GetPlugin() string {
if m != nil {
@@ -72,7 +72,7 @@ type CreateVolumeResponse struct {
func (m *CreateVolumeResponse) Reset() { *m = CreateVolumeResponse{} }
func (m *CreateVolumeResponse) String() string { return proto.CompactTextString(m) }
func (*CreateVolumeResponse) ProtoMessage() {}
func (*CreateVolumeResponse) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{1} }
func (*CreateVolumeResponse) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{1} }
func (m *CreateVolumeResponse) GetVolumeID() string {
if m != nil {
@@ -90,7 +90,7 @@ type GetVolumeInfoRequest struct {
func (m *GetVolumeInfoRequest) Reset() { *m = GetVolumeInfoRequest{} }
func (m *GetVolumeInfoRequest) String() string { return proto.CompactTextString(m) }
func (*GetVolumeInfoRequest) ProtoMessage() {}
func (*GetVolumeInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{2} }
func (*GetVolumeInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{2} }
func (m *GetVolumeInfoRequest) GetPlugin() string {
if m != nil {
@@ -121,7 +121,7 @@ type GetVolumeInfoResponse struct {
func (m *GetVolumeInfoResponse) Reset() { *m = GetVolumeInfoResponse{} }
func (m *GetVolumeInfoResponse) String() string { return proto.CompactTextString(m) }
func (*GetVolumeInfoResponse) ProtoMessage() {}
func (*GetVolumeInfoResponse) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{3} }
func (*GetVolumeInfoResponse) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{3} }
func (m *GetVolumeInfoResponse) GetVolumeType() string {
if m != nil {
@@ -147,7 +147,7 @@ type CreateSnapshotRequest struct {
func (m *CreateSnapshotRequest) Reset() { *m = CreateSnapshotRequest{} }
func (m *CreateSnapshotRequest) String() string { return proto.CompactTextString(m) }
func (*CreateSnapshotRequest) ProtoMessage() {}
func (*CreateSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{4} }
func (*CreateSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{4} }
func (m *CreateSnapshotRequest) GetPlugin() string {
if m != nil {
@@ -184,7 +184,7 @@ type CreateSnapshotResponse struct {
func (m *CreateSnapshotResponse) Reset() { *m = CreateSnapshotResponse{} }
func (m *CreateSnapshotResponse) String() string { return proto.CompactTextString(m) }
func (*CreateSnapshotResponse) ProtoMessage() {}
func (*CreateSnapshotResponse) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{5} }
func (*CreateSnapshotResponse) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{5} }
func (m *CreateSnapshotResponse) GetSnapshotID() string {
if m != nil {
@@ -201,7 +201,7 @@ type DeleteSnapshotRequest struct {
func (m *DeleteSnapshotRequest) Reset() { *m = DeleteSnapshotRequest{} }
func (m *DeleteSnapshotRequest) String() string { return proto.CompactTextString(m) }
func (*DeleteSnapshotRequest) ProtoMessage() {}
func (*DeleteSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{6} }
func (*DeleteSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{6} }
func (m *DeleteSnapshotRequest) GetPlugin() string {
if m != nil {
@@ -225,7 +225,7 @@ type GetVolumeIDRequest struct {
func (m *GetVolumeIDRequest) Reset() { *m = GetVolumeIDRequest{} }
func (m *GetVolumeIDRequest) String() string { return proto.CompactTextString(m) }
func (*GetVolumeIDRequest) ProtoMessage() {}
func (*GetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{7} }
func (*GetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{7} }
func (m *GetVolumeIDRequest) GetPlugin() string {
if m != nil {
@@ -248,7 +248,7 @@ type GetVolumeIDResponse struct {
func (m *GetVolumeIDResponse) Reset() { *m = GetVolumeIDResponse{} }
func (m *GetVolumeIDResponse) String() string { return proto.CompactTextString(m) }
func (*GetVolumeIDResponse) ProtoMessage() {}
func (*GetVolumeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{8} }
func (*GetVolumeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{8} }
func (m *GetVolumeIDResponse) GetVolumeID() string {
if m != nil {
@@ -266,7 +266,7 @@ type SetVolumeIDRequest struct {
func (m *SetVolumeIDRequest) Reset() { *m = SetVolumeIDRequest{} }
func (m *SetVolumeIDRequest) String() string { return proto.CompactTextString(m) }
func (*SetVolumeIDRequest) ProtoMessage() {}
func (*SetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{9} }
func (*SetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{9} }
func (m *SetVolumeIDRequest) GetPlugin() string {
if m != nil {
@@ -296,7 +296,7 @@ type SetVolumeIDResponse struct {
func (m *SetVolumeIDResponse) Reset() { *m = SetVolumeIDResponse{} }
func (m *SetVolumeIDResponse) String() string { return proto.CompactTextString(m) }
func (*SetVolumeIDResponse) ProtoMessage() {}
func (*SetVolumeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{10} }
func (*SetVolumeIDResponse) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{10} }
func (m *SetVolumeIDResponse) GetPersistentVolume() []byte {
if m != nil {
@@ -313,7 +313,7 @@ type VolumeSnapshotterInitRequest struct {
func (m *VolumeSnapshotterInitRequest) Reset() { *m = VolumeSnapshotterInitRequest{} }
func (m *VolumeSnapshotterInitRequest) String() string { return proto.CompactTextString(m) }
func (*VolumeSnapshotterInitRequest) ProtoMessage() {}
func (*VolumeSnapshotterInitRequest) Descriptor() ([]byte, []int) { return fileDescriptor6, []int{11} }
func (*VolumeSnapshotterInitRequest) Descriptor() ([]byte, []int) { return fileDescriptor7, []int{11} }
func (m *VolumeSnapshotterInitRequest) GetPlugin() string {
if m != nil {
@@ -614,9 +614,9 @@ var _VolumeSnapshotter_serviceDesc = grpc.ServiceDesc{
Metadata: "VolumeSnapshotter.proto",
}
func init() { proto.RegisterFile("VolumeSnapshotter.proto", fileDescriptor6) }
func init() { proto.RegisterFile("VolumeSnapshotter.proto", fileDescriptor7) }
var fileDescriptor6 = []byte{
var fileDescriptor7 = []byte{
// 566 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xc1, 0x6e, 0xd3, 0x40,
0x10, 0xd5, 0xda, 0x6e, 0x44, 0x26, 0xa5, 0x0a, 0x9b, 0xa4, 0x58, 0x16, 0x04, 0xe3, 0x0b, 0x51,
+46
View File
@@ -5,6 +5,7 @@ package mocks
import (
mock "github.com/stretchr/testify/mock"
velero "github.com/vmware-tanzu/velero/pkg/plugin/velero"
isv1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
)
// Manager is an autogenerated mock type for the Manager type
@@ -200,3 +201,48 @@ func (_m *Manager) GetVolumeSnapshotter(name string) (velero.VolumeSnapshotter,
return r0, r1
}
// GetItemSnapshotter provides a mock function with given fields: name
func (_m *Manager) GetItemSnapshotter(name string) (isv1.ItemSnapshotter, error) {
ret := _m.Called(name)
var r0 isv1.ItemSnapshotter
if rf, ok := ret.Get(0).(func(string) isv1.ItemSnapshotter); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(isv1.ItemSnapshotter)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetItemSnapshotters provides a mock function with given fields:
func (_m *Manager) GetItemSnapshotters() ([]isv1.ItemSnapshotter, error) {
ret := _m.Called()
var r0 []isv1.ItemSnapshotter
if rf, ok := ret.Get(0).(func() []isv1.ItemSnapshotter); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]isv1.ItemSnapshotter)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
+94
View File
@@ -0,0 +1,94 @@
syntax = "proto3";
package generated;
import "Shared.proto";
message ItemSnapshotterAppliesToRequest {
string plugin = 1;
}
message ItemSnapshotterAppliesToResponse {
ResourceSelector ResourceSelector = 1;
}
message AlsoHandlesRequest {
string plugin = 1;
bytes item = 2;
bytes backup = 3;
}
message AlsoHandlesResponse {
repeated ResourceIdentifier handledItems = 1;
}
message SnapshotItemRequest {
string plugin = 1;
bytes item = 2;
map<string, string> params = 3;
bytes backup = 4;
}
message SnapshotItemResponse {
bytes item = 1;
string snapshotID = 2;
map<string, string> snapshotMetadata = 3;
repeated ResourceIdentifier additionalItems = 4;
repeated ResourceIdentifier handledItems = 5;
}
message ProgressRequest {
string plugin = 1;
ResourceIdentifier itemID = 2;
string snapshotID = 3;
bytes backup = 4;
}
message ProgressResponse {
string phase = 1;
int64 itemsCompleted = 2;
int64 itemsToComplete = 3;
int64 started = 4;
int64 startedNano = 5;
int64 updated = 6;
int64 updatedNano = 7;
string err = 8;
}
message DeleteItemSnapshotRequest {
string plugin = 1;
string snapshotID = 2;
bytes itemFromBackup = 3;
map<string, string> metadata = 4;
map<string, string> params = 5;
}
message CreateItemFromSnapshotRequest {
string plugin = 1;
bytes item = 2;
string snapshotID = 3;
bytes itemFromBackup = 4;
map<string, string> snapshotMetadata = 5;
map<string, string> params = 6;
bytes restore = 7;
}
message CreateItemFromSnapshotResponse {
bytes item = 1;
repeated ResourceIdentifier additionalItems = 2;
bool skipRestore = 3;
}
message ItemSnapshotterInitRequest {
string plugin = 1;
map<string, string> config = 2;
}
service ItemSnapshotter {
rpc Init(ItemSnapshotterInitRequest) returns (Empty);
rpc AppliesTo(ItemSnapshotterAppliesToRequest) returns (ItemSnapshotterAppliesToResponse);
rpc AlsoHandles(AlsoHandlesRequest) returns (AlsoHandlesResponse);
rpc SnapshotItem(SnapshotItemRequest) returns (SnapshotItemResponse);
rpc Progress(ProgressRequest) returns (ProgressResponse);
rpc DeleteSnapshot(DeleteItemSnapshotRequest) returns (Empty);
rpc CreateItemFromSnapshot(CreateItemFromSnapshotRequest) returns (CreateItemFromSnapshotResponse);
}
@@ -0,0 +1,197 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
"context"
"fmt"
"time"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"k8s.io/apimachinery/pkg/runtime"
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
type AlsoHandlesInput struct {
// Item is the item that will be snapshotted
Item runtime.Unstructured
// Backup is the representation of the backup resource being processed by Velero.
Backup *api.Backup
}
type SnapshotItemInput struct {
// Item is the item to snapshot
Item runtime.Unstructured
// Params are parameters to the snapshot
Params map[string]string
// Backup is the representation of the backup resource being processed by Velero.
Backup *api.Backup
}
type SnapshotItemOutput struct {
// UpdatedItem is the Item that should be included in the backup. It can optionally be modified during the Snapshot
UpdatedItem runtime.Unstructured
// SnapshotID identifies the snapshot that was taken
SnapshotID string
// SnapshotMetadata is information in addition to the SnapshotID that the
// plugin wants to store in the backup. SnapshotMetadata will be passed
// back in for DeleteSnapshot and CreateItemFromSnapshot
SnapshotMetadata map[string]string
// AdditionalItems are resources that need to be included in the backup to support this snapshhot
AdditionalItems []velero.ResourceIdentifier
// Items that were handled by this snapshot that should be excluded from the backup
HandledItems []velero.ResourceIdentifier
}
// ProgressInput contains the input parameters for the ItemSnapshotter's Progress function.
type ProgressInput struct {
// ItemID is the id of item that was stored in the backup
ItemID velero.ResourceIdentifier
// SnapshotID is the snapshot ID returned by ItemSnapshotter
SnapshotID string
// Backup is the representation of the backup resource being processed by Velero.
Backup *api.Backup
}
// SnapshotPhase is the lifecycle phase of a Velero item snapshot.
type SnapshotPhase string
const (
// SnapshotPhaseInProgress means the snapshot of the item has been taken and the point-in-time has been preserved,
// but the snapshot is not ready for use yet
SnapshotPhaseInProgress = SnapshotPhase("InProgress")
// SnapshotPhaseCompleted means the item snapshot was successfully created and can be restored from
SnapshotPhaseCompleted = SnapshotPhase("Completed")
// SnapshotPhaseFailed means the item snapshot was unable to be completed
SnapshotPhaseFailed = SnapshotPhase("Failed")
)
func SnapshotPhaseFromString(phase string) (SnapshotPhase, error) {
switch phase {
case string(SnapshotPhaseInProgress):
return SnapshotPhaseInProgress, nil
case string(SnapshotPhaseCompleted):
return SnapshotPhaseCompleted, nil
case string(SnapshotPhaseFailed):
return SnapshotPhaseFailed, nil
default:
return SnapshotPhase(""), fmt.Errorf("%s is not a valid SnapshotPhase", phase)
}
}
type ProgressOutput struct {
// Phase of the snapshot. If the phase is SnapshotPhaseFailed, the error will be in the Err string
Phase SnapshotPhase
// Err is a message about the error(s) that occurred during the processing of the snapshot
Err string
// ItemsCompleted is the number of items that have been completed in processing of the snapshot
// This is simply to show progress, when Phase goes to SnapshotPhaseCompleted ItemsCompleted and ItemsToComplete
// should be the same. This could be blocks to copy, files to copy, or anything else.
ItemsCompleted int64
// ItemsToComplete is the number of items that need to be completed
ItemsToComplete int64
// Started indicates when processing on the snapshot began (usually the time the snapshot was taken)
Started time.Time
// Updated indicates the time the status was last updated. Time 0 (time.Unix(0, 0)) is returned if unknown.
Updated time.Time
}
type DeleteSnapshotInput struct {
// SnapshotID is the snapshot to delete
SnapshotID string
// ItemFromBackup is the resource that was included in the backup
ItemFromBackup runtime.Unstructured
// SnapshotMetadata is the metadata that was returned when the snapshot was originally taken
SnapshotMetadata map[string]string
// Params are parameters to the deletion
Params map[string]string
}
type CreateItemInput struct {
// The snapshotted item at this stage of the restore (RestoreItemActions may
// have modified the item prior to CreateItemFromSnapshot being called)
SnapshottedItem runtime.Unstructured
// SnapshotID is the snapshot to create the item from
SnapshotID string
// ItemFromBackup is the snapshotted item that was stored in the backup
ItemFromBackup runtime.Unstructured
// SnapshotMetadata is the metadata that was returned when the snapshot was originally taken
SnapshotMetadata map[string]string
// Params are parameters to the deletion
Params map[string]string
// Restore is the representation of the restore resource being processed by Velero.
Restore *api.Restore
}
type CreateItemOutput struct {
// UpdatedItem is the item being restored mutated by ItemAction.
UpdatedItem runtime.Unstructured
// AdditionalItems is a list of additional related items that should
// be restored.
AdditionalItems []velero.ResourceIdentifier
// SkipRestore tells velero to stop executing further actions
// on this item, and skip the restore step. When this field's
// value is true, AdditionalItems will be ignored.
SkipRestore bool
}
// ItemSnapshotter handles snapshots on an individual item being backed up.
type ItemSnapshotter interface {
// Init prepares the ItemSnapshotter for usage using the provided map of
// configuration key-value pairs. It returns an error if the ItemSnapshotter
// cannot be initialized from the provided config.
Init(config map[string]string) error
// AppliesTo returns information about which resources this action should be invoked for.
// An ItemSnapshotter's SnapshotItem method will only be invoked on items that match the returned
// selector. A zero-valued ResourceSelector matches all resources.
AppliesTo() (velero.ResourceSelector, error)
// AlsoHandles is called for each item this ItemSnapshotter should handle and returns any items
// which will be handled by this plugin when snapshotting the item. These items will be excluded from the
// items being backed up. AlsoHandles will be called before SnapshotItem is called. For example, a database may expose
// a database resource that can be snapshotted. If the database uses a PVC that will be snapshotted/backed up as
// part of the database snapshot, that PVC should be returned when AlsoHandles is invoked. This is different from
// AdditionalItems (returned in SnapshotItemOutput and CreateItemOutput) which are specifying additional resources
// that Velero should store in the backup or create.
AlsoHandles(input *AlsoHandlesInput) ([]velero.ResourceIdentifier, error)
// SnapshotItem causes the ItemSnapshotter to snapshot the specified item. It may also
// perform arbitrary logic with the item being backed up, including mutating the item itself prior to backup.
// The item (unmodified or modified) should be returned, along with an optional slice of ResourceIdentifiers specifying
// additional related items that should be backed up.
// A caller can pass a context that includes a timeout. If the time to take the snapshot exceeds the
// time in the context, the plugin may abort the snapshot. The context timeout does not apply to upload
// time that occurs after SnapshotItem returns
SnapshotItem(ctx context.Context, input *SnapshotItemInput) (*SnapshotItemOutput, error)
// Progress will return the progress of a snapshot that is being uploaded
Progress(input *ProgressInput) (*ProgressOutput, error)
// DeleteSnapshot removes a snapshot
DeleteSnapshot(ctx context.Context, input *DeleteSnapshotInput) error
// CreateItemFromSnapshot creates a new item from the snapshot
CreateItemFromSnapshot(ctx context.Context, input *CreateItemInput) (*CreateItemOutput, error)
}
@@ -0,0 +1,157 @@
// Code generated by mockery v0.0.0-dev. DO NOT EDIT.
package mocks
import (
context "context"
mock "github.com/stretchr/testify/mock"
v1 "github.com/vmware-tanzu/velero/pkg/plugin/velero/item_snapshotter/v1"
velero "github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// ItemSnapshotter is an autogenerated mock type for the ItemSnapshotter type
type ItemSnapshotter struct {
mock.Mock
}
// AlsoHandles provides a mock function with given fields: input
func (_m *ItemSnapshotter) AlsoHandles(input *v1.AlsoHandlesInput) ([]velero.ResourceIdentifier, error) {
ret := _m.Called(input)
var r0 []velero.ResourceIdentifier
if rf, ok := ret.Get(0).(func(*v1.AlsoHandlesInput) []velero.ResourceIdentifier); ok {
r0 = rf(input)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]velero.ResourceIdentifier)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*v1.AlsoHandlesInput) error); ok {
r1 = rf(input)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// AppliesTo provides a mock function with given fields:
func (_m *ItemSnapshotter) AppliesTo() (velero.ResourceSelector, error) {
ret := _m.Called()
var r0 velero.ResourceSelector
if rf, ok := ret.Get(0).(func() velero.ResourceSelector); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(velero.ResourceSelector)
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateItemFromSnapshot provides a mock function with given fields: ctx, input
func (_m *ItemSnapshotter) CreateItemFromSnapshot(ctx context.Context, input *v1.CreateItemInput) (*v1.CreateItemOutput, error) {
ret := _m.Called(ctx, input)
var r0 *v1.CreateItemOutput
if rf, ok := ret.Get(0).(func(context.Context, *v1.CreateItemInput) *v1.CreateItemOutput); ok {
r0 = rf(ctx, input)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1.CreateItemOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1.CreateItemInput) error); ok {
r1 = rf(ctx, input)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteSnapshot provides a mock function with given fields: ctx, input
func (_m *ItemSnapshotter) DeleteSnapshot(ctx context.Context, input *v1.DeleteSnapshotInput) error {
ret := _m.Called(ctx, input)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *v1.DeleteSnapshotInput) error); ok {
r0 = rf(ctx, input)
} else {
r0 = ret.Error(0)
}
return r0
}
// Init provides a mock function with given fields: config
func (_m *ItemSnapshotter) Init(config map[string]string) error {
ret := _m.Called(config)
var r0 error
if rf, ok := ret.Get(0).(func(map[string]string) error); ok {
r0 = rf(config)
} else {
r0 = ret.Error(0)
}
return r0
}
// Progress provides a mock function with given fields: input
func (_m *ItemSnapshotter) Progress(input *v1.ProgressInput) (*v1.ProgressOutput, error) {
ret := _m.Called(input)
var r0 *v1.ProgressOutput
if rf, ok := ret.Get(0).(func(*v1.ProgressInput) *v1.ProgressOutput); ok {
r0 = rf(input)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1.ProgressOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*v1.ProgressInput) error); ok {
r1 = rf(input)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SnapshotItem provides a mock function with given fields: ctx, input
func (_m *ItemSnapshotter) SnapshotItem(ctx context.Context, input *v1.SnapshotItemInput) (*v1.SnapshotItemOutput, error) {
ret := _m.Called(ctx, input)
var r0 *v1.SnapshotItemOutput
if rf, ok := ret.Get(0).(func(context.Context, *v1.SnapshotItemInput) *v1.SnapshotItemOutput); ok {
r0 = rf(ctx, input)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1.SnapshotItemOutput)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *v1.SnapshotItemInput) error); ok {
r1 = rf(ctx, input)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
+7 -1
View File
@@ -1,5 +1,5 @@
/*
Copyright 2019 the Velero contributors.
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -48,3 +48,9 @@ type ResourceSelector struct {
// for details on syntax.
LabelSelector string
}
// Applicable allows actions and plugins to specify which resources they should be invoked for
type Applicable interface {
// AppliesTo returns information about which resources this Responder should be invoked for.
AppliesTo() (ResourceSelector, error)
}
+20 -5
View File
@@ -25,6 +25,7 @@ import (
"github.com/sirupsen/logrus"
corev1api "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/cache"
@@ -118,9 +119,10 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api.
b.resultsLock.Unlock()
var (
errs []error
podVolumeBackups []*velerov1api.PodVolumeBackup
podVolumes = make(map[string]corev1api.Volume)
errs []error
podVolumeBackups []*velerov1api.PodVolumeBackup
podVolumes = make(map[string]corev1api.Volume)
mountedPodVolumes = sets.String{}
)
// put the pod's volumes in a map for efficient lookup below
@@ -128,6 +130,12 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api.
podVolumes[podVolume.Name] = podVolume
}
for _, container := range pod.Spec.Containers {
for _, volumeMount := range container.VolumeMounts {
mountedPodVolumes.Insert(volumeMount.Name)
}
}
var numVolumeSnapshots int
for _, volumeName := range volumesToBackup {
volume, ok := podVolumes[volumeName]
@@ -145,6 +153,11 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api.
}
}
// ignore non-running pods
if pod.Status.Phase != corev1api.PodRunning {
log.Warnf("Skipping volume %s in pod %s/%s - pod not running", volumeName, pod.Namespace, pod.Name)
continue
}
// hostPath volumes are not supported because they're not mounted into /var/lib/kubelet/pods, so our
// daemonset pod has no way to access their data.
isHostPath, err := isHostPathVolume(&volume, pvc, b.pvClient.PersistentVolumes())
@@ -157,8 +170,10 @@ func (b *backupper) BackupPodVolumes(backup *velerov1api.Backup, pod *corev1api.
continue
}
// emptyDir volumes on finished pods are not supported because the volume is already gone and would result in an error
if (pod.Status.Phase == corev1api.PodSucceeded || pod.Status.Phase == corev1api.PodFailed) && volume.EmptyDir != nil {
// volumes that are not mounted by any container should not be backed up, because
// its directory is not created
if !mountedPodVolumes.Has(volumeName) {
log.Warnf("Volume %s is declared in pod %s/%s but not mounted by any container, skipping", volumeName, pod.Namespace, pod.Name)
continue
}
+17 -10
View File
@@ -100,15 +100,18 @@ func isPVBMatchPod(pvb *velerov1api.PodVolumeBackup, podName string, namespace s
return podName == pvb.Spec.Pod.Name && namespace == pvb.Spec.Pod.Namespace
}
// volumeIsProjected checks if the given volume exists in the list of podVolumes
// and returns true if the volume has a projected source
func volumeIsProjected(volumeName string, podVolumes []corev1api.Volume) bool {
for _, volume := range podVolumes {
if volume.Name == volumeName && volume.Projected != nil {
return true
// volumeHasNonRestorableSource checks if the given volume exists in the list of podVolumes
// and returns true if the volume's source is not restorable. This is true for volumes with
// a Projected or DownwardAPI source.
func volumeHasNonRestorableSource(volumeName string, podVolumes []corev1api.Volume) bool {
var volume corev1api.Volume
for _, v := range podVolumes {
if v.Name == volumeName {
volume = v
break
}
}
return false
return volume.Projected != nil || volume.DownwardAPI != nil
}
// GetVolumeBackupsForPod returns a map, of volume name -> snapshot id,
@@ -127,10 +130,10 @@ func GetVolumeBackupsForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, pod
continue
}
// If the volume came from a projected source, skip its restore.
// If the volume came from a projected or DownwardAPI source, skip its restore.
// This allows backups affected by https://github.com/vmware-tanzu/velero/issues/3863
// to be restored successfully.
if volumeIsProjected(pvb.Spec.Volume, pod.Spec.Volumes) {
// or https://github.com/vmware-tanzu/velero/issues/4053 to be restored successfully.
if volumeHasNonRestorableSource(pvb.Spec.Volume, pod.Spec.Volumes) {
continue
}
@@ -205,6 +208,10 @@ func GetPodVolumesUsingRestic(pod *corev1api.Pod, defaultVolumesToRestic bool) [
if pv.Projected != nil {
continue
}
// don't backup DownwardAPI volumes, all data in those come from kube state.
if pv.DownwardAPI != nil {
continue
}
// don't backup volumes that are included in the exclude list.
if contains(volsToExclude, pv.Name) {
continue
+107 -7
View File
@@ -152,6 +152,30 @@ func TestGetVolumeBackupsForPod(t *testing.T) {
sourcePodNs: "TestNS",
expected: map[string]string{"pvb-non-projected": "snapshot1"},
},
{
name: "volumes from PVBs that correspond to a pod volume from a DownwardAPI source are not returned",
podVolumeBackups: []*velerov1api.PodVolumeBackup{
builder.ForPodVolumeBackup("velero", "pvb-1").PodName("TestPod").PodNamespace("TestNS").SnapshotID("snapshot1").Volume("pvb-non-downwardapi").Result(),
builder.ForPodVolumeBackup("velero", "pvb-1").PodName("TestPod").PodNamespace("TestNS").SnapshotID("snapshot2").Volume("pvb-downwardapi").Result(),
},
podVolumes: []corev1api.Volume{
{
Name: "pvb-non-downwardapi",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
},
{
Name: "pvb-downwardapi",
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{},
},
},
},
podName: "TestPod",
sourcePodNs: "TestNS",
expected: map[string]string{"pvb-non-downwardapi": "snapshot1"},
},
}
for _, test := range tests {
@@ -568,6 +592,39 @@ func TestGetPodVolumesUsingRestic(t *testing.T) {
},
expected: []string{"resticPV1", "resticPV2", "resticPV3"},
},
{
name: "should exclude DownwardAPI volumes",
defaultVolumesToRestic: true,
pod: &corev1api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
VolumesToExcludeAnnotation: "nonResticPV1,nonResticPV2,nonResticPV3",
},
},
Spec: corev1api.PodSpec{
Volumes: []corev1api.Volume{
{Name: "resticPV1"}, {Name: "resticPV2"}, {Name: "resticPV3"},
{
Name: "downwardAPI",
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{
Items: []corev1api.DownwardAPIVolumeFile{
{
Path: "labels",
FieldRef: &corev1api.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.labels",
},
},
},
},
},
},
},
},
},
expected: []string{"resticPV1", "resticPV2", "resticPV3"},
},
}
for _, tc := range testCases {
@@ -656,7 +713,7 @@ func TestIsPVBMatchPod(t *testing.T) {
}
}
func TestVolumeIsProjected(t *testing.T) {
func TestVolumeHasNonRestorableSource(t *testing.T) {
testCases := []struct {
name string
volumeName string
@@ -668,7 +725,7 @@ func TestVolumeIsProjected(t *testing.T) {
volumeName: "missing-volume",
podVolumes: []corev1api.Volume{
{
Name: "non-projected",
Name: "restorable",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
@@ -679,15 +736,21 @@ func TestVolumeIsProjected(t *testing.T) {
Projected: &corev1api.ProjectedVolumeSource{},
},
},
{
Name: "downwardapi",
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{},
},
},
},
expected: false,
},
{
name: "volume name in list of volumes but not projected",
volumeName: "non-projected",
name: "volume name in list of volumes but not projected or DownwardAPI",
volumeName: "restorable",
podVolumes: []corev1api.Volume{
{
Name: "non-projected",
Name: "restorable",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
@@ -698,6 +761,12 @@ func TestVolumeIsProjected(t *testing.T) {
Projected: &corev1api.ProjectedVolumeSource{},
},
},
{
Name: "downwardapi",
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{},
},
},
},
expected: false,
},
@@ -706,7 +775,7 @@ func TestVolumeIsProjected(t *testing.T) {
volumeName: "projected",
podVolumes: []corev1api.Volume{
{
Name: "non-projected",
Name: "restorable",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
@@ -717,6 +786,37 @@ func TestVolumeIsProjected(t *testing.T) {
Projected: &corev1api.ProjectedVolumeSource{},
},
},
{
Name: "downwardapi",
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{},
},
},
},
expected: true,
},
{
name: "volume name in list of volumes and is a DownwardAPI volume",
volumeName: "downwardapi",
podVolumes: []corev1api.Volume{
{
Name: "restorable",
VolumeSource: corev1api.VolumeSource{
PersistentVolumeClaim: &corev1api.PersistentVolumeClaimVolumeSource{},
},
},
{
Name: "projected",
VolumeSource: corev1api.VolumeSource{
Projected: &corev1api.ProjectedVolumeSource{},
},
},
{
Name: "downwardapi",
VolumeSource: corev1api.VolumeSource{
DownwardAPI: &corev1api.DownwardAPIVolumeSource{},
},
},
},
expected: true,
},
@@ -724,7 +824,7 @@ func TestVolumeIsProjected(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
actual := volumeIsProjected(tc.volumeName, tc.podVolumes)
actual := volumeHasNonRestorableSource(tc.volumeName, tc.podVolumes)
assert.Equal(t, tc.expected, actual)
})
+2 -3
View File
@@ -74,10 +74,9 @@ func getRepoPrefix(location *velerov1api.BackupStorageLocation) (string, error)
region, err = getAWSBucketRegion(bucket)
}
if err != nil {
url = "s3.amazonaws.com"
} else {
url = fmt.Sprintf("s3-%s.amazonaws.com", region)
return "", errors.Wrapf(err, "failed to detect the region via bucket: %s", bucket)
}
url = fmt.Sprintf("s3-%s.amazonaws.com", region)
}
return fmt.Sprintf("s3:%s/%s", url, path.Join(bucket, prefix)), nil
+2 -1
View File
@@ -85,7 +85,8 @@ func TestGetRepoIdentifier(t *testing.T) {
getAWSBucketRegion: func(string) (string, error) {
return "", errors.New("no region found")
},
expected: "s3:s3.amazonaws.com/bucket/restic/repo-1",
expected: "",
expectedErr: "failed to detect the region via bucket: bucket: no region found",
},
{
name: "s3.s3-<region>.amazonaws.com URL format is used if region can be determined for AWS BSL",
@@ -0,0 +1,89 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package restore
import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
)
// AdmissionWebhookConfigurationAction is a RestoreItemAction plugin applicable to mutatingwebhookconfiguration and
// validatingwebhookconfiguration to reset the invalid value for "sideEffects" of the webhooks.
// More background please refer to https://github.com/vmware-tanzu/velero/issues/3516
type AdmissionWebhookConfigurationAction struct {
logger logrus.FieldLogger
}
// NewAdmissionWebhookConfigurationAction creates a new instance of AdmissionWebhookConfigurationAction
func NewAdmissionWebhookConfigurationAction(logger logrus.FieldLogger) *AdmissionWebhookConfigurationAction {
return &AdmissionWebhookConfigurationAction{logger: logger}
}
// AppliesTo implements the RestoreItemAction plugin interface method.
func (a *AdmissionWebhookConfigurationAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"mutatingwebhookconfigurations", "validatingwebhookconfigurations"},
}, nil
}
// Execute will reset the value of "sideEffects" attribute of each item in the "webhooks" list to "None" if they are invalid values for
// v1, such as "Unknown" or "Some"
func (a *AdmissionWebhookConfigurationAction) Execute(input *velero.RestoreItemActionExecuteInput) (*velero.RestoreItemActionExecuteOutput, error) {
a.logger.Info("Executing ChangeStorageClassAction")
defer a.logger.Info("Done executing ChangeStorageClassAction")
item := input.Item
apiVersion, _, err := unstructured.NestedString(item.UnstructuredContent(), "apiVersion")
if err != nil {
return nil, errors.Wrap(err, "failed to get the apiVersion from input item")
}
name, _, _ := unstructured.NestedString(item.UnstructuredContent(), "metadata", "name")
logger := a.logger.WithField("resource_name", name)
if apiVersion != "admissionregistration.k8s.io/v1" {
logger.Infof("unable to handle api version: %s, skip", apiVersion)
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
}
webhooks, ok, err := unstructured.NestedSlice(item.UnstructuredContent(), "webhooks")
if err != nil {
return nil, errors.Wrap(err, "failed to get webhooks slice from input item")
}
if !ok {
logger.Info("webhooks is not set, skip")
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
}
newWebhooks := make([]interface{}, 0)
for i, entry := range webhooks {
logger2 := logger.WithField("index", i)
obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&entry)
if err != nil {
logger2.Errorf("failed to convert the webhook entry, error: %v, it will be dropped", err)
continue
}
s, _, _ := unstructured.NestedString(obj, "sideEffects")
if s != "None" && s != "NoneOnDryRun" {
logger2.Infof("reset the invalid sideEffects value '%s' to 'None'", s)
obj["sideEffects"] = "None"
}
newWebhooks = append(newWebhooks, obj)
}
item.UnstructuredContent()["webhooks"] = newWebhooks
return velero.NewRestoreItemActionExecuteOutput(item), nil
}
@@ -0,0 +1,199 @@
package restore
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
func TestNewAdmissionWebhookConfigurationActionExecute(t *testing.T) {
action := NewAdmissionWebhookConfigurationAction(velerotest.NewLogger())
cases := []struct {
name string
itemJSON string
wantErr bool
NoneSideEffectsIndex []int // the indexes with sideEffects that arereset to None
NotNoneSideEffectsIndex []int // the indexes with sideEffects that are not reset to None
}{
{
name: "v1 mutatingwebhookconfiguration with sideEffects as Unknown",
itemJSON: `{
"apiVersion": "admissionregistration.k8s.io/v1",
"kind": "MutatingWebhookConfiguration",
"metadata": {
"name": "my-test-mutating"
},
"webhooks": [
{
"clientConfig": {
"url": "https://mytest.org"
},
"rules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"pods"
],
"scope": "Namespaced"
}
],
"sideEffects": "Unknown"
}
]
}`,
wantErr: false,
NoneSideEffectsIndex: []int{0},
},
{
name: "v1 validatingwebhookconfiguration with sideEffects as Some",
itemJSON: `{
"apiVersion": "admissionregistration.k8s.io/v1",
"kind": "ValidatingWebhookConfiguration",
"metadata": {
"name": "my-test-validating"
},
"webhooks": [
{
"clientConfig": {
"url": "https://mytest.org"
},
"rules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"pods"
],
"scope": "Namespaced"
}
],
"sideEffects": "Some"
}
]
}`,
wantErr: false,
NoneSideEffectsIndex: []int{0},
},
{
name: "v1beta1 validatingwebhookconfiguration with sideEffects as Some, nothing should change",
itemJSON: `{
"apiVersion": "admissionregistration.k8s.io/v1beta1",
"kind": "ValidatingWebhookConfiguration",
"metadata": {
"name": "my-test-validating"
},
"webhooks": [
{
"clientConfig": {
"url": "https://mytest.org"
},
"rules": [
{
"apiGroups": [
""
],
"apiVersions": [
"v1"
],
"operations": [
"CREATE"
],
"resources": [
"pods"
],
"scope": "Namespaced"
}
],
"sideEffects": "Some"
}
]
}`,
wantErr: false,
NotNoneSideEffectsIndex: []int{0},
},
{
name: "v1 validatingwebhookconfiguration with multiple invalid sideEffects",
itemJSON: `{
"apiVersion": "admissionregistration.k8s.io/v1",
"kind": "ValidatingWebhookConfiguration",
"metadata": {
"name": "my-test-validating"
},
"webhooks": [
{
"clientConfig": {
"url": "https://mytest.org"
},
"sideEffects": "Some"
},
{
"clientConfig": {
"url": "https://mytest2.org"
},
"sideEffects": "Some"
}
]
}`,
wantErr: false,
NoneSideEffectsIndex: []int{0, 1},
},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
o := map[string]interface{}{}
json.Unmarshal([]byte(tt.itemJSON), &o)
input := &velero.RestoreItemActionExecuteInput{
Item: &unstructured.Unstructured{
Object: o,
},
}
output, err := action.Execute(input)
if tt.wantErr {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
}
if tt.NoneSideEffectsIndex != nil {
wb, _, err := unstructured.NestedSlice(output.UpdatedItem.UnstructuredContent(), "webhooks")
assert.Nil(t, err)
for _, i := range tt.NoneSideEffectsIndex {
it, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&wb[i])
assert.Nil(t, err)
s := it["sideEffects"].(string)
assert.Equal(t, "None", s)
}
}
if tt.NotNoneSideEffectsIndex != nil {
wb, _, err := unstructured.NestedSlice(output.UpdatedItem.UnstructuredContent(), "webhooks")
assert.Nil(t, err)
for _, i := range tt.NotNoneSideEffectsIndex {
it, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&wb[i])
assert.Nil(t, err)
s := it["sideEffects"].(string)
assert.NotEqual(t, "None", s)
}
}
})
}
}
+59 -17
View File
@@ -21,8 +21,11 @@ import (
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
storagev1client "k8s.io/client-go/kubernetes/typed/storage/v1"
@@ -55,7 +58,7 @@ func NewChangeStorageClassAction(
// be run for.
func (a *ChangeStorageClassAction) AppliesTo() (velero.ResourceSelector, error) {
return velero.ResourceSelector{
IncludedResources: []string{"persistentvolumeclaims", "persistentvolumes"},
IncludedResources: []string{"persistentvolumeclaims", "persistentvolumes", "statefulsets"},
}, nil
}
@@ -87,33 +90,72 @@ func (a *ChangeStorageClassAction) Execute(input *velero.RestoreItemActionExecut
"name": obj.GetName(),
})
// use the unstructured helpers here since this code is for both PVs and PVCs, and the
// field names are the same for both types.
storageClass, _, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "storageClassName")
if err != nil {
return nil, errors.Wrap(err, "error getting item's spec.storageClassName")
// change StatefulSet volumeClaimTemplates storageClassName
if obj.GetKind() == "StatefulSet" {
sts := new(appsv1.StatefulSet)
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), sts); err != nil {
return nil, err
}
if len(sts.Spec.VolumeClaimTemplates) > 0 {
for index, pvc := range sts.Spec.VolumeClaimTemplates {
exists, newStorageClass, err := a.isStorageClassExist(log, *pvc.Spec.StorageClassName, config)
if err != nil {
return nil, err
} else if !exists {
continue
}
log.Infof("Updating item's storage class name to %s", newStorageClass)
sts.Spec.VolumeClaimTemplates[index].Spec.StorageClassName = &newStorageClass
}
newObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(sts)
if err != nil {
return nil, errors.Wrap(err, "convert obj to StatefulSet failed")
}
obj.Object = newObj
}
} else {
// use the unstructured helpers here since this code is for both PVs and PVCs, and the
// field names are the same for both types.
storageClass, _, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "storageClassName")
if err != nil {
return nil, errors.Wrap(err, "error getting item's spec.storageClassName")
}
exists, newStorageClass, err := a.isStorageClassExist(log, storageClass, config)
if err != nil {
return nil, err
} else if !exists {
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
}
log.Infof("Updating item's storage class name to %s", newStorageClass)
if err := unstructured.SetNestedField(obj.UnstructuredContent(), newStorageClass, "spec", "storageClassName"); err != nil {
return nil, errors.Wrap(err, "unable to set item's spec.storageClassName")
}
}
return velero.NewRestoreItemActionExecuteOutput(obj), nil
}
func (a *ChangeStorageClassAction) isStorageClassExist(log *logrus.Entry, storageClass string, cm *corev1.ConfigMap) (exists bool, newStorageClass string, err error) {
if storageClass == "" {
log.Debug("Item has no storage class specified")
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
return false, "", nil
}
newStorageClass, ok := config.Data[storageClass]
newStorageClass, ok := cm.Data[storageClass]
if !ok {
log.Debugf("No mapping found for storage class %s", storageClass)
return velero.NewRestoreItemActionExecuteOutput(input.Item), nil
return false, "", nil
}
// validate that new storage class exists
if _, err := a.storageClassClient.Get(context.TODO(), newStorageClass, metav1.GetOptions{}); err != nil {
return nil, errors.Wrapf(err, "error getting storage class %s from API", newStorageClass)
return false, "", errors.Wrapf(err, "error getting storage class %s from API", newStorageClass)
}
log.Infof("Updating item's storage class name to %s", newStorageClass)
if err := unstructured.SetNestedField(obj.UnstructuredContent(), newStorageClass, "spec", "storageClassName"); err != nil {
return nil, errors.Wrap(err, "unable to set item's spec.storageClassName")
}
return velero.NewRestoreItemActionExecuteOutput(obj), nil
return true, newStorageClass, nil
}
+108 -27
View File
@@ -41,16 +41,17 @@ import (
// desired result.
func TestChangeStorageClassActionExecute(t *testing.T) {
tests := []struct {
name string
pvOrPVC interface{}
configMap *corev1api.ConfigMap
storageClass *storagev1api.StorageClass
want interface{}
wantErr error
name string
pvOrPvcOrSTS interface{}
configMap *corev1api.ConfigMap
storageClass *storagev1api.StorageClass
storageClassSlice []*storagev1api.StorageClass
want interface{}
wantErr error
}{
{
name: "a valid mapping for a persistent volume is applied correctly",
pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
name: "a valid mapping for a persistent volume is applied correctly",
pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
@@ -59,8 +60,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-2").Result(),
},
{
name: "a valid mapping for a persistent volume claim is applied correctly",
pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
name: "a valid mapping for a persistent volume claim is applied correctly",
pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
@@ -69,8 +70,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-2").Result(),
},
{
name: "when no config map exists for the plugin, the item is returned as-is",
pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
name: "when no config map exists for the plugin, the item is returned as-is",
pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/some-other-plugin", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
@@ -78,16 +79,16 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
},
{
name: "when no storage class mappings exist in the plugin config map, the item is returned as-is",
pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
name: "when no storage class mappings exist in the plugin config map, the item is returned as-is",
pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Result(),
want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
},
{
name: "when persistent volume has no storage class, the item is returned as-is",
pvOrPVC: builder.ForPersistentVolume("pv-1").Result(),
name: "when persistent volume has no storage class, the item is returned as-is",
pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
@@ -95,8 +96,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolume("pv-1").Result(),
},
{
name: "when persistent volume claim has no storage class, the item is returned as-is",
pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").Result(),
name: "when persistent volume claim has no storage class, the item is returned as-is",
pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
@@ -104,8 +105,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolumeClaim("velero", "pvc-1").Result(),
},
{
name: "when persistent volume's storage class has no mapping in the config map, the item is returned as-is",
pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
name: "when persistent volume's storage class has no mapping in the config map, the item is returned as-is",
pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-3", "storageclass-4").
@@ -113,8 +114,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
},
{
name: "when persistent volume claim's storage class has no mapping in the config map, the item is returned as-is",
pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
name: "when persistent volume claim's storage class has no mapping in the config map, the item is returned as-is",
pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-3", "storageclass-4").
@@ -122,8 +123,8 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
want: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
},
{
name: "when persistent volume's storage class is mapped to a nonexistent storage class, an error is returned",
pvOrPVC: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
name: "when persistent volume's storage class is mapped to a nonexistent storage class, an error is returned",
pvOrPvcOrSTS: builder.ForPersistentVolume("pv-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "nonexistent-storage-class").
@@ -131,8 +132,81 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
wantErr: errors.New("error getting storage class nonexistent-storage-class from API: storageclasses.storage.k8s.io \"nonexistent-storage-class\" not found"),
},
{
name: "when persistent volume claim's storage class is mapped to a nonexistent storage class, an error is returned",
pvOrPVC: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
name: "when persistent volume claim's storage class is mapped to a nonexistent storage class, an error is returned",
pvOrPvcOrSTS: builder.ForPersistentVolumeClaim("velero", "pvc-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "nonexistent-storage-class").
Result(),
wantErr: errors.New("error getting storage class nonexistent-storage-class from API: storageclasses.storage.k8s.io \"nonexistent-storage-class\" not found"),
},
{
name: "when statefulset's VolumeClaimTemplates has only one pvc, a valid mapping for a statefulset is applied correctly",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
Result(),
storageClass: builder.ForStorageClass("storageclass-2").Result(),
want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-2").Result(),
},
{
name: "when statefulset's VolumeClaimTemplates has more than one same pvc's storageClassName, a valid mapping for a statefulset is applied correctly",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1", "storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2", "storageclass-3", "storageclass-4").
Result(),
storageClass: builder.ForStorageClass("storageclass-2").Result(),
want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-2", "storageclass-2").Result(),
},
{
name: "when statefulset's VolumeClaimTemplates has more than one different pvc's storageClassName, a valid mapping for a statefulset is applied correctly",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1", "storageclass-2", "storageclass-3").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "storageclass-a", "storageclass-2", "storageclass-b", "storageclass-3", "storageclass-c").
Result(),
storageClassSlice: builder.ForStorageClassSlice("storageclass-a", "storageclass-b", "storageclass-c").SliceResult(),
want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-a", "storageclass-b", "storageclass-c").Result(),
},
{
name: "when no config map exists for the plugin, the statefulset item is returned as-is",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/some-other-plugin", "RestoreItemAction")).
Data("storageclass-1", "storageclass-2").
Result(),
want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
},
{
name: "when no storage class mappings exist in the plugin config map, the statefulset item is returned as-is",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Result(),
want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
},
{
name: "when persistent volume claim has no storage class, the statefulset item is returned as-is",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Result(),
want: builder.ForStatefulSet("velero", "sts-1").Result(),
},
{
name: "when statefulset's storage class has no mapping in the config map, the item is returned as-is",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-3", "storageclass-4").
Result(),
want: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
},
{
name: "when statefulset's storage class is mapped to a nonexistent storage class, an error is returned",
pvOrPvcOrSTS: builder.ForStatefulSet("velero", "sts-1").StorageClass("storageclass-1").Result(),
configMap: builder.ForConfigMap("velero", "change-storage-classs").
ObjectMeta(builder.WithLabels("velero.io/plugin-config", "true", "velero.io/change-storage-class", "RestoreItemAction")).
Data("storageclass-1", "nonexistent-storage-class").
@@ -161,7 +235,14 @@ func TestChangeStorageClassActionExecute(t *testing.T) {
require.NoError(t, err)
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvOrPVC)
if tc.storageClassSlice != nil {
for _, storageClass := range tc.storageClassSlice {
_, err := clientset.StorageV1().StorageClasses().Create(context.TODO(), storageClass, metav1.CreateOptions{})
require.NoError(t, err)
}
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(tc.pvOrPvcOrSTS)
require.NoError(t, err)
input := &velero.RestoreItemActionExecuteInput{
+1 -1
View File
@@ -1,5 +1,5 @@
/*
Copyright 2017 the Velero contributors.
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+2 -1
View File
@@ -1,5 +1,5 @@
/*
Copyright 2018 the Velero contributors.
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package restore
import (
+1 -1
View File
@@ -221,7 +221,7 @@ func userPriorityConfigMap() (*corev1.ConfigMap, error) {
return nil, errors.Wrap(err, "getting Kube client")
}
cm, err := kc.CoreV1().ConfigMaps("velero").Get(
cm, err := kc.CoreV1().ConfigMaps(fc.Namespace()).Get(
context.Background(),
"enableapigroupversions",
metav1.GetOptions{},
+101 -81
View File
@@ -56,6 +56,7 @@ import (
listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
"github.com/vmware-tanzu/velero/pkg/kuberesource"
"github.com/vmware-tanzu/velero/pkg/label"
"github.com/vmware-tanzu/velero/pkg/plugin/framework"
"github.com/vmware-tanzu/velero/pkg/plugin/velero"
"github.com/vmware-tanzu/velero/pkg/podexec"
"github.com/vmware-tanzu/velero/pkg/restic"
@@ -66,14 +67,6 @@ import (
"github.com/vmware-tanzu/velero/pkg/volume"
)
// These annotations are taken from the Kubernetes persistent volume/persistent volume claim controller.
// They cannot be directly importing because they are part of the kubernetes/kubernetes package, and importing that package is unsupported.
// Their values are well-known and slow changing. They're duplicated here as constants to provide compile-time checking.
// Originals can be found in kubernetes/kubernetes/pkg/controller/volume/persistentvolume/util/util.go.
const KubeAnnBindCompleted = "pv.kubernetes.io/bind-completed"
const KubeAnnBoundByController = "pv.kubernetes.io/bound-by-controller"
const KubeAnnDynamicallyProvisioned = "pv.kubernetes.io/provisioned-by"
type VolumeSnapshotterGetter interface {
GetVolumeSnapshotter(name string) (velero.VolumeSnapshotter, error)
}
@@ -96,6 +89,13 @@ type Restorer interface {
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result)
RestoreWithResolvers(
req Request,
restoreItemActionResolver framework.RestoreItemActionResolver,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result)
}
// kubernetesRestorer implements Restorer for restoring into a Kubernetes cluster.
@@ -161,6 +161,18 @@ func (kr *kubernetesRestorer) Restore(
actions []velero.RestoreItemAction,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result) {
resolver := framework.NewRestoreItemActionResolver(actions)
snapshotItemResolver := framework.NewItemSnapshotterResolver(nil)
return kr.RestoreWithResolvers(req, resolver, snapshotItemResolver, snapshotLocationLister, volumeSnapshotterGetter)
}
func (kr *kubernetesRestorer) RestoreWithResolvers(
req Request,
restoreItemActionResolver framework.RestoreItemActionResolver,
itemSnapshotterResolver framework.ItemSnapshotterResolver,
snapshotLocationLister listers.VolumeSnapshotLocationLister,
volumeSnapshotterGetter VolumeSnapshotterGetter,
) (Result, Result) {
// metav1.LabelSelectorAsSelector converts a nil LabelSelector to a
// Nothing Selector, i.e. a selector that matches nothing. We want
@@ -188,7 +200,12 @@ func (kr *kubernetesRestorer) Restore(
Includes(req.Restore.Spec.IncludedNamespaces...).
Excludes(req.Restore.Spec.ExcludedNamespaces...)
resolvedActions, err := resolveActions(actions, kr.discoveryHelper)
resolvedActions, err := restoreItemActionResolver.ResolveActions(kr.discoveryHelper)
if err != nil {
return Result{}, Result{Velero: []string{err.Error()}}
}
resolvedItemSnapshotterActions, err := itemSnapshotterResolver.ResolveActions(kr.discoveryHelper)
if err != nil {
return Result{}, Result{Velero: []string{err.Error()}}
}
@@ -251,7 +268,8 @@ func (kr *kubernetesRestorer) Restore(
dynamicFactory: kr.dynamicFactory,
fileSystem: kr.fileSystem,
namespaceClient: kr.namespaceClient,
actions: resolvedActions,
restoreItemActions: resolvedActions,
itemSnapshotterActions: resolvedItemSnapshotterActions,
volumeSnapshotterGetter: volumeSnapshotterGetter,
resticRestorer: resticRestorer,
resticErrs: make(chan error),
@@ -277,46 +295,6 @@ func (kr *kubernetesRestorer) Restore(
return restoreCtx.execute()
}
type resolvedAction struct {
velero.RestoreItemAction
resourceIncludesExcludes *collections.IncludesExcludes
namespaceIncludesExcludes *collections.IncludesExcludes
selector labels.Selector
}
func resolveActions(actions []velero.RestoreItemAction, helper discovery.Helper) ([]resolvedAction, error) {
var resolved []resolvedAction
for _, action := range actions {
resourceSelector, err := action.AppliesTo()
if err != nil {
return nil, err
}
resources := collections.GetResourceIncludesExcludes(helper, resourceSelector.IncludedResources, resourceSelector.ExcludedResources)
namespaces := collections.NewIncludesExcludes().Includes(resourceSelector.IncludedNamespaces...).Excludes(resourceSelector.ExcludedNamespaces...)
selector := labels.Everything()
if resourceSelector.LabelSelector != "" {
if selector, err = labels.Parse(resourceSelector.LabelSelector); err != nil {
return nil, err
}
}
res := resolvedAction{
RestoreItemAction: action,
resourceIncludesExcludes: resources,
namespaceIncludesExcludes: namespaces,
selector: selector,
}
resolved = append(resolved, res)
}
return resolved, nil
}
type restoreContext struct {
backup *velerov1api.Backup
backupReader io.Reader
@@ -331,7 +309,8 @@ type restoreContext struct {
dynamicFactory client.DynamicFactory
fileSystem filesystem.Interface
namespaceClient corev1.NamespaceInterface
actions []resolvedAction
restoreItemActions []framework.RestoreItemResolvedAction
itemSnapshotterActions []framework.ItemSnapshotterResolvedAction
volumeSnapshotterGetter VolumeSnapshotterGetter
resticRestorer restic.Restorer
resticWaitGroup sync.WaitGroup
@@ -713,23 +692,22 @@ func getNamespace(logger logrus.FieldLogger, path, remappedName string) *v1.Name
}
}
// TODO: this should be combined with DeleteItemActions at some point.
func (ctx *restoreContext) getApplicableActions(groupResource schema.GroupResource, namespace string) []resolvedAction {
var actions []resolvedAction
for _, action := range ctx.actions {
if !action.resourceIncludesExcludes.ShouldInclude(groupResource.String()) {
continue
func (ctx *restoreContext) getApplicableActions(groupResource schema.GroupResource, namespace string) []framework.RestoreItemResolvedAction {
var actions []framework.RestoreItemResolvedAction
for _, action := range ctx.restoreItemActions {
if action.ShouldUse(groupResource, namespace, nil, ctx.log) {
actions = append(actions, action)
}
}
return actions
}
if namespace != "" && !action.namespaceIncludesExcludes.ShouldInclude(namespace) {
continue
func (ctx *restoreContext) getApplicableItemSnapshotters(groupResource schema.GroupResource, namespace string) []framework.ItemSnapshotterResolvedAction {
var actions []framework.ItemSnapshotterResolvedAction
for _, action := range ctx.itemSnapshotterActions {
if action.ShouldUse(groupResource, namespace, nil, ctx.log) {
actions = append(actions, action)
}
if namespace == "" && !action.namespaceIncludesExcludes.IncludeEverything() {
continue
}
actions = append(actions, action)
}
return actions
@@ -842,12 +820,7 @@ func (ctx *restoreContext) crdAvailable(name string, crdClient client.Dynamic) (
if err != nil {
return true, err
}
// TODO: Due to upstream conversion issues in runtime.FromUnstructured,
// we use the unstructured object here. Once the upstream conversion
// functions are fixed, we should convert to the CRD types and use
// IsCRDReady.
available, err = kube.IsUnstructuredCRDReady(unstructuredCRD)
available, err = kube.IsCRDReady(unstructuredCRD)
if err != nil {
return true, err
}
@@ -1107,6 +1080,12 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
default:
ctx.log.Infof("Restoring persistent volume as-is because it doesn't have a snapshot and its reclaim policy is not Delete.")
// Check to see if the claimRef.namespace field needs to be remapped, and do so if necessary.
_, err = remapClaimRefNS(ctx, obj)
if err != nil {
errs.Add(namespace, err)
return warnings, errs
}
obj = resetVolumeBindingInfo(obj)
// We call the pvRestorer here to clear out the PV's claimRef.UID,
// so it can be re-claimed when its PVC is restored and gets a new UID.
@@ -1126,13 +1105,13 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
}
for _, action := range ctx.getApplicableActions(groupResource, namespace) {
if !action.selector.Matches(labels.Set(obj.GetLabels())) {
if !action.Selector.Matches(labels.Set(obj.GetLabels())) {
return warnings, errs
}
ctx.log.Infof("Executing item action for %v", &groupResource)
executeOutput, err := action.Execute(&velero.RestoreItemActionExecuteInput{
executeOutput, err := action.RestoreItemAction.Execute(&velero.RestoreItemActionExecuteInput{
Item: obj,
ItemFromBackup: itemFromBackup,
Restore: ctx.restore,
@@ -1237,7 +1216,12 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
ctx.log.Infof("Attempting to restore %s: %v", obj.GroupVersionKind().Kind, name)
createdObj, restoreErr := resourceClient.Create(obj)
if apierrors.IsAlreadyExists(restoreErr) {
isAlreadyExistsError, err := isAlreadyExistsError(ctx, obj, restoreErr, resourceClient)
if err != nil {
errs.Add(namespace, err)
return warnings, errs
}
if isAlreadyExistsError {
fromCluster, err := resourceClient.Get(name, metav1.GetOptions{})
if err != nil {
ctx.log.Infof("Error retrieving cluster version of %s: %v", kube.NamespaceAndName(obj), err)
@@ -1287,7 +1271,8 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
ctx.log.Infof("ServiceAccount %s successfully updated", kube.NamespaceAndName(obj))
}
default:
e := errors.Errorf("could not restore, %s. Warning: the in-cluster version is different than the backed-up version.", restoreErr)
e := errors.Errorf("could not restore, %s %q already exists. Warning: the in-cluster version is different than the backed-up version.",
obj.GetKind(), obj.GetName())
warnings.Add(namespace, e)
}
return warnings, errs
@@ -1334,6 +1319,45 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
return warnings, errs
}
func isAlreadyExistsError(ctx *restoreContext, obj *unstructured.Unstructured, err error, client client.Dynamic) (bool, error) {
if err == nil {
return false, nil
}
if apierrors.IsAlreadyExists(err) {
return true, nil
}
// The "invalid value error" or "internal error" rather than "already exists" error returns when restoring nodePort service in the following two cases:
// 1. For NodePort service, the service has nodePort preservation while the same nodePort service already exists. - Get invalid value error
// 2. For LoadBalancer service, the "healthCheckNodePort" already exists. - Get internal error
// If this is the case, the function returns true to avoid reporting error.
// Refer to https://github.com/vmware-tanzu/velero/issues/2308 for more details
if obj.GetKind() != "Service" {
return false, nil
}
statusErr, ok := err.(*apierrors.StatusError)
if !ok || statusErr.Status().Details == nil || len(statusErr.Status().Details.Causes) == 0 {
return false, nil
}
// make sure all the causes are "port allocated" error
for _, cause := range statusErr.Status().Details.Causes {
if !strings.Contains(cause.Message, "provided port is already allocated") {
return false, nil
}
}
// the "already allocated" error may caused by other services, check whether the expected service exists or not
if _, err = client.Get(obj.GetName(), metav1.GetOptions{}); err != nil {
if apierrors.IsNotFound(err) {
ctx.log.Debugf("Service %s not found", kube.NamespaceAndName(obj))
return false, nil
}
return false, errors.Wrapf(err, "Unable to get the service %s while checking the NodePort is already allocated error", kube.NamespaceAndName(obj))
}
ctx.log.Infof("Service %s exists, ignore the provided port is already allocated error", kube.NamespaceAndName(obj))
return true, nil
}
// shouldRenamePV returns a boolean indicating whether a persistent volume should
// be given a new name before being restored, or an error if this cannot be determined.
// A persistent volume will be given a new name if and only if (a) a PV with the
@@ -1538,14 +1562,10 @@ func resetVolumeBindingInfo(obj *unstructured.Unstructured) *unstructured.Unstru
// Upon restore, this new PV will look like a statically provisioned, manually-
// bound volume rather than one bound by the controller, so remove the annotation
// that signals that a controller bound it.
delete(annotations, KubeAnnBindCompleted)
delete(annotations, kube.KubeAnnBindCompleted)
// Remove the annotation that signals that the PV is already bound; we want
// the PV(C) controller to take the two objects and bind them again.
delete(annotations, KubeAnnBoundByController)
// Remove the provisioned-by annotation which signals that the persistent
// volume was dynamically provisioned; it is now statically provisioned.
delete(annotations, KubeAnnDynamicallyProvisioned)
delete(annotations, kube.KubeAnnBoundByController)
// GetAnnotations returns a copy, so we have to set them again.
obj.SetAnnotations(annotations)
+262 -15
View File
@@ -30,6 +30,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -51,6 +52,7 @@ import (
resticmocks "github.com/vmware-tanzu/velero/pkg/restic/mocks"
"github.com/vmware-tanzu/velero/pkg/test"
testutil "github.com/vmware-tanzu/velero/pkg/test"
"github.com/vmware-tanzu/velero/pkg/util/kube"
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
"github.com/vmware-tanzu/velero/pkg/volume"
)
@@ -544,7 +546,7 @@ func TestRestoreResourceFiltering(t *testing.T) {
}
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
@@ -625,7 +627,7 @@ func TestRestoreNamespaceMapping(t *testing.T) {
}
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
@@ -707,7 +709,7 @@ func TestRestoreResourcePriorities(t *testing.T) {
}
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
@@ -784,7 +786,7 @@ func TestInvalidTarballContents(t *testing.T) {
}
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
@@ -999,7 +1001,7 @@ func TestRestoreItems(t *testing.T) {
}
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
@@ -1811,6 +1813,8 @@ func TestRestorePersistentVolumes(t *testing.T) {
volumeSnapshotLocations []*velerov1api.VolumeSnapshotLocation
volumeSnapshotterGetter volumeSnapshotterGetter
want []*test.APIResource
wantError bool
wantWarning bool
}{
{
name: "when a PV with a reclaim policy of delete has no snapshot and does not exist in-cluster, it does not get restored, and its PVC gets reset for dynamic provisioning",
@@ -2191,6 +2195,95 @@ func TestRestorePersistentVolumes(t *testing.T) {
),
},
},
{
name: "when a PV without a snapshot is used by a PVC in a namespace that's being remapped, and the original PV exists in-cluster, the PV is not replaced and there is a restore warning",
restore: defaultRestore().NamespaceMappings("source-ns", "target-ns").Result(),
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).
AddItems(
"persistentvolumes",
builder.ForPersistentVolume("source-pv").
//ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).
AWSEBSVolumeID("source-volume").
ClaimRef("source-ns", "pvc-1").
Result(),
).
AddItems(
"persistentvolumeclaims",
builder.ForPersistentVolumeClaim("source-ns", "pvc-1").VolumeName("source-pv").Result(),
).
Done(),
apiResources: []*test.APIResource{
test.PVs(
builder.ForPersistentVolume("source-pv").
//ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).
AWSEBSVolumeID("source-volume").
ClaimRef("source-ns", "pvc-1").
Result(),
),
test.PVCs(),
},
want: []*test.APIResource{
test.PVs(
builder.ForPersistentVolume("source-pv").
AWSEBSVolumeID("source-volume").
ClaimRef("source-ns", "pvc-1").
Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("target-ns", "pvc-1").
ObjectMeta(
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
).
VolumeName("source-pv").
Result(),
),
},
wantWarning: true,
},
{
name: "when a PV without a snapshot is used by a PVC in a namespace that's being remapped, and the original PV does not exist in-cluster, the PV is not renamed",
restore: defaultRestore().NamespaceMappings("source-ns", "target-ns").Result(),
backup: defaultBackup().Result(),
tarball: test.NewTarWriter(t).
AddItems(
"persistentvolumes",
builder.ForPersistentVolume("source-pv").
AWSEBSVolumeID("source-volume").
ClaimRef("source-ns", "pvc-1").
Result(),
).
AddItems(
"persistentvolumeclaims",
builder.ForPersistentVolumeClaim("source-ns", "pvc-1").VolumeName("source-pv").Result(),
).
Done(),
apiResources: []*test.APIResource{
test.PVs(),
test.PVCs(),
},
want: []*test.APIResource{
test.PVs(
builder.ForPersistentVolume("source-pv").
//ReclaimPolicy(corev1api.PersistentVolumeReclaimRetain).
ObjectMeta(
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
).
// the namespace for this PV's claimRef should be the one that the PVC was remapped into.
ClaimRef("target-ns", "pvc-1").
AWSEBSVolumeID("source-volume").
Result(),
),
test.PVCs(
builder.ForPersistentVolumeClaim("target-ns", "pvc-1").
ObjectMeta(
builder.WithLabels("velero.io/backup-name", "backup-1", "velero.io/restore-name", "restore-1"),
).
VolumeName("source-pv").
Result(),
),
},
},
{
name: "when a PV is renamed and the original PV does not exist in-cluster, the PV should be renamed",
restore: defaultRestore().NamespaceMappings("source-ns", "target-ns").Result(),
@@ -2418,12 +2511,21 @@ func TestRestorePersistentVolumes(t *testing.T) {
}
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
vslInformer.Lister(),
tc.volumeSnapshotterGetter,
)
assertEmptyResults(t, warnings, errs)
if tc.wantWarning {
assertNonEmptyResults(t, "warning", warnings)
} else {
assertEmptyResults(t, warnings)
}
if tc.wantError {
assertNonEmptyResults(t, "error", errs)
} else {
assertEmptyResults(t, errs)
}
assertAPIContents(t, h, wantIDs)
assertRestoredItems(t, h, tc.want)
})
@@ -2545,7 +2647,7 @@ func TestRestoreWithRestic(t *testing.T) {
warnings, errs := h.restorer.Restore(
data,
nil, // actions
nil, // restoreItemActions
nil, // snapshot location lister
nil, // volume snapshotter getter
)
@@ -2804,6 +2906,17 @@ func assertEmptyResults(t *testing.T, res ...Result) {
}
}
func assertNonEmptyResults(t *testing.T, typeMsg string, res ...Result) {
t.Helper()
total := 0
for _, r := range res {
total += len(r.Cluster)
total += len(r.Namespaces)
total += len(r.Velero)
}
assert.Greater(t, total, 0, "Expected at least one "+typeMsg)
}
type harness struct {
*test.APIServer
@@ -2875,9 +2988,9 @@ func Test_resetVolumeBindingInfo(t *testing.T) {
name: "PVs that are bound have their binding and dynamic provisioning annotations removed",
obj: NewTestUnstructured().WithMetadataField("kind", "persistentVolume").
WithName("pv-1").WithAnnotations(
KubeAnnBindCompleted,
KubeAnnBoundByController,
KubeAnnDynamicallyProvisioned,
kube.KubeAnnBindCompleted,
kube.KubeAnnBoundByController,
kube.KubeAnnDynamicallyProvisioned,
).WithSpecField("claimRef", map[string]interface{}{
"namespace": "ns-1",
"name": "pvc-1",
@@ -2885,7 +2998,7 @@ func Test_resetVolumeBindingInfo(t *testing.T) {
"resourceVersion": "1"}).Unstructured,
expected: NewTestUnstructured().WithMetadataField("kind", "persistentVolume").
WithName("pv-1").
WithAnnotations().
WithAnnotations(kube.KubeAnnDynamicallyProvisioned).
WithSpecField("claimRef", map[string]interface{}{
"namespace": "ns-1", "name": "pvc-1"}).Unstructured,
},
@@ -2893,9 +3006,8 @@ func Test_resetVolumeBindingInfo(t *testing.T) {
name: "PVCs that are bound have their binding annotations removed, but the volume name stays",
obj: NewTestUnstructured().WithMetadataField("kind", "persistentVolumeClaim").
WithName("pvc-1").WithAnnotations(
KubeAnnBindCompleted,
KubeAnnBoundByController,
KubeAnnDynamicallyProvisioned,
kube.KubeAnnBindCompleted,
kube.KubeAnnBoundByController,
).WithSpecField("volumeName", "pv-1").Unstructured,
expected: NewTestUnstructured().WithMetadataField("kind", "persistentVolumeClaim").
WithName("pvc-1").WithAnnotations().
@@ -2910,3 +3022,138 @@ func Test_resetVolumeBindingInfo(t *testing.T) {
})
}
}
func TestIsAlreadyExistsError(t *testing.T) {
tests := []struct {
name string
apiResource *test.APIResource
obj *unstructured.Unstructured
err error
expected bool
}{
{
name: "The input error is IsAlreadyExists error",
err: apierrors.NewAlreadyExists(schema.GroupResource{}, ""),
expected: true,
},
{
name: "The input obj isn't service",
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Pod",
},
},
expected: false,
},
{
name: "The StatusError contains no causes",
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Service",
},
},
err: &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonInvalid,
},
},
expected: false,
},
{
name: "The causes contains not only port already allocated error",
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Service",
},
},
err: &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonInvalid,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{Message: "provided port is already allocated"},
{Message: "other error"},
},
},
},
},
expected: false,
},
{
name: "Get already allocated error but the service doesn't exist",
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Service",
"metadata": map[string]interface{}{
"namespace": "default",
"name": "test",
},
},
},
err: &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonInvalid,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{Message: "provided port is already allocated"},
},
},
},
},
expected: false,
},
{
name: "Get already allocated error and the service exists",
apiResource: test.Services(
builder.ForService("default", "test").Result(),
),
obj: &unstructured.Unstructured{
Object: map[string]interface{}{
"kind": "Service",
"metadata": map[string]interface{}{
"namespace": "default",
"name": "test",
},
},
},
err: &apierrors.StatusError{
ErrStatus: metav1.Status{
Reason: metav1.StatusReasonInvalid,
Details: &metav1.StatusDetails{
Causes: []metav1.StatusCause{
{Message: "provided port is already allocated"},
},
},
},
},
expected: true,
},
}
for _, test := range tests {
h := newHarness(t)
ctx := &restoreContext{
log: h.log,
dynamicFactory: client.NewDynamicFactory(h.DynamicClient),
namespaceClient: h.KubeClient.CoreV1().Namespaces(),
}
if test.apiResource != nil {
h.AddItems(t, test.apiResource)
}
client, err := ctx.dynamicFactory.ClientForGroupVersionResource(
schema.GroupVersion{Group: "", Version: "v1"},
metav1.APIResource{Name: "services"},
"default",
)
require.NoError(t, err)
t.Run(test.name, func(t *testing.T) {
result, err := isAlreadyExistsError(ctx, test.obj, test.err, client)
require.NoError(t, err)
assert.Equal(t, test.expected, result)
})
}
}

Some files were not shown because too many files have changed in this diff Show More