mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-09-19 22:44:21 +00:00
Merge remote-tracking branch 'upstream/main'
Signed-off-by: MatthieuFin <matthieu2717@gmail.com>
This commit is contained in:
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 "", ""
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>`" + `
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ var kindToResource = map[string]string{
|
||||
"Deployment": "deployments",
|
||||
"DaemonSet": "daemonsets",
|
||||
"Secret": "secrets",
|
||||
"ConfigMap": "configmaps",
|
||||
"BackupStorageLocation": "backupstoragelocations",
|
||||
"VolumeSnapshotLocation": "volumesnapshotlocations",
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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...),
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user