diff --git a/examples/common/00-prereqs.yaml b/examples/common/00-prereqs.yaml index a32f5996c5..4ffb20b9b6 100644 --- a/examples/common/00-prereqs.yaml +++ b/examples/common/00-prereqs.yaml @@ -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 diff --git a/pkg/apis/ark/v1/register.go b/pkg/apis/ark/v1/register.go index f80729b60b..5ce3bab1e0 100644 --- a/pkg/apis/ark/v1/register.go +++ b/pkg/apis/ark/v1/register.go @@ -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{}), } } diff --git a/pkg/apis/ark/v1/volume_snapshot.go b/pkg/apis/ark/v1/volume_snapshot.go deleted file mode 100644 index 34d48b4348..0000000000 --- a/pkg/apis/ark/v1/volume_snapshot.go +++ /dev/null @@ -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" -) diff --git a/pkg/apis/ark/v1/zz_generated.deepcopy.go b/pkg/apis/ark/v1/zz_generated.deepcopy.go index 0e478e803b..cbc3b4b7e3 100644 --- a/pkg/apis/ark/v1/zz_generated.deepcopy.go +++ b/pkg/apis/ark/v1/zz_generated.deepcopy.go @@ -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 -} diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index 534504c19c..0f012970cb 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -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, + }, + } } diff --git a/pkg/backup/item_backupper_test.go b/pkg/backup/item_backupper_test.go index dc401070ee..505044198a 100644 --- a/pkg/backup/item_backupper_test.go +++ b/pkg/backup/item_backupper_test.go @@ -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 - 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, - } + require.Len(t, ib.backupRequest.VolumeSnapshots, 1) + snapshot := ib.backupRequest.VolumeSnapshots[0] - if e, a := expectedVolumeBackups, backup.Status.VolumeBackups; !reflect.DeepEqual(e, a) { - t.Errorf("backup.status.VolumeBackups: expected %v, got %v", e, a) - } + snapshotID, _ := blockStore.SnapshotsTaken.PopAny() + 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) } }) } diff --git a/pkg/backup/request.go b/pkg/backup/request.go index 1211295ea5..7f12da180a 100644 --- a/pkg/backup/request.go +++ b/pkg/backup/request.go @@ -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 } diff --git a/pkg/backup/resource_backupper_test.go b/pkg/backup/resource_backupper_test.go index 289fb24ea6..f6c3a4f3e2 100644 --- a/pkg/backup/resource_backupper_test.go +++ b/pkg/backup/resource_backupper_test.go @@ -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{} diff --git a/pkg/controller/backup_controller.go b/pkg/controller/backup_controller.go index ea31b71693..0a85703a09 100644 --- a/pkg/controller/backup_controller.go +++ b/pkg/controller/backup_controller.go @@ -385,51 +385,74 @@ 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 } + if err := gzippedLogFile.Close(); err != nil { + c.logger.WithError(err).Error("error closing gzippedLogFile") + } + // 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 - } + 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 { - errs = append(errs, errors.Wrap(err, "error getting file info")) + err = errors.Wrap(err, "error getting file info") } else { backupSizeBytes = backupFileStat.Size() } + serverMetrics.SetBackupTarballSizeBytesGauge(backupScheduleName, backupSizeBytes) - if err := gzippedLogFile.Close(); err != nil { - c.logger.WithError(err).Error("error closing gzippedLogFile") - } + backupDuration := backup.Status.CompletionTimestamp.Time.Sub(backup.Status.StartTimestamp.Time) + backupDurationSeconds := float64(backupDuration / time.Second) + serverMetrics.RegisterBackupDuration(backupScheduleName, backupDurationSeconds) - if err := backupStore.PutBackup(backup.Name, backupJSONToUpload, backupFileToUpload, logFile); err != nil { - errs = append(errs, err) + 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")) } - backupScheduleName := backup.GetLabels()["ark-schedule"] - c.metrics.SetBackupTarballSizeBytesGauge(backupScheduleName, backupSizeBytes) + volumeSnapshots := new(bytes.Buffer) + gzw := gzip.NewWriter(volumeSnapshots) + defer gzw.Close() - backupDuration := backup.Status.CompletionTimestamp.Time.Sub(backup.Status.StartTimestamp.Time) - backupDurationSeconds := float64(backupDuration / time.Second) - c.metrics.RegisterBackupDuration(backupScheduleName, backupDurationSeconds) + if err := json.NewEncoder(gzw).Encode(backup.VolumeSnapshots); err != nil { + errs = append(errs, errors.Wrap(err, "error encoding list of volume snapshots")) + } - log.Info("Backup completed") + if len(errs) > 0 { + // Don't upload the JSON files or backup tarball if encoding to json fails. + backupJSON = nil + backupContents = nil + volumeSnapshots = nil + } - return kerrors.NewAggregate(errs) + 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) { diff --git a/pkg/controller/backup_controller_test.go b/pkg/controller/backup_controller_test.go index 2523bdc910..be16ce0573 100644 --- a/pkg/controller/backup_controller_test.go +++ b/pkg/controller/backup_controller_test.go @@ -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) diff --git a/pkg/controller/backup_sync_controller.go b/pkg/controller/backup_sync_controller.go index 75f6d6bc7a..ee85bff285 100644 --- a/pkg/controller/backup_sync_controller.go +++ b/pkg/controller/backup_sync_controller.go @@ -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") } diff --git a/pkg/generated/clientset/versioned/typed/ark/v1/ark_client.go b/pkg/generated/clientset/versioned/typed/ark/v1/ark_client.go index a2bf60aeac..a835394066 100644 --- a/pkg/generated/clientset/versioned/typed/ark/v1/ark_client.go +++ b/pkg/generated/clientset/versioned/typed/ark/v1/ark_client.go @@ -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) } diff --git a/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_ark_client.go b/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_ark_client.go index 69d0821017..988261b667 100644 --- a/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_ark_client.go +++ b/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_ark_client.go @@ -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} } diff --git a/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_volumesnapshot.go b/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_volumesnapshot.go deleted file mode 100644 index 1138b1d269..0000000000 --- a/pkg/generated/clientset/versioned/typed/ark/v1/fake/fake_volumesnapshot.go +++ /dev/null @@ -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 -} diff --git a/pkg/generated/clientset/versioned/typed/ark/v1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/ark/v1/generated_expansion.go index c50e95cd9b..47768e6b02 100644 --- a/pkg/generated/clientset/versioned/typed/ark/v1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/ark/v1/generated_expansion.go @@ -38,6 +38,4 @@ type RestoreExpansion interface{} type ScheduleExpansion interface{} -type VolumeSnapshotExpansion interface{} - type VolumeSnapshotLocationExpansion interface{} diff --git a/pkg/generated/clientset/versioned/typed/ark/v1/volumesnapshot.go b/pkg/generated/clientset/versioned/typed/ark/v1/volumesnapshot.go deleted file mode 100644 index b5c8d55fcb..0000000000 --- a/pkg/generated/clientset/versioned/typed/ark/v1/volumesnapshot.go +++ /dev/null @@ -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 -} diff --git a/pkg/generated/informers/externalversions/ark/v1/interface.go b/pkg/generated/informers/externalversions/ark/v1/interface.go index da71f31e9a..bdeb84abcc 100644 --- a/pkg/generated/informers/externalversions/ark/v1/interface.go +++ b/pkg/generated/informers/externalversions/ark/v1/interface.go @@ -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} diff --git a/pkg/generated/informers/externalversions/ark/v1/volumesnapshot.go b/pkg/generated/informers/externalversions/ark/v1/volumesnapshot.go deleted file mode 100644 index c206a3dff7..0000000000 --- a/pkg/generated/informers/externalversions/ark/v1/volumesnapshot.go +++ /dev/null @@ -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()) -} diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 9a9fb7ba07..3973bf1d80 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -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 diff --git a/pkg/generated/listers/ark/v1/expansion_generated.go b/pkg/generated/listers/ark/v1/expansion_generated.go index 1b75869c17..7daa6b8e96 100644 --- a/pkg/generated/listers/ark/v1/expansion_generated.go +++ b/pkg/generated/listers/ark/v1/expansion_generated.go @@ -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{} diff --git a/pkg/generated/listers/ark/v1/volumesnapshot.go b/pkg/generated/listers/ark/v1/volumesnapshot.go deleted file mode 100644 index a597f1226d..0000000000 --- a/pkg/generated/listers/ark/v1/volumesnapshot.go +++ /dev/null @@ -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 -} diff --git a/pkg/persistence/mocks/backup_store.go b/pkg/persistence/mocks/backup_store.go index 5407f97416..315bbe5190 100644 --- a/pkg/persistence/mocks/backup_store.go +++ b/pkg/persistence/mocks/backup_store.go @@ -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) } diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index b2793cc460..050b498f18 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -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) { diff --git a/pkg/persistence/object_store_layout.go b/pkg/persistence/object_store_layout.go index 0454b4333a..6d95d7c29a 100644 --- a/pkg/persistence/object_store_layout.go +++ b/pkg/persistence/object_store_layout.go @@ -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)) } diff --git a/pkg/persistence/object_store_test.go b/pkg/persistence/object_store_test.go index 24489ea02f..bd32f12fd1 100644 --- a/pkg/persistence/object_store_test.go +++ b/pkg/persistence/object_store_test.go @@ -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)) diff --git a/pkg/volume/snapshot.go b/pkg/volume/snapshot.go new file mode 100644 index 0000000000..13aad0f29e --- /dev/null +++ b/pkg/volume/snapshot.go @@ -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" +)