mirror of
https://github.com/vmware-tanzu/velero.git
synced 2025-12-23 06:15:21 +00:00
store volume snapshot info as JSON in backup storage
Signed-off-by: Steve Kriss <steve@heptio.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,19 +402,14 @@ 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
|
||||
blockStore cloudprovider.BlockStore
|
||||
volumeID, location string
|
||||
blockStore cloudprovider.BlockStore
|
||||
)
|
||||
|
||||
for _, snapshotLocation := range ib.backupRequest.SnapshotLocations {
|
||||
@@ -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)
|
||||
|
||||
ib.backupRequest.Status.VolumeBackups[name] = &api.VolumeBackupInfo{
|
||||
SnapshotID: snapshotID,
|
||||
Type: volumeType,
|
||||
Iops: iops,
|
||||
AvailabilityZone: pvFailureDomainZone,
|
||||
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)
|
||||
|
||||
return nil
|
||||
// nil errors are automatically removed
|
||||
return kubeerrs.NewAggregate(errs)
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
if len(test.snapshottableVolumes) > 0 {
|
||||
require.Len(t, backup.VolumeSnapshots, 1)
|
||||
snapshot := backup.VolumeSnapshots[0]
|
||||
|
||||
var actualBackups []api.VolumeBackupInfo
|
||||
for _, vbi := range backup.Status.VolumeBackups {
|
||||
actualBackups = append(actualBackups, *vbi)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -38,6 +38,4 @@ type RestoreExpansion interface{}
|
||||
|
||||
type ScheduleExpansion interface{}
|
||||
|
||||
type VolumeSnapshotExpansion interface{}
|
||||
|
||||
type VolumeSnapshotLocationExpansion interface{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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{}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -211,31 +211,47 @@ func TestPutBackup(t *testing.T) {
|
||||
metadata io.Reader
|
||||
contents io.Reader
|
||||
log io.Reader
|
||||
snapshots io.Reader
|
||||
expectedErr string
|
||||
expectedKeys []string
|
||||
}{
|
||||
{
|
||||
name: "normal case",
|
||||
metadata: newStringReadSeeker("metadata"),
|
||||
contents: newStringReadSeeker("contents"),
|
||||
log: newStringReadSeeker("log"),
|
||||
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"},
|
||||
name: "normal case",
|
||||
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",
|
||||
"backups/backup-1/backup-1-volumesnapshots.json.gz",
|
||||
"metadata/revision",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "normal case with backup store prefix",
|
||||
prefix: "prefix-1/",
|
||||
metadata: newStringReadSeeker("metadata"),
|
||||
contents: newStringReadSeeker("contents"),
|
||||
log: newStringReadSeeker("log"),
|
||||
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"},
|
||||
name: "normal case with backup store prefix",
|
||||
prefix: "prefix-1/",
|
||||
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/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,22 +260,30 @@ 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"},
|
||||
},
|
||||
{
|
||||
name: "error on log upload is ok",
|
||||
metadata: newStringReadSeeker("foo"),
|
||||
contents: newStringReadSeeker("bar"),
|
||||
log: new(errorReader),
|
||||
expectedErr: "",
|
||||
expectedKeys: []string{"backups/backup-1/ark-backup.json", "backups/backup-1/backup-1.tar.gz", "metadata/revision"},
|
||||
name: "error on log upload is ok",
|
||||
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",
|
||||
"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
77
pkg/volume/snapshot.go
Normal 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"
|
||||
)
|
||||
Reference in New Issue
Block a user