store volume snapshot info as JSON in backup storage

Signed-off-by: Steve Kriss <steve@heptio.com>
This commit is contained in:
Steve Kriss
2018-10-12 11:55:02 -06:00
parent e24248e07a
commit da9ed38c63
26 changed files with 302 additions and 865 deletions

View File

@@ -177,21 +177,6 @@ spec:
plural: volumesnapshotlocations
kind: VolumeSnapshotLocation
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: volumesnapshots.ark.heptio.com
labels:
component: ark
spec:
group: ark.heptio.com
version: v1
scope: Namespaced
names:
plural: volumesnapshots
kind: VolumeSnapshot
---
apiVersion: v1
kind: Namespace

View File

@@ -70,7 +70,6 @@ func CustomResources() map[string]typeInfo {
"ResticRepository": newTypeInfo("resticrepositories", &ResticRepository{}, &ResticRepositoryList{}),
"BackupStorageLocation": newTypeInfo("backupstoragelocations", &BackupStorageLocation{}, &BackupStorageLocationList{}),
"VolumeSnapshotLocation": newTypeInfo("volumesnapshotlocations", &VolumeSnapshotLocation{}, &VolumeSnapshotLocationList{}),
"VolumeSnapshot": newTypeInfo("volumesnapshots", &VolumeSnapshot{}, &VolumeSnapshotList{}),
}
}

View File

@@ -1,89 +0,0 @@
/*
Copyright 2018 the Heptio Ark 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 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// VolumeSnapshot represents a snapshot of a persistent volume
type VolumeSnapshot struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata"`
Spec VolumeSnapshotSpec `json:"spec"`
Status VolumeSnapshotStatus `json:"status"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// VolumeSnapshotList is a list of VolumeSnapshots.
type VolumeSnapshotList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []VolumeSnapshot `json:"items"`
}
// VolumeSnapshotSpec defines the specification for an Ark VolumeSnapshot.
type VolumeSnapshotSpec struct {
// Type is the type of the disk/volume in the cloud provider
// API.
Type string `json:"type"`
// AvailabilityZone is the where the volume is provisioned
// in the cloud provider.
AvailabilityZone string `json:"availabilityZone,omitempty"`
// Iops is the optional value of provisioned IOPS for the
// disk/volume in the cloud provider API.
Iops *int64 `json:"iops,omitempty"`
// Backup is a string containing the name of name of the Ark backup this snapshot is associated with.
Backup string `json:"backup"`
// Location is the name of the VolumeSnapshotLocation where this snapshot is stored.
Location string `json:"location"`
}
// VolumeSnapshotStatus captures the current status of an Ark VolumeSnapshot.
type VolumeSnapshotStatus struct {
// SnapshotID is the UUID generated by Ark.
SnapshotID string `json:"snapshotID"`
// ProviderSnapshotID is the ID of the snapshot taken in the cloud
// provider API of this volume.
ProviderSnapshotID string `json:"providerSnapshotID"`
// Phase is the current state of the VolumeSnapshot.
Phase VolumeSnapshotPhase `json:"phase,omitempty"`
}
// VolumeSnapshotPhase is the lifecyle phase of an Ark VolumeSnapshot.
type VolumeSnapshotPhase string
const (
// VolumeSnapshotPhaseNew means the volume snapshot has been created but not
// yet processed by the VolumeSnapshotController.
VolumeSnapshotPhaseNew VolumeSnapshotPhase = "New"
// VolumeSnapshotPhaseCompleted means the volume snapshot was successfully created and can be restored from..
VolumeSnapshotPhaseCompleted VolumeSnapshotPhase = "Completed"
// VolumeSnapshotPhaseFailed means the volume snapshot was unable to execute.
VolumeSnapshotPhaseFailed VolumeSnapshotPhase = "Failed"
)

View File

@@ -1376,67 +1376,6 @@ func (in *VolumeBackupInfo) DeepCopy() *VolumeBackupInfo {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeSnapshot) DeepCopyInto(out *VolumeSnapshot) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Status = in.Status
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSnapshot.
func (in *VolumeSnapshot) DeepCopy() *VolumeSnapshot {
if in == nil {
return nil
}
out := new(VolumeSnapshot)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *VolumeSnapshot) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeSnapshotList) DeepCopyInto(out *VolumeSnapshotList) {
*out = *in
out.TypeMeta = in.TypeMeta
out.ListMeta = in.ListMeta
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]VolumeSnapshot, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSnapshotList.
func (in *VolumeSnapshotList) DeepCopy() *VolumeSnapshotList {
if in == nil {
return nil
}
out := new(VolumeSnapshotList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *VolumeSnapshotList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeSnapshotLocation) DeepCopyInto(out *VolumeSnapshotLocation) {
*out = *in
@@ -1536,44 +1475,3 @@ func (in *VolumeSnapshotLocationStatus) DeepCopy() *VolumeSnapshotLocationStatus
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeSnapshotSpec) DeepCopyInto(out *VolumeSnapshotSpec) {
*out = *in
if in.Iops != nil {
in, out := &in.Iops, &out.Iops
if *in == nil {
*out = nil
} else {
*out = new(int64)
**out = **in
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSnapshotSpec.
func (in *VolumeSnapshotSpec) DeepCopy() *VolumeSnapshotSpec {
if in == nil {
return nil
}
out := new(VolumeSnapshotSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *VolumeSnapshotStatus) DeepCopyInto(out *VolumeSnapshotStatus) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeSnapshotStatus.
func (in *VolumeSnapshotStatus) DeepCopy() *VolumeSnapshotStatus {
if in == nil {
return nil
}
out := new(VolumeSnapshotStatus)
in.DeepCopyInto(out)
return out
}

View File

@@ -40,6 +40,7 @@ import (
"github.com/heptio/ark/pkg/kuberesource"
"github.com/heptio/ark/pkg/podexec"
"github.com/heptio/ark/pkg/restic"
"github.com/heptio/ark/pkg/volume"
)
type itemBackupperFactory interface {
@@ -401,18 +402,13 @@ func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, log log
return errors.WithStack(err)
}
name := metadata.GetName()
var pvFailureDomainZone string
labels := metadata.GetLabels()
if labels[zoneLabel] != "" {
pvFailureDomainZone = labels[zoneLabel]
} else {
pvFailureDomainZone := metadata.GetLabels()[zoneLabel]
if pvFailureDomainZone == "" {
log.Infof("label %q is not present on PersistentVolume", zoneLabel)
}
var (
volumeID string
volumeID, location string
blockStore cloudprovider.BlockStore
)
@@ -439,6 +435,7 @@ func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, log log
log.Infof("Got volume ID for persistent volume")
blockStore = bs
location = snapshotLocation.Name
break
}
@@ -454,30 +451,45 @@ func (ib *defaultItemBackupper) takePVSnapshot(obj runtime.Unstructured, log log
"ark.heptio.com/pv": metadata.GetName(),
}
log.Info("Snapshotting PersistentVolume")
snapshotID, err := blockStore.CreateSnapshot(volumeID, pvFailureDomainZone, tags)
if err != nil {
// log+error on purpose - log goes to the per-backup log file, error goes to the backup
log.WithError(err).Error("error creating snapshot")
return errors.WithMessage(err, "error creating snapshot")
}
log.Info("Getting volume information")
volumeType, iops, err := blockStore.GetVolumeInfo(volumeID, pvFailureDomainZone)
if err != nil {
log.WithError(err).Error("error getting volume info")
return errors.WithMessage(err, "error getting volume info")
}
if ib.backupRequest.Status.VolumeBackups == nil {
ib.backupRequest.Status.VolumeBackups = make(map[string]*api.VolumeBackupInfo)
log.Info("Snapshotting PersistentVolume")
snapshot := volumeSnapshot(ib.backupRequest.Backup, volumeID, volumeType, pvFailureDomainZone, location, iops)
var errs []error
snapshotID, err := blockStore.CreateSnapshot(snapshot.Spec.ProviderVolumeID, snapshot.Spec.VolumeAZ, tags)
if err != nil {
log.WithError(err).Error("error creating snapshot")
errs = append(errs, errors.Wrap(err, "error taking snapshot of volume"))
snapshot.Status.Phase = volume.SnapshotPhaseFailed
} else {
snapshot.Status.Phase = volume.SnapshotPhaseCompleted
snapshot.Status.ProviderSnapshotID = snapshotID
}
ib.backupRequest.VolumeSnapshots = append(ib.backupRequest.VolumeSnapshots, snapshot)
// nil errors are automatically removed
return kubeerrs.NewAggregate(errs)
}
ib.backupRequest.Status.VolumeBackups[name] = &api.VolumeBackupInfo{
SnapshotID: snapshotID,
Type: volumeType,
Iops: iops,
AvailabilityZone: pvFailureDomainZone,
func volumeSnapshot(backup *api.Backup, volumeID, volumeType, az, location string, iops *int64) *volume.Snapshot {
return &volume.Snapshot{
Spec: volume.SnapshotSpec{
BackupName: backup.Name,
BackupUID: string(backup.UID),
Location: location,
ProviderVolumeID: volumeID,
VolumeType: volumeType,
VolumeAZ: az,
VolumeIOPS: iops,
},
Status: volume.SnapshotStatus{
Phase: volume.SnapshotPhaseNew,
},
}
return nil
}

View File

@@ -524,18 +524,16 @@ func TestBackupItemNoSkips(t *testing.T) {
if test.snapshottableVolumes != nil {
require.Equal(t, len(test.snapshottableVolumes), len(blockStore.SnapshotsTaken))
var expectedBackups []api.VolumeBackupInfo
for _, vbi := range test.snapshottableVolumes {
expectedBackups = append(expectedBackups, vbi)
}
var actualBackups []api.VolumeBackupInfo
for _, vbi := range backup.Status.VolumeBackups {
actualBackups = append(actualBackups, *vbi)
}
if len(test.snapshottableVolumes) > 0 {
require.Len(t, backup.VolumeSnapshots, 1)
snapshot := backup.VolumeSnapshots[0]
assert.Equal(t, expectedBackups, actualBackups)
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].SnapshotID, snapshot.Status.ProviderSnapshotID)
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].Type, snapshot.Spec.VolumeType)
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].Iops, snapshot.Spec.VolumeIOPS)
assert.Equal(t, test.snapshottableVolumes["vol-abc123"].AvailabilityZone, snapshot.Spec.VolumeAZ)
}
if test.expectedTrackedPVCs != nil {
@@ -718,7 +716,6 @@ func TestTakePVSnapshot(t *testing.T) {
expectError bool
expectedVolumeID string
expectedSnapshotsTaken int
existingVolumeBackups map[string]*v1.VolumeBackupInfo
volumeInfo map[string]v1.VolumeBackupInfo
}{
{
@@ -756,21 +753,6 @@ func TestTakePVSnapshot(t *testing.T) {
"vol-abc123": {Type: "io1", Iops: &iops, SnapshotID: "snap-1", AvailabilityZone: "us-east-1c"},
},
},
{
name: "preexisting volume backup info in backup status",
snapshotEnabled: true,
pv: `{"apiVersion": "v1", "kind": "PersistentVolume", "metadata": {"name": "mypv"}, "spec": {"gcePersistentDisk": {"pdName": "pd-abc123"}}}`,
expectError: false,
expectedSnapshotsTaken: 1,
expectedVolumeID: "pd-abc123",
ttl: 5 * time.Minute,
existingVolumeBackups: map[string]*v1.VolumeBackupInfo{
"anotherpv": {SnapshotID: "anothersnap"},
},
volumeInfo: map[string]v1.VolumeBackupInfo{
"pd-abc123": {Type: "gp", SnapshotID: "snap-1"},
},
},
{
name: "create snapshot error",
snapshotEnabled: true,
@@ -803,9 +785,6 @@ func TestTakePVSnapshot(t *testing.T) {
SnapshotVolumes: &test.snapshotEnabled,
TTL: metav1.Duration{Duration: test.ttl},
},
Status: v1.BackupStatus{
VolumeBackups: test.existingVolumeBackups,
},
}
blockStore := &arktest.FakeBlockStore{
@@ -843,29 +822,18 @@ func TestTakePVSnapshot(t *testing.T) {
return
}
expectedVolumeBackups := test.existingVolumeBackups
if expectedVolumeBackups == nil {
expectedVolumeBackups = make(map[string]*v1.VolumeBackupInfo)
}
// we should have one snapshot taken exactly
// we should have exactly one snapshot taken
require.Equal(t, test.expectedSnapshotsTaken, blockStore.SnapshotsTaken.Len())
if test.expectedSnapshotsTaken > 0 {
// the snapshotID should be the one in the entry in blockStore.SnapshottableVolumes
// for the volume we ran the test for
require.Len(t, ib.backupRequest.VolumeSnapshots, 1)
snapshot := ib.backupRequest.VolumeSnapshots[0]
snapshotID, _ := blockStore.SnapshotsTaken.PopAny()
expectedVolumeBackups["mypv"] = &v1.VolumeBackupInfo{
SnapshotID: snapshotID,
Type: test.volumeInfo[test.expectedVolumeID].Type,
Iops: test.volumeInfo[test.expectedVolumeID].Iops,
AvailabilityZone: test.volumeInfo[test.expectedVolumeID].AvailabilityZone,
}
if e, a := expectedVolumeBackups, backup.Status.VolumeBackups; !reflect.DeepEqual(e, a) {
t.Errorf("backup.status.VolumeBackups: expected %v, got %v", e, a)
}
assert.Equal(t, snapshotID, snapshot.Status.ProviderSnapshotID)
assert.Equal(t, test.volumeInfo[test.expectedVolumeID].Type, snapshot.Spec.VolumeType)
assert.Equal(t, test.volumeInfo[test.expectedVolumeID].Iops, snapshot.Spec.VolumeIOPS)
assert.Equal(t, test.volumeInfo[test.expectedVolumeID].AvailabilityZone, snapshot.Spec.VolumeAZ)
}
})
}

View File

@@ -3,6 +3,7 @@ package backup
import (
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/util/collections"
"github.com/heptio/ark/pkg/volume"
)
// Request is a request for a backup, with all references to other objects
@@ -16,4 +17,6 @@ type Request struct {
ResourceIncludesExcludes *collections.IncludesExcludes
ResourceHooks []resourceHook
ResolvedActions []resolvedAction
VolumeSnapshots []*volume.Snapshot
}

View File

@@ -448,6 +448,7 @@ func TestBackupResourceCohabitation(t *testing.T) {
mock.Anything, // restic backupper
mock.Anything, // pvc snapshot tracker
nil,
mock.Anything,
).Return(itemBackupper)
client := &arktest.FakeDynamicClient{}

View File

@@ -385,53 +385,76 @@ func (c *backupController) runBackup(backup *pkgbackup.Request) error {
// Do the actual backup
if err := c.backupper.Backup(log, backup, backupFile, actions, pluginManager); err != nil {
errs = append(errs, err)
backup.Status.Phase = api.BackupPhaseFailed
} else {
backup.Status.Phase = api.BackupPhaseCompleted
}
// Mark completion timestamp before serializing and uploading.
// Otherwise, the JSON file in object storage has a CompletionTimestamp of 'null'.
backup.Status.CompletionTimestamp.Time = c.clock.Now()
var backupJSONToUpload, backupFileToUpload io.Reader
backupJSON := new(bytes.Buffer)
if err := encode.EncodeTo(backup.Backup, "json", backupJSON); err != nil {
errs = append(errs, errors.Wrap(err, "error encoding backup"))
} else {
// Only upload the json and backup tarball if encoding to json succeeded.
backupJSONToUpload = backupJSON
backupFileToUpload = backupFile
}
var backupSizeBytes int64
if backupFileStat, err := backupFile.Stat(); err != nil {
errs = append(errs, errors.Wrap(err, "error getting file info"))
} else {
backupSizeBytes = backupFileStat.Size()
}
if err := gzippedLogFile.Close(); err != nil {
c.logger.WithError(err).Error("error closing gzippedLogFile")
}
if err := backupStore.PutBackup(backup.Name, backupJSONToUpload, backupFileToUpload, logFile); err != nil {
errs = append(errs, err)
}
// Mark completion timestamp before serializing and uploading.
// Otherwise, the JSON file in object storage has a CompletionTimestamp of 'null'.
backup.Status.CompletionTimestamp.Time = c.clock.Now()
backupScheduleName := backup.GetLabels()["ark-schedule"]
c.metrics.SetBackupTarballSizeBytesGauge(backupScheduleName, backupSizeBytes)
backupDuration := backup.Status.CompletionTimestamp.Time.Sub(backup.Status.StartTimestamp.Time)
backupDurationSeconds := float64(backupDuration / time.Second)
c.metrics.RegisterBackupDuration(backupScheduleName, backupDurationSeconds)
errs = append(errs, persistBackup(backup, backupFile, logFile, backupStore, c.logger)...)
errs = append(errs, recordBackupMetrics(backup.Backup, backupFile, c.metrics))
log.Info("Backup completed")
return kerrors.NewAggregate(errs)
}
func recordBackupMetrics(backup *api.Backup, backupFile *os.File, serverMetrics *metrics.ServerMetrics) error {
backupScheduleName := backup.GetLabels()["ark-schedule"]
var backupSizeBytes int64
var err error
if backupFileStat, err := backupFile.Stat(); err != nil {
err = errors.Wrap(err, "error getting file info")
} else {
backupSizeBytes = backupFileStat.Size()
}
serverMetrics.SetBackupTarballSizeBytesGauge(backupScheduleName, backupSizeBytes)
backupDuration := backup.Status.CompletionTimestamp.Time.Sub(backup.Status.StartTimestamp.Time)
backupDurationSeconds := float64(backupDuration / time.Second)
serverMetrics.RegisterBackupDuration(backupScheduleName, backupDurationSeconds)
return err
}
func persistBackup(backup *pkgbackup.Request, backupContents, backupLog *os.File, backupStore persistence.BackupStore, log logrus.FieldLogger) []error {
errs := []error{}
backupJSON := new(bytes.Buffer)
if err := encode.EncodeTo(backup.Backup, "json", backupJSON); err != nil {
errs = append(errs, errors.Wrap(err, "error encoding backup"))
}
volumeSnapshots := new(bytes.Buffer)
gzw := gzip.NewWriter(volumeSnapshots)
defer gzw.Close()
if err := json.NewEncoder(gzw).Encode(backup.VolumeSnapshots); err != nil {
errs = append(errs, errors.Wrap(err, "error encoding list of volume snapshots"))
}
if len(errs) > 0 {
// Don't upload the JSON files or backup tarball if encoding to json fails.
backupJSON = nil
backupContents = nil
volumeSnapshots = nil
}
if err := backupStore.PutBackup(backup.Name, backupJSON, backupContents, backupLog, volumeSnapshots); err != nil {
errs = append(errs, err)
}
return errs
}
func closeAndRemoveFile(file *os.File, log logrus.FieldLogger) {
if err := file.Close(); err != nil {
log.WithError(err).WithField("file", file.Name()).Error("error closing file")

View File

@@ -320,7 +320,7 @@ func TestProcessBackupCompletions(t *testing.T) {
completionTimestampIsPresent := func(buf *bytes.Buffer) bool {
return strings.Contains(buf.String(), `"completionTimestamp": "2006-01-02T22:04:05Z"`)
}
backupStore.On("PutBackup", test.backup.Name, mock.MatchedBy(completionTimestampIsPresent), mock.Anything, mock.Anything).Return(nil)
backupStore.On("PutBackup", test.backup.Name, mock.MatchedBy(completionTimestampIsPresent), mock.Anything, mock.Anything, mock.Anything).Return(nil)
// add the test's backup to the informer/lister store
require.NotNil(t, test.backup)

View File

@@ -198,8 +198,10 @@ func (c *backupSyncController) run() {
switch {
case err != nil && kuberrs.IsAlreadyExists(err):
log.Debug("Backup already exists in cluster")
continue
case err != nil && !kuberrs.IsAlreadyExists(err):
log.WithError(errors.WithStack(err)).Error("Error syncing backup into cluster")
continue
default:
log.Debug("Synced backup into cluster")
}

View File

@@ -37,7 +37,6 @@ type ArkV1Interface interface {
ResticRepositoriesGetter
RestoresGetter
SchedulesGetter
VolumeSnapshotsGetter
VolumeSnapshotLocationsGetter
}
@@ -86,10 +85,6 @@ func (c *ArkV1Client) Schedules(namespace string) ScheduleInterface {
return newSchedules(c, namespace)
}
func (c *ArkV1Client) VolumeSnapshots(namespace string) VolumeSnapshotInterface {
return newVolumeSnapshots(c, namespace)
}
func (c *ArkV1Client) VolumeSnapshotLocations(namespace string) VolumeSnapshotLocationInterface {
return newVolumeSnapshotLocations(c, namespace)
}

View File

@@ -68,10 +68,6 @@ func (c *FakeArkV1) Schedules(namespace string) v1.ScheduleInterface {
return &FakeSchedules{c, namespace}
}
func (c *FakeArkV1) VolumeSnapshots(namespace string) v1.VolumeSnapshotInterface {
return &FakeVolumeSnapshots{c, namespace}
}
func (c *FakeArkV1) VolumeSnapshotLocations(namespace string) v1.VolumeSnapshotLocationInterface {
return &FakeVolumeSnapshotLocations{c, namespace}
}

View File

@@ -1,140 +0,0 @@
/*
Copyright 2018 the Heptio Ark 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package fake
import (
ark_v1 "github.com/heptio/ark/pkg/apis/ark/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
labels "k8s.io/apimachinery/pkg/labels"
schema "k8s.io/apimachinery/pkg/runtime/schema"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
testing "k8s.io/client-go/testing"
)
// FakeVolumeSnapshots implements VolumeSnapshotInterface
type FakeVolumeSnapshots struct {
Fake *FakeArkV1
ns string
}
var volumesnapshotsResource = schema.GroupVersionResource{Group: "ark.heptio.com", Version: "v1", Resource: "volumesnapshots"}
var volumesnapshotsKind = schema.GroupVersionKind{Group: "ark.heptio.com", Version: "v1", Kind: "VolumeSnapshot"}
// Get takes name of the volumeSnapshot, and returns the corresponding volumeSnapshot object, and an error if there is any.
func (c *FakeVolumeSnapshots) Get(name string, options v1.GetOptions) (result *ark_v1.VolumeSnapshot, err error) {
obj, err := c.Fake.
Invokes(testing.NewGetAction(volumesnapshotsResource, c.ns, name), &ark_v1.VolumeSnapshot{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.VolumeSnapshot), err
}
// List takes label and field selectors, and returns the list of VolumeSnapshots that match those selectors.
func (c *FakeVolumeSnapshots) List(opts v1.ListOptions) (result *ark_v1.VolumeSnapshotList, err error) {
obj, err := c.Fake.
Invokes(testing.NewListAction(volumesnapshotsResource, volumesnapshotsKind, c.ns, opts), &ark_v1.VolumeSnapshotList{})
if obj == nil {
return nil, err
}
label, _, _ := testing.ExtractFromListOptions(opts)
if label == nil {
label = labels.Everything()
}
list := &ark_v1.VolumeSnapshotList{ListMeta: obj.(*ark_v1.VolumeSnapshotList).ListMeta}
for _, item := range obj.(*ark_v1.VolumeSnapshotList).Items {
if label.Matches(labels.Set(item.Labels)) {
list.Items = append(list.Items, item)
}
}
return list, err
}
// Watch returns a watch.Interface that watches the requested volumeSnapshots.
func (c *FakeVolumeSnapshots) Watch(opts v1.ListOptions) (watch.Interface, error) {
return c.Fake.
InvokesWatch(testing.NewWatchAction(volumesnapshotsResource, c.ns, opts))
}
// Create takes the representation of a volumeSnapshot and creates it. Returns the server's representation of the volumeSnapshot, and an error, if there is any.
func (c *FakeVolumeSnapshots) Create(volumeSnapshot *ark_v1.VolumeSnapshot) (result *ark_v1.VolumeSnapshot, err error) {
obj, err := c.Fake.
Invokes(testing.NewCreateAction(volumesnapshotsResource, c.ns, volumeSnapshot), &ark_v1.VolumeSnapshot{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.VolumeSnapshot), err
}
// Update takes the representation of a volumeSnapshot and updates it. Returns the server's representation of the volumeSnapshot, and an error, if there is any.
func (c *FakeVolumeSnapshots) Update(volumeSnapshot *ark_v1.VolumeSnapshot) (result *ark_v1.VolumeSnapshot, err error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateAction(volumesnapshotsResource, c.ns, volumeSnapshot), &ark_v1.VolumeSnapshot{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.VolumeSnapshot), err
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *FakeVolumeSnapshots) UpdateStatus(volumeSnapshot *ark_v1.VolumeSnapshot) (*ark_v1.VolumeSnapshot, error) {
obj, err := c.Fake.
Invokes(testing.NewUpdateSubresourceAction(volumesnapshotsResource, "status", c.ns, volumeSnapshot), &ark_v1.VolumeSnapshot{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.VolumeSnapshot), err
}
// Delete takes name of the volumeSnapshot and deletes it. Returns an error if one occurs.
func (c *FakeVolumeSnapshots) Delete(name string, options *v1.DeleteOptions) error {
_, err := c.Fake.
Invokes(testing.NewDeleteAction(volumesnapshotsResource, c.ns, name), &ark_v1.VolumeSnapshot{})
return err
}
// DeleteCollection deletes a collection of objects.
func (c *FakeVolumeSnapshots) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
action := testing.NewDeleteCollectionAction(volumesnapshotsResource, c.ns, listOptions)
_, err := c.Fake.Invokes(action, &ark_v1.VolumeSnapshotList{})
return err
}
// Patch applies the patch and returns the patched volumeSnapshot.
func (c *FakeVolumeSnapshots) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *ark_v1.VolumeSnapshot, err error) {
obj, err := c.Fake.
Invokes(testing.NewPatchSubresourceAction(volumesnapshotsResource, c.ns, name, data, subresources...), &ark_v1.VolumeSnapshot{})
if obj == nil {
return nil, err
}
return obj.(*ark_v1.VolumeSnapshot), err
}

View File

@@ -38,6 +38,4 @@ type RestoreExpansion interface{}
type ScheduleExpansion interface{}
type VolumeSnapshotExpansion interface{}
type VolumeSnapshotLocationExpansion interface{}

View File

@@ -1,174 +0,0 @@
/*
Copyright 2018 the Heptio Ark 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.
*/
// Code generated by client-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/heptio/ark/pkg/apis/ark/v1"
scheme "github.com/heptio/ark/pkg/generated/clientset/versioned/scheme"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
)
// VolumeSnapshotsGetter has a method to return a VolumeSnapshotInterface.
// A group's client should implement this interface.
type VolumeSnapshotsGetter interface {
VolumeSnapshots(namespace string) VolumeSnapshotInterface
}
// VolumeSnapshotInterface has methods to work with VolumeSnapshot resources.
type VolumeSnapshotInterface interface {
Create(*v1.VolumeSnapshot) (*v1.VolumeSnapshot, error)
Update(*v1.VolumeSnapshot) (*v1.VolumeSnapshot, error)
UpdateStatus(*v1.VolumeSnapshot) (*v1.VolumeSnapshot, error)
Delete(name string, options *meta_v1.DeleteOptions) error
DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error
Get(name string, options meta_v1.GetOptions) (*v1.VolumeSnapshot, error)
List(opts meta_v1.ListOptions) (*v1.VolumeSnapshotList, error)
Watch(opts meta_v1.ListOptions) (watch.Interface, error)
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.VolumeSnapshot, err error)
VolumeSnapshotExpansion
}
// volumeSnapshots implements VolumeSnapshotInterface
type volumeSnapshots struct {
client rest.Interface
ns string
}
// newVolumeSnapshots returns a VolumeSnapshots
func newVolumeSnapshots(c *ArkV1Client, namespace string) *volumeSnapshots {
return &volumeSnapshots{
client: c.RESTClient(),
ns: namespace,
}
}
// Get takes name of the volumeSnapshot, and returns the corresponding volumeSnapshot object, and an error if there is any.
func (c *volumeSnapshots) Get(name string, options meta_v1.GetOptions) (result *v1.VolumeSnapshot, err error) {
result = &v1.VolumeSnapshot{}
err = c.client.Get().
Namespace(c.ns).
Resource("volumesnapshots").
Name(name).
VersionedParams(&options, scheme.ParameterCodec).
Do().
Into(result)
return
}
// List takes label and field selectors, and returns the list of VolumeSnapshots that match those selectors.
func (c *volumeSnapshots) List(opts meta_v1.ListOptions) (result *v1.VolumeSnapshotList, err error) {
result = &v1.VolumeSnapshotList{}
err = c.client.Get().
Namespace(c.ns).
Resource("volumesnapshots").
VersionedParams(&opts, scheme.ParameterCodec).
Do().
Into(result)
return
}
// Watch returns a watch.Interface that watches the requested volumeSnapshots.
func (c *volumeSnapshots) Watch(opts meta_v1.ListOptions) (watch.Interface, error) {
opts.Watch = true
return c.client.Get().
Namespace(c.ns).
Resource("volumesnapshots").
VersionedParams(&opts, scheme.ParameterCodec).
Watch()
}
// Create takes the representation of a volumeSnapshot and creates it. Returns the server's representation of the volumeSnapshot, and an error, if there is any.
func (c *volumeSnapshots) Create(volumeSnapshot *v1.VolumeSnapshot) (result *v1.VolumeSnapshot, err error) {
result = &v1.VolumeSnapshot{}
err = c.client.Post().
Namespace(c.ns).
Resource("volumesnapshots").
Body(volumeSnapshot).
Do().
Into(result)
return
}
// Update takes the representation of a volumeSnapshot and updates it. Returns the server's representation of the volumeSnapshot, and an error, if there is any.
func (c *volumeSnapshots) Update(volumeSnapshot *v1.VolumeSnapshot) (result *v1.VolumeSnapshot, err error) {
result = &v1.VolumeSnapshot{}
err = c.client.Put().
Namespace(c.ns).
Resource("volumesnapshots").
Name(volumeSnapshot.Name).
Body(volumeSnapshot).
Do().
Into(result)
return
}
// UpdateStatus was generated because the type contains a Status member.
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
func (c *volumeSnapshots) UpdateStatus(volumeSnapshot *v1.VolumeSnapshot) (result *v1.VolumeSnapshot, err error) {
result = &v1.VolumeSnapshot{}
err = c.client.Put().
Namespace(c.ns).
Resource("volumesnapshots").
Name(volumeSnapshot.Name).
SubResource("status").
Body(volumeSnapshot).
Do().
Into(result)
return
}
// Delete takes name of the volumeSnapshot and deletes it. Returns an error if one occurs.
func (c *volumeSnapshots) Delete(name string, options *meta_v1.DeleteOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("volumesnapshots").
Name(name).
Body(options).
Do().
Error()
}
// DeleteCollection deletes a collection of objects.
func (c *volumeSnapshots) DeleteCollection(options *meta_v1.DeleteOptions, listOptions meta_v1.ListOptions) error {
return c.client.Delete().
Namespace(c.ns).
Resource("volumesnapshots").
VersionedParams(&listOptions, scheme.ParameterCodec).
Body(options).
Do().
Error()
}
// Patch applies the patch and returns the patched volumeSnapshot.
func (c *volumeSnapshots) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.VolumeSnapshot, err error) {
result = &v1.VolumeSnapshot{}
err = c.client.Patch(pt).
Namespace(c.ns).
Resource("volumesnapshots").
SubResource(subresources...).
Name(name).
Body(data).
Do().
Into(result)
return
}

View File

@@ -44,8 +44,6 @@ type Interface interface {
Restores() RestoreInformer
// Schedules returns a ScheduleInformer.
Schedules() ScheduleInformer
// VolumeSnapshots returns a VolumeSnapshotInformer.
VolumeSnapshots() VolumeSnapshotInformer
// VolumeSnapshotLocations returns a VolumeSnapshotLocationInformer.
VolumeSnapshotLocations() VolumeSnapshotLocationInformer
}
@@ -111,11 +109,6 @@ func (v *version) Schedules() ScheduleInformer {
return &scheduleInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// VolumeSnapshots returns a VolumeSnapshotInformer.
func (v *version) VolumeSnapshots() VolumeSnapshotInformer {
return &volumeSnapshotInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
}
// VolumeSnapshotLocations returns a VolumeSnapshotLocationInformer.
func (v *version) VolumeSnapshotLocations() VolumeSnapshotLocationInformer {
return &volumeSnapshotLocationInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}

View File

@@ -1,89 +0,0 @@
/*
Copyright 2018 the Heptio Ark 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.
*/
// Code generated by informer-gen. DO NOT EDIT.
package v1
import (
time "time"
ark_v1 "github.com/heptio/ark/pkg/apis/ark/v1"
versioned "github.com/heptio/ark/pkg/generated/clientset/versioned"
internalinterfaces "github.com/heptio/ark/pkg/generated/informers/externalversions/internalinterfaces"
v1 "github.com/heptio/ark/pkg/generated/listers/ark/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
watch "k8s.io/apimachinery/pkg/watch"
cache "k8s.io/client-go/tools/cache"
)
// VolumeSnapshotInformer provides access to a shared informer and lister for
// VolumeSnapshots.
type VolumeSnapshotInformer interface {
Informer() cache.SharedIndexInformer
Lister() v1.VolumeSnapshotLister
}
type volumeSnapshotInformer struct {
factory internalinterfaces.SharedInformerFactory
tweakListOptions internalinterfaces.TweakListOptionsFunc
namespace string
}
// NewVolumeSnapshotInformer constructs a new informer for VolumeSnapshot type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewVolumeSnapshotInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
return NewFilteredVolumeSnapshotInformer(client, namespace, resyncPeriod, indexers, nil)
}
// NewFilteredVolumeSnapshotInformer constructs a new informer for VolumeSnapshot type.
// Always prefer using an informer factory to get a shared informer instead of getting an independent
// one. This reduces memory footprint and number of connections to the server.
func NewFilteredVolumeSnapshotInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options meta_v1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ArkV1().VolumeSnapshots(namespace).List(options)
},
WatchFunc: func(options meta_v1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.ArkV1().VolumeSnapshots(namespace).Watch(options)
},
},
&ark_v1.VolumeSnapshot{},
resyncPeriod,
indexers,
)
}
func (f *volumeSnapshotInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
return NewFilteredVolumeSnapshotInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
}
func (f *volumeSnapshotInformer) Informer() cache.SharedIndexInformer {
return f.factory.InformerFor(&ark_v1.VolumeSnapshot{}, f.defaultInformer)
}
func (f *volumeSnapshotInformer) Lister() v1.VolumeSnapshotLister {
return v1.NewVolumeSnapshotLister(f.Informer().GetIndexer())
}

View File

@@ -73,8 +73,6 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().Restores().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("schedules"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().Schedules().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("volumesnapshots"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().VolumeSnapshots().Informer()}, nil
case v1.SchemeGroupVersion.WithResource("volumesnapshotlocations"):
return &genericInformer{resource: resource.GroupResource(), informer: f.Ark().V1().VolumeSnapshotLocations().Informer()}, nil

View File

@@ -98,14 +98,6 @@ type ScheduleListerExpansion interface{}
// ScheduleNamespaceLister.
type ScheduleNamespaceListerExpansion interface{}
// VolumeSnapshotListerExpansion allows custom methods to be added to
// VolumeSnapshotLister.
type VolumeSnapshotListerExpansion interface{}
// VolumeSnapshotNamespaceListerExpansion allows custom methods to be added to
// VolumeSnapshotNamespaceLister.
type VolumeSnapshotNamespaceListerExpansion interface{}
// VolumeSnapshotLocationListerExpansion allows custom methods to be added to
// VolumeSnapshotLocationLister.
type VolumeSnapshotLocationListerExpansion interface{}

View File

@@ -1,94 +0,0 @@
/*
Copyright 2018 the Heptio Ark 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.
*/
// Code generated by lister-gen. DO NOT EDIT.
package v1
import (
v1 "github.com/heptio/ark/pkg/apis/ark/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/tools/cache"
)
// VolumeSnapshotLister helps list VolumeSnapshots.
type VolumeSnapshotLister interface {
// List lists all VolumeSnapshots in the indexer.
List(selector labels.Selector) (ret []*v1.VolumeSnapshot, err error)
// VolumeSnapshots returns an object that can list and get VolumeSnapshots.
VolumeSnapshots(namespace string) VolumeSnapshotNamespaceLister
VolumeSnapshotListerExpansion
}
// volumeSnapshotLister implements the VolumeSnapshotLister interface.
type volumeSnapshotLister struct {
indexer cache.Indexer
}
// NewVolumeSnapshotLister returns a new VolumeSnapshotLister.
func NewVolumeSnapshotLister(indexer cache.Indexer) VolumeSnapshotLister {
return &volumeSnapshotLister{indexer: indexer}
}
// List lists all VolumeSnapshots in the indexer.
func (s *volumeSnapshotLister) List(selector labels.Selector) (ret []*v1.VolumeSnapshot, err error) {
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
ret = append(ret, m.(*v1.VolumeSnapshot))
})
return ret, err
}
// VolumeSnapshots returns an object that can list and get VolumeSnapshots.
func (s *volumeSnapshotLister) VolumeSnapshots(namespace string) VolumeSnapshotNamespaceLister {
return volumeSnapshotNamespaceLister{indexer: s.indexer, namespace: namespace}
}
// VolumeSnapshotNamespaceLister helps list and get VolumeSnapshots.
type VolumeSnapshotNamespaceLister interface {
// List lists all VolumeSnapshots in the indexer for a given namespace.
List(selector labels.Selector) (ret []*v1.VolumeSnapshot, err error)
// Get retrieves the VolumeSnapshot from the indexer for a given namespace and name.
Get(name string) (*v1.VolumeSnapshot, error)
VolumeSnapshotNamespaceListerExpansion
}
// volumeSnapshotNamespaceLister implements the VolumeSnapshotNamespaceLister
// interface.
type volumeSnapshotNamespaceLister struct {
indexer cache.Indexer
namespace string
}
// List lists all VolumeSnapshots in the indexer for a given namespace.
func (s volumeSnapshotNamespaceLister) List(selector labels.Selector) (ret []*v1.VolumeSnapshot, err error) {
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
ret = append(ret, m.(*v1.VolumeSnapshot))
})
return ret, err
}
// Get retrieves the VolumeSnapshot from the indexer for a given namespace and name.
func (s volumeSnapshotNamespaceLister) Get(name string) (*v1.VolumeSnapshot, error) {
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
if err != nil {
return nil, err
}
if !exists {
return nil, errors.NewNotFound(v1.Resource("volumesnapshot"), name)
}
return obj.(*v1.VolumeSnapshot), nil
}

View File

@@ -5,6 +5,7 @@ import io "io"
import mock "github.com/stretchr/testify/mock"
import v1 "github.com/heptio/ark/pkg/apis/ark/v1"
import volume "github.com/heptio/ark/pkg/volume"
// BackupStore is an autogenerated mock type for the BackupStore type
type BackupStore struct {
@@ -85,6 +86,29 @@ func (_m *BackupStore) GetBackupMetadata(name string) (*v1.Backup, error) {
return r0, r1
}
// GetBackupVolumeSnapshots provides a mock function with given fields: name
func (_m *BackupStore) GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error) {
ret := _m.Called(name)
var r0 []*volume.Snapshot
if rf, ok := ret.Get(0).(func(string) []*volume.Snapshot); ok {
r0 = rf(name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*volume.Snapshot)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(name)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetDownloadURL provides a mock function with given fields: target
func (_m *BackupStore) GetDownloadURL(target v1.DownloadTarget) (string, error) {
ret := _m.Called(target)
@@ -164,13 +188,13 @@ func (_m *BackupStore) ListBackups() ([]string, error) {
return r0, r1
}
// PutBackup provides a mock function with given fields: name, metadata, contents, log
func (_m *BackupStore) PutBackup(name string, metadata io.Reader, contents io.Reader, log io.Reader) error {
ret := _m.Called(name, metadata, contents, log)
// PutBackup provides a mock function with given fields: name, metadata, contents, log, volumeSnapshots
func (_m *BackupStore) PutBackup(name string, metadata io.Reader, contents io.Reader, log io.Reader, volumeSnapshots io.Reader) error {
ret := _m.Called(name, metadata, contents, log, volumeSnapshots)
var r0 error
if rf, ok := ret.Get(0).(func(string, io.Reader, io.Reader, io.Reader) error); ok {
r0 = rf(name, metadata, contents, log)
if rf, ok := ret.Get(0).(func(string, io.Reader, io.Reader, io.Reader, io.Reader) error); ok {
r0 = rf(name, metadata, contents, log, volumeSnapshots)
} else {
r0 = ret.Error(0)
}

View File

@@ -17,6 +17,7 @@ limitations under the License.
package persistence
import (
"encoding/json"
"io"
"io/ioutil"
"strings"
@@ -31,6 +32,7 @@ import (
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
"github.com/heptio/ark/pkg/cloudprovider"
"github.com/heptio/ark/pkg/generated/clientset/versioned/scheme"
"github.com/heptio/ark/pkg/volume"
)
// BackupStore defines operations for creating, retrieving, and deleting
@@ -41,8 +43,9 @@ type BackupStore interface {
ListBackups() ([]string, error)
PutBackup(name string, metadata, contents, log io.Reader) error
PutBackup(name string, metadata, contents, log, volumeSnapshots io.Reader) error
GetBackupMetadata(name string) (*arkv1api.Backup, error)
GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error)
GetBackupContents(name string) (io.ReadCloser, error)
DeleteBackup(name string) error
@@ -159,7 +162,7 @@ func (s *objectBackupStore) ListBackups() ([]string, error) {
return output, nil
}
func (s *objectBackupStore) PutBackup(name string, metadata io.Reader, contents io.Reader, log io.Reader) error {
func (s *objectBackupStore) PutBackup(name string, metadata, contents, log, volumeSnapshots io.Reader) error {
if err := seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupLogKey(name), log); err != nil {
// Uploading the log file is best-effort; if it fails, we log the error but it doesn't impact the
// backup's status.
@@ -183,6 +186,18 @@ func (s *objectBackupStore) PutBackup(name string, metadata io.Reader, contents
return kerrors.NewAggregate([]error{err, deleteErr})
}
if err := seekAndPutObject(s.objectStore, s.bucket, s.layout.getBackupVolumeSnapshotsKey(name), volumeSnapshots); err != nil {
errs := []error{err}
deleteErr := s.objectStore.DeleteObject(s.bucket, s.layout.getBackupContentsKey(name))
errs = append(errs, deleteErr)
deleteErr = s.objectStore.DeleteObject(s.bucket, s.layout.getBackupMetadataKey(name))
errs = append(errs, deleteErr)
return kerrors.NewAggregate(errs)
}
if err := s.putRevision(); err != nil {
s.logger.WithField("backup", name).WithError(err).Warn("Error updating backup store revision")
}
@@ -216,7 +231,23 @@ func (s *objectBackupStore) GetBackupMetadata(name string) (*arkv1api.Backup, er
}
return backupObj, nil
}
func (s *objectBackupStore) GetBackupVolumeSnapshots(name string) ([]*volume.Snapshot, error) {
key := s.layout.getBackupVolumeSnapshotsKey(name)
res, err := s.objectStore.GetObject(s.bucket, key)
if err != nil {
return nil, err
}
defer res.Close()
var volumeSnapshots []*volume.Snapshot
if err := json.NewDecoder(res).Decode(&volumeSnapshots); err != nil {
return nil, errors.Wrap(err, "error decoding object data")
}
return volumeSnapshots, nil
}
func (s *objectBackupStore) GetBackupContents(name string) (io.ReadCloser, error) {

View File

@@ -83,6 +83,10 @@ func (l *ObjectStoreLayout) getBackupLogKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-logs.gz", backup))
}
func (l *ObjectStoreLayout) getBackupVolumeSnapshotsKey(backup string) string {
return path.Join(l.subdirs["backups"], backup, fmt.Sprintf("%s-volumesnapshots.json.gz", backup))
}
func (l *ObjectStoreLayout) getRestoreLogKey(restore string) string {
return path.Join(l.subdirs["restores"], restore, fmt.Sprintf("restore-%s-logs.gz", restore))
}

View File

@@ -211,6 +211,7 @@ func TestPutBackup(t *testing.T) {
metadata io.Reader
contents io.Reader
log io.Reader
snapshots io.Reader
expectedErr string
expectedKeys []string
}{
@@ -219,8 +220,15 @@ func TestPutBackup(t *testing.T) {
metadata: newStringReadSeeker("metadata"),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
expectedErr: "",
expectedKeys: []string{"backups/backup-1/ark-backup.json", "backups/backup-1/backup-1.tar.gz", "backups/backup-1/backup-1-logs.gz", "metadata/revision"},
expectedKeys: []string{
"backups/backup-1/ark-backup.json",
"backups/backup-1/backup-1.tar.gz",
"backups/backup-1/backup-1-logs.gz",
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"metadata/revision",
},
},
{
name: "normal case with backup store prefix",
@@ -228,14 +236,22 @@ func TestPutBackup(t *testing.T) {
metadata: newStringReadSeeker("metadata"),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
expectedErr: "",
expectedKeys: []string{"prefix-1/backups/backup-1/ark-backup.json", "prefix-1/backups/backup-1/backup-1.tar.gz", "prefix-1/backups/backup-1/backup-1-logs.gz", "prefix-1/metadata/revision"},
expectedKeys: []string{
"prefix-1/backups/backup-1/ark-backup.json",
"prefix-1/backups/backup-1/backup-1.tar.gz",
"prefix-1/backups/backup-1/backup-1-logs.gz",
"prefix-1/backups/backup-1/backup-1-volumesnapshots.json.gz",
"prefix-1/metadata/revision",
},
},
{
name: "error on metadata upload does not upload data",
metadata: new(errorReader),
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
@@ -244,6 +260,7 @@ func TestPutBackup(t *testing.T) {
metadata: newStringReadSeeker("metadata"),
contents: new(errorReader),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
expectedErr: "error readers return errors",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
@@ -252,14 +269,21 @@ func TestPutBackup(t *testing.T) {
metadata: newStringReadSeeker("foo"),
contents: newStringReadSeeker("bar"),
log: new(errorReader),
snapshots: newStringReadSeeker("snapshots"),
expectedErr: "",
expectedKeys: []string{"backups/backup-1/ark-backup.json", "backups/backup-1/backup-1.tar.gz", "metadata/revision"},
expectedKeys: []string{
"backups/backup-1/ark-backup.json",
"backups/backup-1/backup-1.tar.gz",
"backups/backup-1/backup-1-volumesnapshots.json.gz",
"metadata/revision",
},
},
{
name: "don't upload data when metadata is nil",
metadata: nil,
contents: newStringReadSeeker("contents"),
log: newStringReadSeeker("log"),
snapshots: newStringReadSeeker("snapshots"),
expectedErr: "",
expectedKeys: []string{"backups/backup-1/backup-1-logs.gz"},
},
@@ -269,7 +293,7 @@ func TestPutBackup(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
harness := newObjectBackupStoreTestHarness("foo", tc.prefix)
err := harness.PutBackup("backup-1", tc.metadata, tc.contents, tc.log)
err := harness.PutBackup("backup-1", tc.metadata, tc.contents, tc.log, tc.snapshots)
arktest.AssertErrorMatches(t, tc.expectedErr, err)
assert.Len(t, harness.objectStore.Data[harness.bucket], len(tc.expectedKeys))

77
pkg/volume/snapshot.go Normal file
View File

@@ -0,0 +1,77 @@
/*
Copyright 2018 the Heptio Ark 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 volume
// Snapshot stores information about a persistent volume snapshot taken as
// part of an Ark backup.
type Snapshot struct {
Spec SnapshotSpec `json:"spec"`
Status SnapshotStatus `json:"status"`
}
type SnapshotSpec struct {
// BackupName is the name of the Ark backup this snapshot
// is associated with.
BackupName string `json:"backupName"`
// BackupUID is the UID of the Ark backup this snapshot
// is associated with.
BackupUID string `json:"backupUID"`
// Location is the name of the VolumeSnapshotLocation where this snapshot is stored.
Location string `json:"location"`
// ProviderVolumeID is the provider's ID for the volume.
ProviderVolumeID string `json:"providerVolumeID"`
// VolumeType is the type of the disk/volume in the cloud provider
// API.
VolumeType string `json:"volumeType"`
// VolumeAZ is the where the volume is provisioned
// in the cloud provider.
VolumeAZ string `json:"volumeAZ,omitempty"`
// VolumeIOPS is the optional value of provisioned IOPS for the
// disk/volume in the cloud provider API.
VolumeIOPS *int64 `json:"volumeIOPS,omitempty"`
}
type SnapshotStatus struct {
// ProviderSnapshotID is the ID of the snapshot taken in the cloud
// provider API of this volume.
ProviderSnapshotID string `json:"providerSnapshotID,omitempty"`
// Phase is the current state of the VolumeSnapshot.
Phase SnapshotPhase `json:"phase,omitempty"`
}
// SnapshotPhase is the lifecyle phase of an Ark volume snapshot.
type SnapshotPhase string
const (
// SnapshotPhaseNew means the volume snapshot has been created but not
// yet processed by the VolumeSnapshotController.
SnapshotPhaseNew SnapshotPhase = "New"
// SnapshotPhaseCompleted means the volume snapshot was successfully created and can be restored from..
SnapshotPhaseCompleted SnapshotPhase = "Completed"
// SnapshotPhaseFailed means the volume snapshot was unable to execute.
SnapshotPhaseFailed SnapshotPhase = "Failed"
)