make PVProvider optional in server config; disallow snap/restore PVs when not provided
Signed-off-by: Steve Kriss <steve@heptio.com>pull/43/head
parent
3ca085eb58
commit
ebc06fd632
|
@ -25,17 +25,12 @@ metadata:
|
|||
name: default
|
||||
persistentVolumeProvider:
|
||||
aws:
|
||||
region: minio
|
||||
availabilityZone: minio
|
||||
s3ForcePathStyle: true
|
||||
s3Url: http://minio:9000
|
||||
region: us-west-2
|
||||
availabilityZone: us-west-2a
|
||||
backupStorageProvider:
|
||||
bucket: ark
|
||||
aws:
|
||||
region: minio
|
||||
availabilityZone: minio
|
||||
s3ForcePathStyle: true
|
||||
s3Url: http://minio:9000
|
||||
region: us-west-2
|
||||
backupSyncPeriod: 60m
|
||||
gcSyncPeriod: 60m
|
||||
scheduleSyncPeriod: 1m
|
||||
|
@ -50,7 +45,7 @@ The configurable parameters are as follows:
|
|||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `persistentVolumeProvider` | CloudProviderConfig<br><br>(Supported key values are `aws`, `gcp`, and `azure`, but only one can be present. See the corresponding [AWS][0], [GCP][1], and [Azure][2]-specific configs.) | Required Field | The specification for whichever cloud provider the cluster is using for persistent volumes (to be snapshotted).<br><br> *NOTE*: For Azure, your Kubernetes cluster needs to be version 1.7.2+ in order to support PV snapshotting of its managed disks. |
|
||||
| `persistentVolumeProvider` | CloudProviderConfig<br><br>(Supported key values are `aws`, `gcp`, and `azure`, but only one can be present. See the corresponding [AWS][0], [GCP][1], and [Azure][2]-specific configs.) | None (Optional) | The specification for whichever cloud provider the cluster is using for persistent volumes (to be snapshotted), if any.<br><br>If not specified, Backups and Restores requesting PV snapshots & restores, respectively, are considered invalid. <br><br> *NOTE*: For Azure, your Kubernetes cluster needs to be version 1.7.2+ in order to support PV snapshotting of its managed disks. |
|
||||
| `backupStorageProvider`/(inline) | CloudProviderConfig<br><br>(Supported key values are `aws`, `gcp`, and `azure`, but only one can be present. See the corresponding [AWS][0], [GCP][1], and [Azure][2]-specific configs.) | Required Field | The specification for whichever cloud provider will be used to actually store the backups. |
|
||||
| `backupStorageProvider/bucket` | String | Required Field | The storage bucket where backups are to be uploaded. |
|
||||
| `backupSyncPeriod` | metav1.Duration | 60m0s | How frequently Ark queries the object storage to make sure that the appropriate Backup resources have been created for existing backup files. |
|
||||
|
@ -63,22 +58,44 @@ The configurable parameters are as follows:
|
|||
|
||||
**(Or other S3-compatible storage)**
|
||||
|
||||
#### backupStorageProvider
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `region` | string | Required Field | *Example*: "us-east-1"<br><br>See [AWS documentation][3] for the full list. |
|
||||
| `disableSSL` | bool | `false` | Set this to `true` if you are using Minio (or another local, S3-compatible storage service) and your deployment is not secured. |
|
||||
| `s3ForcePathStyle` | bool | `false` | Set this to `true` if you are using a local storage service like Minio. |
|
||||
| `s3Url` | string | Required field for non-AWS-hosted storage| *Example*: http://minio:9000<br><br>You can specify the AWS S3 URL here for explicitness, but Ark can already generate it from `region`, `availabilityZone`, and `bucket`. This field is primarily for local storage services like Minio.|
|
||||
| `kmsKeyID` | string | Empty | *Example*: "502b409c-4da1-419f-a16e-eif453b3i49f"<br><br>Specify an [AWS KMS key][12] id to enable encryption of the backups stored in S3. Only works with AWS S3 and may require explicitly granting key usage rights.|
|
||||
|
||||
#### persistentVolumeProvider (AWS Only)
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `region` | string | Required Field | *Example*: "us-east-1"<br><br>See [AWS documentation][3] for the full list. |
|
||||
| `availabilityZone` | string | Required Field | *Example*: "us-east-1a"<br><br>See [AWS documentation][4] for details. |
|
||||
| `disableSSL` | bool | `false` | Set this to `true` if you are using Minio (or another local, S3-compatible storage service) and your deployment is not secured. |
|
||||
| `s3ForcePathStyle` | bool | `false` | Set this to `true` if you are using a local storage service like Minio. |
|
||||
| `s3Url` | string | Required field for non-AWS-hosted storage| *Example*: http://minio:9000<br><br>You can specify the AWS S3 URL here for explicitness, but Ark can already generate it from `region`, `availabilityZone`, and `bucket`. This field is primarily for local sotrage services like Minio.|
|
||||
| `kmsKeyID` | string | Empty | *Example*: "502b409c-4da1-419f-a16e-eif453b3i49f"<br><br>Specify an [AWS KMS key][12] id to enable encryption of the backups stored in S3. Only works with AWS S3 and may require explicitly granting key usage rights.|
|
||||
|
||||
### GCP
|
||||
|
||||
#### backupStorageProvider
|
||||
|
||||
No parameters required; specify an empty object per [example file][13].
|
||||
|
||||
#### persistentVolumeProvider
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `project` | string | Required Field | *Example*: "project-example-3jsn23"<br><br> See the [Project ID documentation][5] for details. |
|
||||
| `zone` | string | Required Field | *Example*: "us-central1-a"<br><br>See [GCP documentation][6] for the full list. |
|
||||
|
||||
### Azure
|
||||
|
||||
#### backupStorageProvider
|
||||
|
||||
No parameters required; specify an empty object per [example file][14].
|
||||
|
||||
#### persistentVolumeProvider
|
||||
|
||||
| Key | Type | Default | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `location` | string | Required Field | *Example*: "Canada East"<br><br>See [the list of available locations][7] (note that this particular page refers to them as "Regions"). |
|
||||
|
@ -97,4 +114,6 @@ The configurable parameters are as follows:
|
|||
[10]: #overview
|
||||
[11]: #example
|
||||
[12]: http://docs.aws.amazon.com/kms/latest/developerguide/overview.html
|
||||
[13]: ../examples/gcp/00-ark-config.yaml
|
||||
[14]: ../examples/azure/10-ark-config.yaml
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ backupStorageProvider:
|
|||
bucket: <YOUR_BUCKET>
|
||||
aws:
|
||||
region: <YOUR_REGION>
|
||||
availabilityZone: <YOUR_AVAILABILITY_ZONE>
|
||||
backupSyncPeriod: 30m
|
||||
gcSyncPeriod: 30m
|
||||
scheduleSyncPeriod: 1m
|
||||
|
|
|
@ -24,9 +24,7 @@ persistentVolumeProvider:
|
|||
apiTimeout: <YOUR_TIMEOUT>
|
||||
backupStorageProvider:
|
||||
bucket: <YOUR_BUCKET>
|
||||
azure:
|
||||
location: <YOUR_LOCATION>
|
||||
apiTimeout: <YOUR_TIMEOUT>
|
||||
azure: {}
|
||||
backupSyncPeriod: 30m
|
||||
gcSyncPeriod: 30m
|
||||
scheduleSyncPeriod: 1m
|
||||
|
|
|
@ -24,9 +24,7 @@ persistentVolumeProvider:
|
|||
zone: <YOUR_ZONE>
|
||||
backupStorageProvider:
|
||||
bucket: <YOUR_BUCKET>
|
||||
gcp:
|
||||
project: <YOUR_PROJECT>
|
||||
zone: <YOUR_ZONE>
|
||||
gcp: {}
|
||||
backupSyncPeriod: 30m
|
||||
gcSyncPeriod: 30m
|
||||
scheduleSyncPeriod: 1m
|
||||
|
|
|
@ -18,17 +18,10 @@ kind: Config
|
|||
metadata:
|
||||
namespace: heptio-ark
|
||||
name: default
|
||||
persistentVolumeProvider:
|
||||
aws:
|
||||
region: minio
|
||||
availabilityZone: minio
|
||||
s3ForcePathStyle: true
|
||||
s3Url: http://minio:9000
|
||||
backupStorageProvider:
|
||||
bucket: ark
|
||||
aws:
|
||||
region: minio
|
||||
availabilityZone: minio
|
||||
s3ForcePathStyle: true
|
||||
s3Url: http://minio:9000
|
||||
backupSyncPeriod: 1m
|
||||
|
|
|
@ -35,8 +35,8 @@ type Config struct {
|
|||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
// PersistentVolumeProvider is the configuration information for the cloud where
|
||||
// the cluster is running and has PersistentVolumes to snapshot or restore.
|
||||
PersistentVolumeProvider CloudProviderConfig `json:"persistentVolumeProvider"`
|
||||
// the cluster is running and has PersistentVolumes to snapshot or restore. Optional.
|
||||
PersistentVolumeProvider *CloudProviderConfig `json:"persistentVolumeProvider"`
|
||||
|
||||
// BackupStorageProvider is the configuration information for the cloud where
|
||||
// Ark backups are stored in object storage. This may be a different cloud than
|
||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||
package backup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
|
@ -38,11 +39,15 @@ type volumeSnapshotAction struct {
|
|||
|
||||
var _ Action = &volumeSnapshotAction{}
|
||||
|
||||
func NewVolumeSnapshotAction(snapshotService cloudprovider.SnapshotService) Action {
|
||||
func NewVolumeSnapshotAction(snapshotService cloudprovider.SnapshotService) (Action, error) {
|
||||
if snapshotService == nil {
|
||||
return nil, errors.New("snapshotService cannot be nil")
|
||||
}
|
||||
|
||||
return &volumeSnapshotAction{
|
||||
snapshotService: snapshotService,
|
||||
clock: clock.RealClock{},
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Execute triggers a snapshot for the volume/disk underlying a PersistentVolume if the provided
|
||||
|
|
|
@ -155,7 +155,10 @@ func TestVolumeSnapshotAction(t *testing.T) {
|
|||
}
|
||||
|
||||
snapshotService := &FakeSnapshotService{SnapshottableVolumes: test.volumeInfo}
|
||||
action := NewVolumeSnapshotAction(snapshotService).(*volumeSnapshotAction)
|
||||
|
||||
vsa, _ := NewVolumeSnapshotAction(snapshotService)
|
||||
action := vsa.(*volumeSnapshotAction)
|
||||
|
||||
fakeClock := clock.NewFakeClock(time.Now())
|
||||
action.clock = fakeClock
|
||||
|
||||
|
|
|
@ -251,8 +251,13 @@ func (s *server) initBackupService(config *api.Config) error {
|
|||
}
|
||||
|
||||
func (s *server) initSnapshotService(config *api.Config) error {
|
||||
if config.PersistentVolumeProvider == nil {
|
||||
glog.Infof("PersistentVolumeProvider config not provided, skipping SnapshotService creation")
|
||||
return nil
|
||||
}
|
||||
|
||||
glog.Infof("Configuring cloud provider for snapshot service")
|
||||
cloud, err := initCloud(config.PersistentVolumeProvider, "persistentVolumeProvider")
|
||||
cloud, err := initCloud(*config.PersistentVolumeProvider, "persistentVolumeProvider")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -408,6 +413,7 @@ func (s *server) runControllers(config *api.Config) error {
|
|||
backupper,
|
||||
s.backupService,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.snapshotService != nil,
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
@ -461,6 +467,7 @@ func (s *server) runControllers(config *api.Config) error {
|
|||
s.backupService,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.sharedInformerFactory.Ark().V1().Backups(),
|
||||
s.snapshotService != nil,
|
||||
)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
@ -490,7 +497,12 @@ func newBackupper(
|
|||
actions := map[string]backup.Action{}
|
||||
|
||||
if snapshotService != nil {
|
||||
actions["persistentvolumes"] = backup.NewVolumeSnapshotAction(snapshotService)
|
||||
action, err := backup.NewVolumeSnapshotAction(snapshotService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actions["persistentvolumes"] = action
|
||||
}
|
||||
|
||||
return backup.NewKubernetesBackupper(
|
||||
|
|
|
@ -49,9 +49,10 @@ import (
|
|||
const backupVersion = 1
|
||||
|
||||
type backupController struct {
|
||||
backupper backup.Backupper
|
||||
backupService cloudprovider.BackupService
|
||||
bucket string
|
||||
backupper backup.Backupper
|
||||
backupService cloudprovider.BackupService
|
||||
bucket string
|
||||
allowSnapshots bool
|
||||
|
||||
lister listers.BackupLister
|
||||
listerSynced cache.InformerSynced
|
||||
|
@ -68,11 +69,13 @@ func NewBackupController(
|
|||
backupper backup.Backupper,
|
||||
backupService cloudprovider.BackupService,
|
||||
bucket string,
|
||||
allowSnapshots bool,
|
||||
) Interface {
|
||||
c := &backupController{
|
||||
backupper: backupper,
|
||||
backupService: backupService,
|
||||
bucket: bucket,
|
||||
backupper: backupper,
|
||||
backupService: backupService,
|
||||
bucket: bucket,
|
||||
allowSnapshots: allowSnapshots,
|
||||
|
||||
lister: backupInformer.Lister(),
|
||||
listerSynced: backupInformer.Informer().HasSynced,
|
||||
|
@ -297,6 +300,10 @@ func (controller *backupController) getValidationErrors(itm *api.Backup) []strin
|
|||
validationErrors = append(validationErrors, fmt.Sprintf("Invalid included/excluded namespace lists: %v", err))
|
||||
}
|
||||
|
||||
if !controller.allowSnapshots && itm.Spec.SnapshotVolumes {
|
||||
validationErrors = append(validationErrors, "Server is not configured for PV snapshots")
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ func TestProcessBackup(t *testing.T) {
|
|||
expectedExcludes []string
|
||||
backup *TestBackup
|
||||
expectBackup bool
|
||||
allowSnapshots bool
|
||||
}{
|
||||
{
|
||||
name: "bad key",
|
||||
|
@ -129,6 +130,20 @@ func TestProcessBackup(t *testing.T) {
|
|||
expectedIncludes: []string{"*"},
|
||||
expectBackup: true,
|
||||
},
|
||||
{
|
||||
name: "backup with SnapshotVolumes when allowSnapshots=false fails validation",
|
||||
key: "heptio-ark/backup1",
|
||||
backup: NewTestBackup().WithName("backup1").WithPhase(v1.BackupPhaseNew).WithSnapshotVolumes(true),
|
||||
expectBackup: false,
|
||||
},
|
||||
{
|
||||
name: "backup with SnapshotVolumes when allowSnapshots=true gets executed",
|
||||
key: "heptio-ark/backup1",
|
||||
backup: NewTestBackup().WithName("backup1").WithPhase(v1.BackupPhaseNew).WithSnapshotVolumes(true),
|
||||
allowSnapshots: true,
|
||||
expectedIncludes: []string{"*"},
|
||||
expectBackup: true,
|
||||
},
|
||||
}
|
||||
|
||||
// flag.Set("logtostderr", "true")
|
||||
|
@ -150,6 +165,7 @@ func TestProcessBackup(t *testing.T) {
|
|||
backupper,
|
||||
cloudBackups,
|
||||
"bucket",
|
||||
test.allowSnapshots,
|
||||
).(*backupController)
|
||||
c.clock = clock.NewFakeClock(time.Now())
|
||||
|
||||
|
@ -180,6 +196,7 @@ func TestProcessBackup(t *testing.T) {
|
|||
}
|
||||
|
||||
backup.Spec.IncludedNamespaces = expectedNSes
|
||||
backup.Spec.SnapshotVolumes = test.backup.Spec.SnapshotVolumes
|
||||
backup.Status.Phase = v1.BackupPhaseInProgress
|
||||
backup.Status.Expiration.Time = expiration
|
||||
backup.Status.Version = 1
|
||||
|
@ -226,6 +243,7 @@ func TestProcessBackup(t *testing.T) {
|
|||
WithExcludedResources(test.expectedExcludes...).
|
||||
WithIncludedNamespaces(expectedNSes...).
|
||||
WithTTL(test.backup.Spec.TTL.Duration).
|
||||
WithSnapshotVolumes(test.backup.Spec.SnapshotVolumes).
|
||||
WithExpiration(expiration).
|
||||
WithVersion(1).
|
||||
Backup,
|
||||
|
@ -241,6 +259,7 @@ func TestProcessBackup(t *testing.T) {
|
|||
WithExcludedResources(test.expectedExcludes...).
|
||||
WithIncludedNamespaces(expectedNSes...).
|
||||
WithTTL(test.backup.Spec.TTL.Duration).
|
||||
WithSnapshotVolumes(test.backup.Spec.SnapshotVolumes).
|
||||
WithExpiration(expiration).
|
||||
WithVersion(1).
|
||||
Backup,
|
||||
|
|
|
@ -108,26 +108,36 @@ func (c *gcController) cleanBackups() {
|
|||
// storage should happen first because otherwise there's a possibility the backup sync
|
||||
// controller would re-create the API object after deletion.
|
||||
for _, backup := range backups {
|
||||
if backup.Status.Expiration.Time.Before(now) {
|
||||
glog.Infof("Removing backup %s/%s", backup.Namespace, backup.Name)
|
||||
if err := c.backupService.DeleteBackup(c.bucket, backup.Name); err != nil {
|
||||
glog.Errorf("error deleting backup %s/%s: %v", backup.Namespace, backup.Name, err)
|
||||
}
|
||||
|
||||
for _, volumeBackup := range backup.Status.VolumeBackups {
|
||||
glog.Infof("Removing snapshot %s associated with backup %s/%s", volumeBackup.SnapshotID, backup.Namespace, backup.Name)
|
||||
if err := c.snapshotService.DeleteSnapshot(volumeBackup.SnapshotID); err != nil {
|
||||
glog.Errorf("error deleting snapshot %v: %v", volumeBackup.SnapshotID, err)
|
||||
}
|
||||
}
|
||||
|
||||
glog.Infof("Removing backup API object %s/%s", backup.Namespace, backup.Name)
|
||||
if err := c.client.Backups(backup.Namespace).Delete(backup.Name, &metav1.DeleteOptions{}); err != nil {
|
||||
glog.Errorf("error deleting backup API object %s/%s: %v", backup.Namespace, backup.Name, err)
|
||||
}
|
||||
} else {
|
||||
if !backup.Status.Expiration.Time.Before(now) {
|
||||
glog.Infof("Backup %s/%s has not expired yet, skipping", backup.Namespace, backup.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
// if the backup includes snapshots but we don't currently have a PVProvider, we don't
|
||||
// want to orphan the snapshots so skip garbage-collection entirely.
|
||||
if c.snapshotService == nil && len(backup.Status.VolumeBackups) > 0 {
|
||||
glog.Warningf("Cannot garbage-collect backup %s/%s because backup includes snapshots and server is not configured with PersistentVolumeProvider",
|
||||
backup.Namespace, backup.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
glog.Infof("Removing backup %s/%s", backup.Namespace, backup.Name)
|
||||
if err := c.backupService.DeleteBackup(c.bucket, backup.Name); err != nil {
|
||||
glog.Errorf("error deleting backup %s/%s: %v", backup.Namespace, backup.Name, err)
|
||||
}
|
||||
|
||||
for _, volumeBackup := range backup.Status.VolumeBackups {
|
||||
glog.Infof("Removing snapshot %s associated with backup %s/%s", volumeBackup.SnapshotID, backup.Namespace, backup.Name)
|
||||
if err := c.snapshotService.DeleteSnapshot(volumeBackup.SnapshotID); err != nil {
|
||||
glog.Errorf("error deleting snapshot %v: %v", volumeBackup.SnapshotID, err)
|
||||
}
|
||||
}
|
||||
|
||||
glog.Infof("Removing backup API object %s/%s", backup.Namespace, backup.Name)
|
||||
if err := c.client.Backups(backup.Namespace).Delete(backup.Name, &metav1.DeleteOptions{}); err != nil {
|
||||
glog.Errorf("error deleting backup API object %s/%s: %v", backup.Namespace, backup.Name, err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// also GC any Backup API objects without files in object storage
|
||||
|
|
|
@ -31,16 +31,18 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/fake"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
. "github.com/heptio/ark/pkg/util/test"
|
||||
)
|
||||
|
||||
type gcTest struct {
|
||||
name string
|
||||
bucket string
|
||||
backups map[string][]*api.Backup
|
||||
snapshots sets.String
|
||||
name string
|
||||
bucket string
|
||||
backups map[string][]*api.Backup
|
||||
snapshots sets.String
|
||||
nilSnapshotService bool
|
||||
|
||||
expectedBackupsRemaining map[string]sets.String
|
||||
expectedSnapshotsRemaining sets.String
|
||||
|
@ -149,11 +151,38 @@ func TestGarbageCollect(t *testing.T) {
|
|||
expectedBackupsRemaining: make(map[string]sets.String),
|
||||
expectedSnapshotsRemaining: sets.NewString("snapshot-3", "snapshot-4"),
|
||||
},
|
||||
gcTest{
|
||||
name: "no snapshot service only GC's backups without snapshots",
|
||||
bucket: "bucket-1",
|
||||
backups: map[string][]*api.Backup{
|
||||
"bucket-1": []*api.Backup{
|
||||
NewTestBackup().WithName("backup-1").
|
||||
WithExpiration(fakeClock.Now().Add(-1*time.Second)).
|
||||
WithSnapshot("pv-1", "snapshot-1").
|
||||
WithSnapshot("pv-2", "snapshot-2").
|
||||
Backup,
|
||||
NewTestBackup().WithName("backup-2").
|
||||
WithExpiration(fakeClock.Now().Add(-1 * time.Second)).
|
||||
Backup,
|
||||
},
|
||||
},
|
||||
snapshots: sets.NewString("snapshot-1", "snapshot-2"),
|
||||
nilSnapshotService: true,
|
||||
expectedBackupsRemaining: map[string]sets.String{
|
||||
"bucket-1": sets.NewString("backup-1"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
backupService := &fakeBackupService{}
|
||||
snapshotService := &FakeSnapshotService{}
|
||||
var (
|
||||
backupService = &fakeBackupService{}
|
||||
snapshotService *FakeSnapshotService
|
||||
)
|
||||
|
||||
if !test.nilSnapshotService {
|
||||
snapshotService = &FakeSnapshotService{SnapshotsTaken: test.snapshots}
|
||||
}
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
backupService.backupsByBucket = make(map[string][]*api.Backup)
|
||||
|
@ -167,16 +196,19 @@ func TestGarbageCollect(t *testing.T) {
|
|||
backupService.backupsByBucket[bucket] = data
|
||||
}
|
||||
|
||||
snapshotService.SnapshotsTaken = test.snapshots
|
||||
|
||||
var (
|
||||
client = fake.NewSimpleClientset()
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
snapSvc cloudprovider.SnapshotService
|
||||
)
|
||||
|
||||
if snapshotService != nil {
|
||||
snapSvc = snapshotService
|
||||
}
|
||||
|
||||
controller := NewGCController(
|
||||
backupService,
|
||||
snapshotService,
|
||||
snapSvc,
|
||||
test.bucket,
|
||||
1*time.Millisecond,
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
|
@ -202,7 +234,9 @@ func TestGarbageCollect(t *testing.T) {
|
|||
assert.Equal(t, test.expectedBackupsRemaining[bucket], backupNames)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expectedSnapshotsRemaining, snapshotService.SnapshotsTaken)
|
||||
if !test.nilSnapshotService {
|
||||
assert.Equal(t, test.expectedSnapshotsRemaining, snapshotService.SnapshotsTaken)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,12 @@ import (
|
|||
)
|
||||
|
||||
type restoreController struct {
|
||||
restoreClient arkv1client.RestoresGetter
|
||||
backupClient arkv1client.BackupsGetter
|
||||
restorer restore.Restorer
|
||||
backupService cloudprovider.BackupService
|
||||
bucket string
|
||||
restoreClient arkv1client.RestoresGetter
|
||||
backupClient arkv1client.BackupsGetter
|
||||
restorer restore.Restorer
|
||||
backupService cloudprovider.BackupService
|
||||
bucket string
|
||||
allowSnapshotRestores bool
|
||||
|
||||
backupLister listers.BackupLister
|
||||
backupListerSynced cache.InformerSynced
|
||||
|
@ -64,18 +65,20 @@ func NewRestoreController(
|
|||
backupService cloudprovider.BackupService,
|
||||
bucket string,
|
||||
backupInformer informers.BackupInformer,
|
||||
allowSnapshotRestores bool,
|
||||
) Interface {
|
||||
c := &restoreController{
|
||||
restoreClient: restoreClient,
|
||||
backupClient: backupClient,
|
||||
restorer: restorer,
|
||||
backupService: backupService,
|
||||
bucket: bucket,
|
||||
backupLister: backupInformer.Lister(),
|
||||
backupListerSynced: backupInformer.Informer().HasSynced,
|
||||
restoreLister: restoreInformer.Lister(),
|
||||
restoreListerSynced: restoreInformer.Informer().HasSynced,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "restore"),
|
||||
restoreClient: restoreClient,
|
||||
backupClient: backupClient,
|
||||
restorer: restorer,
|
||||
backupService: backupService,
|
||||
bucket: bucket,
|
||||
allowSnapshotRestores: allowSnapshotRestores,
|
||||
backupLister: backupInformer.Lister(),
|
||||
backupListerSynced: backupInformer.Informer().HasSynced,
|
||||
restoreLister: restoreInformer.Lister(),
|
||||
restoreListerSynced: restoreInformer.Informer().HasSynced,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "restore"),
|
||||
}
|
||||
|
||||
c.syncHandler = c.processRestore
|
||||
|
@ -275,6 +278,10 @@ func (controller *restoreController) getValidationErrors(itm *api.Restore) []str
|
|||
validationErrors = append(validationErrors, "BackupName must be non-empty and correspond to the name of a backup in object storage.")
|
||||
}
|
||||
|
||||
if !controller.allowSnapshotRestores && itm.Spec.RestorePVs {
|
||||
validationErrors = append(validationErrors, "Server is not configured for PV snapshot restores")
|
||||
}
|
||||
|
||||
return validationErrors
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ func TestProcessRestore(t *testing.T) {
|
|||
restore *api.Restore
|
||||
backup *api.Backup
|
||||
restorerError error
|
||||
allowRestoreSnapshots bool
|
||||
expectedErr bool
|
||||
expectedRestoreUpdates []*api.Restore
|
||||
expectedRestorerCall *api.Restore
|
||||
|
@ -137,6 +138,28 @@ func TestProcessRestore(t *testing.T) {
|
|||
},
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("*").Restore,
|
||||
},
|
||||
{
|
||||
name: "valid restore with RestorePVs=true gets executed when allowRestoreSnapshots=true",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
allowRestoreSnapshots: true,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseCompleted).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
},
|
||||
expectedRestorerCall: NewTestRestore("foo", "bar", api.RestorePhaseInProgress).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
},
|
||||
{
|
||||
name: "restore with RestorePVs=true fails validation when allowRestoreSnapshots=false",
|
||||
restore: NewTestRestore("foo", "bar", api.RestorePhaseNew).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).Restore,
|
||||
backup: NewTestBackup().WithName("backup-1").Backup,
|
||||
expectedErr: false,
|
||||
expectedRestoreUpdates: []*api.Restore{
|
||||
NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).WithBackup("backup-1").WithRestorableNamespace("ns-1").WithRestorePVs(true).
|
||||
WithValidationError("Server is not configured for PV snapshot restores").Restore,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// flag.Set("logtostderr", "true")
|
||||
|
@ -160,6 +183,7 @@ func TestProcessRestore(t *testing.T) {
|
|||
backupSvc,
|
||||
"bucket",
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
test.allowRestoreSnapshots,
|
||||
).(*restoreController)
|
||||
|
||||
if test.restore != nil {
|
||||
|
|
|
@ -57,6 +57,10 @@ func (sr *persistentVolumeRestorer) Prepare(obj runtime.Unstructured, restore *a
|
|||
delete(spec, "storageClassName")
|
||||
|
||||
if restore.Spec.RestorePVs {
|
||||
if sr.snapshotService == nil {
|
||||
return nil, errors.New("PV restorer is not configured for PV snapshot restores")
|
||||
}
|
||||
|
||||
volumeID, err := sr.restoreVolume(obj.UnstructuredContent(), restore, backup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -104,3 +104,8 @@ func (b *TestBackup) WithSnapshot(pv string, snapshot string) *TestBackup {
|
|||
b.Status.VolumeBackups[pv] = &v1.VolumeBackupInfo{SnapshotID: snapshot}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *TestBackup) WithSnapshotVolumes(value bool) *TestBackup {
|
||||
b.Spec.SnapshotVolumes = value
|
||||
return b
|
||||
}
|
||||
|
|
|
@ -60,3 +60,8 @@ func (r *TestRestore) WithErrors(e api.RestoreResult) *TestRestore {
|
|||
r.Status.Errors = e
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *TestRestore) WithRestorePVs(value bool) *TestRestore {
|
||||
r.Spec.RestorePVs = value
|
||||
return r
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue