mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-01 04:46:05 +00:00
Merge pull request #710 from skriss/resumable-plugins-rebased
Resumable plugins rebased
This commit is contained in:
+2
-10
@@ -40,14 +40,13 @@ import (
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
)
|
||||
|
||||
// Backupper performs backups.
|
||||
type Backupper interface {
|
||||
// Backup takes a backup using the specification in the api.Backup and writes backup and log data
|
||||
// to the given writers.
|
||||
Backup(backup *api.Backup, backupFile, logFile io.Writer, actions []ItemAction) error
|
||||
Backup(logger logrus.FieldLogger, backup *api.Backup, backupFile io.Writer, actions []ItemAction) error
|
||||
}
|
||||
|
||||
// kubernetesBackupper implements Backupper.
|
||||
@@ -212,20 +211,13 @@ func getResourceHook(hookSpec api.BackupResourceHookSpec, discoveryHelper discov
|
||||
|
||||
// Backup backs up the items specified in the Backup, placing them in a gzip-compressed tar file
|
||||
// written to backupFile. The finalized api.Backup is written to metadata.
|
||||
func (kb *kubernetesBackupper) Backup(backup *api.Backup, backupFile, logFile io.Writer, actions []ItemAction) error {
|
||||
func (kb *kubernetesBackupper) Backup(logger logrus.FieldLogger, backup *api.Backup, backupFile io.Writer, actions []ItemAction) error {
|
||||
gzippedData := gzip.NewWriter(backupFile)
|
||||
defer gzippedData.Close()
|
||||
|
||||
tw := tar.NewWriter(gzippedData)
|
||||
defer tw.Close()
|
||||
|
||||
gzippedLog := gzip.NewWriter(logFile)
|
||||
defer gzippedLog.Close()
|
||||
|
||||
logger := logrus.New()
|
||||
logger.Out = gzippedLog
|
||||
logger.Hooks.Add(&logging.ErrorLocationHook{})
|
||||
logger.Hooks.Add(&logging.LogLocationHook{})
|
||||
log := logger.WithField("backup", kubeutil.NamespaceAndName(backup))
|
||||
log.Info("Starting backup")
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ type backupPVAction struct {
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
func NewBackupPVAction(log logrus.FieldLogger) ItemAction {
|
||||
return &backupPVAction{log: log}
|
||||
func NewBackupPVAction(logger logrus.FieldLogger) ItemAction {
|
||||
return &backupPVAction{log: logger}
|
||||
}
|
||||
|
||||
func (a *backupPVAction) AppliesTo() (ResourceSelector, error) {
|
||||
|
||||
@@ -18,8 +18,6 @@ package backup
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
@@ -46,6 +44,7 @@ import (
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
)
|
||||
|
||||
@@ -549,22 +548,9 @@ func TestBackup(t *testing.T) {
|
||||
groupBackupper.On("backupGroup", group).Return(err)
|
||||
}
|
||||
|
||||
var backupFile, logFile bytes.Buffer
|
||||
var backupFile bytes.Buffer
|
||||
|
||||
err = b.Backup(test.backup, &backupFile, &logFile, nil)
|
||||
defer func() {
|
||||
// print log if anything failed
|
||||
if t.Failed() {
|
||||
gzr, err := gzip.NewReader(&logFile)
|
||||
require.NoError(t, err)
|
||||
t.Log("Backup log contents:")
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gzr)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, gzr.Close())
|
||||
t.Log(buf.String())
|
||||
}
|
||||
}()
|
||||
err = b.Backup(logging.DefaultLogger(logrus.DebugLevel), test.backup, &backupFile, nil)
|
||||
|
||||
if test.expectedError != nil {
|
||||
assert.EqualError(t, err, test.expectedError.Error())
|
||||
@@ -610,7 +596,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
).Return(&mockGroupBackupper{})
|
||||
|
||||
assert.NoError(t, b.Backup(&v1.Backup{}, &bytes.Buffer{}, &bytes.Buffer{}, nil))
|
||||
assert.NoError(t, b.Backup(arktest.NewLogger(), &v1.Backup{}, &bytes.Buffer{}, nil))
|
||||
groupBackupperFactory.AssertExpectations(t)
|
||||
|
||||
// mutate the cohabitatingResources map that was used in the first backup to simulate
|
||||
@@ -642,7 +628,7 @@ func TestBackupUsesNewCohabitatingResourcesForEachBackup(t *testing.T) {
|
||||
mock.Anything,
|
||||
).Return(&mockGroupBackupper{})
|
||||
|
||||
assert.NoError(t, b.Backup(&v1.Backup{}, &bytes.Buffer{}, &bytes.Buffer{}, nil))
|
||||
assert.NoError(t, b.Backup(arktest.NewLogger(), &v1.Backup{}, &bytes.Buffer{}, nil))
|
||||
assert.NotEqual(t, firstCohabitatingResources, secondCohabitatingResources)
|
||||
for _, resource := range secondCohabitatingResources {
|
||||
assert.False(t, resource.seen)
|
||||
|
||||
@@ -41,7 +41,6 @@ import (
|
||||
"github.com/heptio/ark/pkg/podexec"
|
||||
"github.com/heptio/ark/pkg/restic"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
)
|
||||
|
||||
type itemBackupperFactory interface {
|
||||
@@ -320,10 +319,6 @@ func (ib *defaultItemBackupper) executeActions(
|
||||
|
||||
log.Info("Executing custom action")
|
||||
|
||||
if logSetter, ok := action.ItemAction.(logging.LogSetter); ok {
|
||||
logSetter.SetLog(log)
|
||||
}
|
||||
|
||||
updatedItem, additionalItemIdentifiers, err := action.Execute(obj, ib.backup)
|
||||
if err != nil {
|
||||
// We want this to show up in the log file at the place where the error occurs. When we return
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 mockery v1.0.0. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ type podAction struct {
|
||||
}
|
||||
|
||||
// NewPodAction creates a new ItemAction for pods.
|
||||
func NewPodAction(log logrus.FieldLogger) ItemAction {
|
||||
return &podAction{log: log}
|
||||
func NewPodAction(logger logrus.FieldLogger) ItemAction {
|
||||
return &podAction{log: logger}
|
||||
}
|
||||
|
||||
// AppliesTo returns a ResourceSelector that applies only to pods.
|
||||
|
||||
@@ -38,7 +38,7 @@ type serviceAccountAction struct {
|
||||
}
|
||||
|
||||
// NewServiceAccountAction creates a new ItemAction for service accounts.
|
||||
func NewServiceAccountAction(log logrus.FieldLogger, clusterRoleBindingListers map[string]ClusterRoleBindingLister, discoveryHelper arkdiscovery.Helper) (ItemAction, error) {
|
||||
func NewServiceAccountAction(logger logrus.FieldLogger, clusterRoleBindingListers map[string]ClusterRoleBindingLister, discoveryHelper arkdiscovery.Helper) (ItemAction, error) {
|
||||
// Look up the supported RBAC version
|
||||
var supportedAPI metav1.GroupVersionForDiscovery
|
||||
for _, ag := range discoveryHelper.APIGroups() {
|
||||
@@ -58,7 +58,7 @@ func NewServiceAccountAction(log logrus.FieldLogger, clusterRoleBindingListers m
|
||||
}
|
||||
|
||||
return &serviceAccountAction{
|
||||
log: log,
|
||||
log: logger,
|
||||
clusterRoleBindings: crbs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 buildinfo
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 client
|
||||
|
||||
import (
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/ec2"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
@@ -40,6 +41,7 @@ const regionKey = "region"
|
||||
var iopsVolumeTypes = sets.NewString("io1")
|
||||
|
||||
type blockStore struct {
|
||||
log logrus.FieldLogger
|
||||
ec2 *ec2.EC2
|
||||
}
|
||||
|
||||
@@ -56,8 +58,8 @@ func getSession(config *aws.Config) (*session.Session, error) {
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func NewBlockStore() cloudprovider.BlockStore {
|
||||
return &blockStore{}
|
||||
func NewBlockStore(logger logrus.FieldLogger) cloudprovider.BlockStore {
|
||||
return &blockStore{log: logger}
|
||||
}
|
||||
|
||||
func (b *blockStore) Init(config map[string]string) error {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go/service/s3/s3manager"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
)
|
||||
@@ -38,13 +39,14 @@ const (
|
||||
)
|
||||
|
||||
type objectStore struct {
|
||||
log logrus.FieldLogger
|
||||
s3 *s3.S3
|
||||
s3Uploader *s3manager.Uploader
|
||||
kmsKeyID string
|
||||
}
|
||||
|
||||
func NewObjectStore() cloudprovider.ObjectStore {
|
||||
return &objectStore{}
|
||||
func NewObjectStore(logger logrus.FieldLogger) cloudprovider.ObjectStore {
|
||||
return &objectStore{log: logger}
|
||||
}
|
||||
|
||||
func (o *objectStore) Init(config map[string]string) error {
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/satori/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
@@ -52,6 +53,7 @@ const (
|
||||
)
|
||||
|
||||
type blockStore struct {
|
||||
log logrus.FieldLogger
|
||||
disks *disk.DisksClient
|
||||
snaps *disk.SnapshotsClient
|
||||
subscription string
|
||||
@@ -87,8 +89,8 @@ func getConfig() map[string]string {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func NewBlockStore() cloudprovider.BlockStore {
|
||||
return &blockStore{}
|
||||
func NewBlockStore(logger logrus.FieldLogger) cloudprovider.BlockStore {
|
||||
return &blockStore{log: logger}
|
||||
}
|
||||
|
||||
func (b *blockStore) Init(config map[string]string) error {
|
||||
|
||||
@@ -23,16 +23,18 @@ import (
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
)
|
||||
|
||||
type objectStore struct {
|
||||
blobClient *storage.BlobStorageClient
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
func NewObjectStore() cloudprovider.ObjectStore {
|
||||
return &objectStore{}
|
||||
func NewObjectStore(logger logrus.FieldLogger) cloudprovider.ObjectStore {
|
||||
return &objectStore{log: logger}
|
||||
}
|
||||
|
||||
func (o *objectStore) Init(config map[string]string) error {
|
||||
|
||||
@@ -28,15 +28,15 @@ import (
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
// backupCacheBucket holds the backups and error from a GetAllBackups call.
|
||||
// backupCacheBucket holds the backups and error from a ListBackups call.
|
||||
type backupCacheBucket struct {
|
||||
backups []*v1.Backup
|
||||
error error
|
||||
}
|
||||
|
||||
// backupCache caches GetAllBackups calls, refreshing them periodically.
|
||||
// backupCache caches ListBackups calls, refreshing them periodically.
|
||||
type backupCache struct {
|
||||
delegate BackupGetter
|
||||
delegate BackupLister
|
||||
lock sync.RWMutex
|
||||
// This doesn't really need to be a map right now, but if we ever move to supporting multiple
|
||||
// buckets, this will be ready for it.
|
||||
@@ -44,10 +44,10 @@ type backupCache struct {
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
var _ BackupGetter = &backupCache{}
|
||||
var _ BackupLister = &backupCache{}
|
||||
|
||||
// NewBackupCache returns a new backup cache that refreshes from delegate every resyncPeriod.
|
||||
func NewBackupCache(ctx context.Context, delegate BackupGetter, resyncPeriod time.Duration, logger logrus.FieldLogger) BackupGetter {
|
||||
func NewBackupCache(ctx context.Context, delegate BackupLister, resyncPeriod time.Duration, logger logrus.FieldLogger) BackupLister {
|
||||
c := &backupCache{
|
||||
delegate: delegate,
|
||||
buckets: make(map[string]*backupCacheBucket),
|
||||
@@ -70,11 +70,11 @@ func (c *backupCache) refresh() {
|
||||
|
||||
for bucketName, bucket := range c.buckets {
|
||||
c.logger.WithField("bucket", bucketName).Debug("Refreshing bucket")
|
||||
bucket.backups, bucket.error = c.delegate.GetAllBackups(bucketName)
|
||||
bucket.backups, bucket.error = c.delegate.ListBackups(bucketName)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *backupCache) GetAllBackups(bucketName string) ([]*v1.Backup, error) {
|
||||
func (c *backupCache) ListBackups(bucketName string) ([]*v1.Backup, error) {
|
||||
c.lock.RLock()
|
||||
bucket, found := c.buckets[bucketName]
|
||||
c.lock.RUnlock()
|
||||
@@ -88,7 +88,7 @@ func (c *backupCache) GetAllBackups(bucketName string) ([]*v1.Backup, error) {
|
||||
|
||||
logContext.Debug("Bucket is not in cache - doing a live lookup")
|
||||
|
||||
backups, err := c.delegate.GetAllBackups(bucketName)
|
||||
backups, err := c.delegate.ListBackups(bucketName)
|
||||
c.lock.Lock()
|
||||
c.buckets[bucketName] = &backupCacheBucket{backups: backups, error: err}
|
||||
c.lock.Unlock()
|
||||
|
||||
@@ -25,13 +25,13 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
cloudprovidermocks "github.com/heptio/ark/pkg/cloudprovider/mocks"
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
)
|
||||
|
||||
func TestNewBackupCache(t *testing.T) {
|
||||
|
||||
var (
|
||||
delegate = &test.FakeBackupService{}
|
||||
delegate = &cloudprovidermocks.BackupLister{}
|
||||
ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second)
|
||||
logger = test.NewLogger()
|
||||
)
|
||||
@@ -44,32 +44,32 @@ func TestNewBackupCache(t *testing.T) {
|
||||
test.NewTestBackup().WithName("backup1").Backup,
|
||||
test.NewTestBackup().WithName("backup2").Backup,
|
||||
}
|
||||
delegate.On("GetAllBackups", "bucket1").Return(bucket1, nil).Once()
|
||||
delegate.On("ListBackups", "bucket1").Return(bucket1, nil).Once()
|
||||
|
||||
// should be updated via refresh
|
||||
updatedBucket1 := []*v1.Backup{
|
||||
test.NewTestBackup().WithName("backup2").Backup,
|
||||
}
|
||||
delegate.On("GetAllBackups", "bucket1").Return(updatedBucket1, nil)
|
||||
delegate.On("ListBackups", "bucket1").Return(updatedBucket1, nil)
|
||||
|
||||
// nothing in cache, live lookup
|
||||
bucket2 := []*v1.Backup{
|
||||
test.NewTestBackup().WithName("backup5").Backup,
|
||||
test.NewTestBackup().WithName("backup6").Backup,
|
||||
}
|
||||
delegate.On("GetAllBackups", "bucket2").Return(bucket2, nil).Once()
|
||||
delegate.On("ListBackups", "bucket2").Return(bucket2, nil).Once()
|
||||
|
||||
// should be updated via refresh
|
||||
updatedBucket2 := []*v1.Backup{
|
||||
test.NewTestBackup().WithName("backup7").Backup,
|
||||
}
|
||||
delegate.On("GetAllBackups", "bucket2").Return(updatedBucket2, nil)
|
||||
delegate.On("ListBackups", "bucket2").Return(updatedBucket2, nil)
|
||||
|
||||
backups, err := c.GetAllBackups("bucket1")
|
||||
backups, err := c.ListBackups("bucket1")
|
||||
assert.Equal(t, bucket1, backups)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backups, err = c.GetAllBackups("bucket2")
|
||||
backups, err = c.ListBackups("bucket2")
|
||||
assert.Equal(t, bucket2, backups)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -84,14 +84,14 @@ func TestNewBackupCache(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
backups, err = c.GetAllBackups("bucket1")
|
||||
backups, err = c.ListBackups("bucket1")
|
||||
if len(backups) == 1 {
|
||||
if assert.Equal(t, updatedBucket1[0], backups[0]) {
|
||||
done1 = true
|
||||
}
|
||||
}
|
||||
|
||||
backups, err = c.GetAllBackups("bucket2")
|
||||
backups, err = c.ListBackups("bucket2")
|
||||
if len(backups) == 1 {
|
||||
if assert.Equal(t, updatedBucket2[0], backups[0]) {
|
||||
done2 = true
|
||||
@@ -103,7 +103,7 @@ func TestNewBackupCache(t *testing.T) {
|
||||
|
||||
func TestBackupCacheRefresh(t *testing.T) {
|
||||
var (
|
||||
delegate = &test.FakeBackupService{}
|
||||
delegate = &cloudprovidermocks.BackupLister{}
|
||||
logger = test.NewLogger()
|
||||
)
|
||||
|
||||
@@ -120,9 +120,9 @@ func TestBackupCacheRefresh(t *testing.T) {
|
||||
test.NewTestBackup().WithName("backup1").Backup,
|
||||
test.NewTestBackup().WithName("backup2").Backup,
|
||||
}
|
||||
delegate.On("GetAllBackups", "bucket1").Return(bucket1, nil)
|
||||
delegate.On("ListBackups", "bucket1").Return(bucket1, nil)
|
||||
|
||||
delegate.On("GetAllBackups", "bucket2").Return(nil, errors.New("bad"))
|
||||
delegate.On("ListBackups", "bucket2").Return(nil, errors.New("bad"))
|
||||
|
||||
c.refresh()
|
||||
|
||||
@@ -135,7 +135,7 @@ func TestBackupCacheRefresh(t *testing.T) {
|
||||
|
||||
func TestBackupCacheGetAllBackupsUsesCacheIfPresent(t *testing.T) {
|
||||
var (
|
||||
delegate = &test.FakeBackupService{}
|
||||
delegate = &cloudprovidermocks.BackupLister{}
|
||||
logger = test.NewLogger()
|
||||
bucket1 = []*v1.Backup{
|
||||
test.NewTestBackup().WithName("backup1").Backup,
|
||||
@@ -158,13 +158,13 @@ func TestBackupCacheGetAllBackupsUsesCacheIfPresent(t *testing.T) {
|
||||
test.NewTestBackup().WithName("backup4").Backup,
|
||||
}
|
||||
|
||||
delegate.On("GetAllBackups", "bucket2").Return(bucket2, nil)
|
||||
delegate.On("ListBackups", "bucket2").Return(bucket2, nil)
|
||||
|
||||
backups, err := c.GetAllBackups("bucket1")
|
||||
backups, err := c.ListBackups("bucket1")
|
||||
assert.Equal(t, bucket1, backups)
|
||||
assert.NoError(t, err)
|
||||
|
||||
backups, err = c.GetAllBackups("bucket2")
|
||||
backups, err = c.ListBackups("bucket2")
|
||||
assert.Equal(t, bucket2, backups)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
+102
-120
@@ -17,7 +17,6 @@ limitations under the License.
|
||||
package cloudprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -26,47 +25,16 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/versioned/scheme"
|
||||
)
|
||||
|
||||
// BackupService contains methods for working with backups in object storage.
|
||||
type BackupService interface {
|
||||
BackupGetter
|
||||
// UploadBackup uploads the specified Ark backup of a set of Kubernetes API objects, whose manifests are
|
||||
// stored in the specified file, into object storage in an Ark bucket, tagged with Ark metadata. Returns
|
||||
// an error if a problem is encountered accessing the file or performing the upload via the cloud API.
|
||||
UploadBackup(bucket, name string, metadata, backup, log io.Reader) error
|
||||
|
||||
// DownloadBackup downloads an Ark backup with the specified object key from object storage via the cloud API.
|
||||
// It returns the snapshot metadata and data (separately), or an error if a problem is encountered
|
||||
// downloading or reading the file from the cloud API.
|
||||
DownloadBackup(bucket, name string) (io.ReadCloser, error)
|
||||
|
||||
// DeleteBackupDir deletes all files in object storage for the given backup.
|
||||
DeleteBackupDir(bucket, backupName string) error
|
||||
|
||||
// GetBackup gets the specified api.Backup from the given bucket in object storage.
|
||||
GetBackup(bucket, name string) (*api.Backup, error)
|
||||
|
||||
// CreateSignedURL creates a pre-signed URL that can be used to download a file from object
|
||||
// storage. The URL expires after ttl.
|
||||
CreateSignedURL(target api.DownloadTarget, bucket, directory string, ttl time.Duration) (string, error)
|
||||
|
||||
// UploadRestoreLog uploads the restore's log file to object storage.
|
||||
UploadRestoreLog(bucket, backup, restore string, log io.Reader) error
|
||||
|
||||
// UploadRestoreResults uploads the restore's results file to object storage.
|
||||
UploadRestoreResults(bucket, backup, restore string, results io.Reader) error
|
||||
}
|
||||
|
||||
// BackupGetter knows how to list backups in object storage.
|
||||
type BackupGetter interface {
|
||||
// GetAllBackups lists all the api.Backups in object storage for the given bucket.
|
||||
GetAllBackups(bucket string) ([]*api.Backup, error)
|
||||
// BackupLister knows how to list backups in object storage.
|
||||
type BackupLister interface {
|
||||
// ListBackups lists all the api.Backups in object storage for the given bucket.
|
||||
ListBackups(bucket string) ([]*api.Backup, error)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -97,24 +65,6 @@ func getRestoreResultsKey(directory, restore string) string {
|
||||
return fmt.Sprintf(restoreResultsFileFormatString, directory, restore)
|
||||
}
|
||||
|
||||
type backupService struct {
|
||||
objectStore ObjectStore
|
||||
decoder runtime.Decoder
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
var _ BackupService = &backupService{}
|
||||
var _ BackupGetter = &backupService{}
|
||||
|
||||
// NewBackupService creates a backup service using the provided object store
|
||||
func NewBackupService(objectStore ObjectStore, logger logrus.FieldLogger) BackupService {
|
||||
return &backupService{
|
||||
objectStore: objectStore,
|
||||
decoder: scheme.Codecs.UniversalDecoder(api.SchemeGroupVersion),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func seekToBeginning(r io.Reader) error {
|
||||
seeker, ok := r.(io.Seeker)
|
||||
if !ok {
|
||||
@@ -125,7 +75,7 @@ func seekToBeginning(r io.Reader) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (br *backupService) seekAndPutObject(bucket, key string, file io.Reader) error {
|
||||
func seekAndPutObject(objectStore ObjectStore, bucket, key string, file io.Reader) error {
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -134,18 +84,34 @@ func (br *backupService) seekAndPutObject(bucket, key string, file io.Reader) er
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return br.objectStore.PutObject(bucket, key, file)
|
||||
return objectStore.PutObject(bucket, key, file)
|
||||
}
|
||||
|
||||
func (br *backupService) UploadBackup(bucket, backupName string, metadata, backup, log io.Reader) error {
|
||||
// Uploading the log file is best-effort; if it fails, we log the error but it doesn't impact the
|
||||
// backup's status.
|
||||
func UploadBackupLog(objectStore ObjectStore, bucket, backupName string, log io.Reader) error {
|
||||
logKey := getBackupLogKey(backupName, backupName)
|
||||
if err := br.seekAndPutObject(bucket, logKey, log); err != nil {
|
||||
br.logger.WithError(err).WithFields(logrus.Fields{
|
||||
"bucket": bucket,
|
||||
"key": logKey,
|
||||
}).Error("Error uploading log file")
|
||||
return seekAndPutObject(objectStore, bucket, logKey, log)
|
||||
}
|
||||
|
||||
func UploadBackupMetadata(objectStore ObjectStore, bucket, backupName string, metadata io.Reader) error {
|
||||
metadataKey := getMetadataKey(backupName)
|
||||
return seekAndPutObject(objectStore, bucket, metadataKey, metadata)
|
||||
}
|
||||
|
||||
func DeleteBackupMetadata(objectStore ObjectStore, bucket, backupName string) error {
|
||||
metadataKey := getMetadataKey(backupName)
|
||||
return objectStore.DeleteObject(bucket, metadataKey)
|
||||
}
|
||||
|
||||
func UploadBackupData(objectStore ObjectStore, bucket, backupName string, backup io.Reader) error {
|
||||
backupKey := getBackupContentsKey(backupName, backupName)
|
||||
return seekAndPutObject(objectStore, bucket, backupKey, backup)
|
||||
}
|
||||
|
||||
func UploadBackup(logger logrus.FieldLogger, objectStore ObjectStore, bucket, backupName string, metadata, backup, log io.Reader) error {
|
||||
if err := UploadBackupLog(objectStore, bucket, backupName, 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.
|
||||
logger.WithError(err).WithField("bucket", bucket).Error("Error uploading log file")
|
||||
}
|
||||
|
||||
if metadata == nil {
|
||||
@@ -156,31 +122,49 @@ func (br *backupService) UploadBackup(bucket, backupName string, metadata, backu
|
||||
}
|
||||
|
||||
// upload metadata file
|
||||
metadataKey := getMetadataKey(backupName)
|
||||
if err := br.seekAndPutObject(bucket, metadataKey, metadata); err != nil {
|
||||
if err := UploadBackupMetadata(objectStore, bucket, backupName, metadata); err != nil {
|
||||
// failure to upload metadata file is a hard-stop
|
||||
return err
|
||||
}
|
||||
|
||||
if backup != nil {
|
||||
// upload tar file
|
||||
if err := br.seekAndPutObject(bucket, getBackupContentsKey(backupName, backupName), backup); err != nil {
|
||||
// try to delete the metadata file since the data upload failed
|
||||
deleteErr := br.objectStore.DeleteObject(bucket, metadataKey)
|
||||
|
||||
return kerrors.NewAggregate([]error{err, deleteErr})
|
||||
}
|
||||
// upload tar file
|
||||
if err := UploadBackupData(objectStore, bucket, backupName, backup); err != nil {
|
||||
// try to delete the metadata file since the data upload failed
|
||||
deleteErr := DeleteBackupMetadata(objectStore, bucket, backupName)
|
||||
return kerrors.NewAggregate([]error{err, deleteErr})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (br *backupService) DownloadBackup(bucket, backupName string) (io.ReadCloser, error) {
|
||||
return br.objectStore.GetObject(bucket, getBackupContentsKey(backupName, backupName))
|
||||
// DownloadBackupFunc is a function that can download backup metadata from a bucket in object storage.
|
||||
type DownloadBackupFunc func(objectStore ObjectStore, bucket, backupName string) (io.ReadCloser, error)
|
||||
|
||||
// DownloadBackup downloads an Ark backup with the specified object key from object storage via the cloud API.
|
||||
// It returns the snapshot metadata and data (separately), or an error if a problem is encountered
|
||||
// downloading or reading the file from the cloud API.
|
||||
func DownloadBackup(objectStore ObjectStore, bucket, backupName string) (io.ReadCloser, error) {
|
||||
return objectStore.GetObject(bucket, getBackupContentsKey(backupName, backupName))
|
||||
}
|
||||
|
||||
func (br *backupService) GetAllBackups(bucket string) ([]*api.Backup, error) {
|
||||
prefixes, err := br.objectStore.ListCommonPrefixes(bucket, "/")
|
||||
type liveBackupLister struct {
|
||||
logger logrus.FieldLogger
|
||||
objectStore ObjectStore
|
||||
}
|
||||
|
||||
func NewLiveBackupLister(logger logrus.FieldLogger, objectStore ObjectStore) BackupLister {
|
||||
return &liveBackupLister{
|
||||
logger: logger,
|
||||
objectStore: objectStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *liveBackupLister) ListBackups(bucket string) ([]*api.Backup, error) {
|
||||
return ListBackups(l.logger, l.objectStore, bucket)
|
||||
}
|
||||
|
||||
func ListBackups(logger logrus.FieldLogger, objectStore ObjectStore, bucket string) ([]*api.Backup, error) {
|
||||
prefixes, err := objectStore.ListCommonPrefixes(bucket, "/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -191,9 +175,9 @@ func (br *backupService) GetAllBackups(bucket string) ([]*api.Backup, error) {
|
||||
output := make([]*api.Backup, 0, len(prefixes))
|
||||
|
||||
for _, backupDir := range prefixes {
|
||||
backup, err := br.GetBackup(bucket, backupDir)
|
||||
backup, err := GetBackup(objectStore, bucket, backupDir)
|
||||
if err != nil {
|
||||
br.logger.WithError(err).WithField("dir", backupDir).Error("Error reading backup directory")
|
||||
logger.WithError(err).WithField("dir", backupDir).Error("Error reading backup directory")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -203,10 +187,14 @@ func (br *backupService) GetAllBackups(bucket string) ([]*api.Backup, error) {
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (br *backupService) GetBackup(bucket, backupName string) (*api.Backup, error) {
|
||||
//GetBackupFunc is a function that can retrieve backup metadata from an object store
|
||||
type GetBackupFunc func(objectStore ObjectStore, bucket, backupName string) (*api.Backup, error)
|
||||
|
||||
// GetBackup gets the specified api.Backup from the given bucket in object storage.
|
||||
func GetBackup(objectStore ObjectStore, bucket, backupName string) (*api.Backup, error) {
|
||||
key := getMetadataKey(backupName)
|
||||
|
||||
res, err := br.objectStore.GetObject(bucket, key)
|
||||
res, err := objectStore.GetObject(bucket, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -217,7 +205,8 @@ func (br *backupService) GetBackup(bucket, backupName string) (*api.Backup, erro
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
obj, _, err := br.decoder.Decode(data, nil, nil)
|
||||
decoder := scheme.Codecs.UniversalDecoder(api.SchemeGroupVersion)
|
||||
obj, _, err := decoder.Decode(data, nil, nil)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
@@ -230,19 +219,23 @@ func (br *backupService) GetBackup(bucket, backupName string) (*api.Backup, erro
|
||||
return backup, nil
|
||||
}
|
||||
|
||||
func (br *backupService) DeleteBackupDir(bucket, backupName string) error {
|
||||
objects, err := br.objectStore.ListObjects(bucket, backupName+"/")
|
||||
// DeleteBackupDirFunc is a function that can delete a backup directory from a bucket in object storage.
|
||||
type DeleteBackupDirFunc func(logger logrus.FieldLogger, objectStore ObjectStore, bucket, backupName string) error
|
||||
|
||||
// DeleteBackupDir deletes all files in object storage for the given backup.
|
||||
func DeleteBackupDir(logger logrus.FieldLogger, objectStore ObjectStore, bucket, backupName string) error {
|
||||
objects, err := objectStore.ListObjects(bucket, backupName+"/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var errs []error
|
||||
for _, key := range objects {
|
||||
br.logger.WithFields(logrus.Fields{
|
||||
logger.WithFields(logrus.Fields{
|
||||
"bucket": bucket,
|
||||
"key": key,
|
||||
}).Debug("Trying to delete object")
|
||||
if err := br.objectStore.DeleteObject(bucket, key); err != nil {
|
||||
if err := objectStore.DeleteObject(bucket, key); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
@@ -250,51 +243,40 @@ func (br *backupService) DeleteBackupDir(bucket, backupName string) error {
|
||||
return errors.WithStack(kerrors.NewAggregate(errs))
|
||||
}
|
||||
|
||||
func (br *backupService) CreateSignedURL(target api.DownloadTarget, bucket, directory string, ttl time.Duration) (string, error) {
|
||||
// CreateSignedURLFunc is a function that can create a signed URL for an object in object storage.
|
||||
type CreateSignedURLFunc func(objectStore ObjectStore, target api.DownloadTarget, bucket, directory string, ttl time.Duration) (string, error)
|
||||
|
||||
// CreateSignedURL creates a pre-signed URL that can be used to download a file from object
|
||||
// storage. The URL expires after ttl.
|
||||
func CreateSignedURL(objectStore ObjectStore, target api.DownloadTarget, bucket, directory string, ttl time.Duration) (string, error) {
|
||||
switch target.Kind {
|
||||
case api.DownloadTargetKindBackupContents:
|
||||
return br.objectStore.CreateSignedURL(bucket, getBackupContentsKey(directory, target.Name), ttl)
|
||||
return objectStore.CreateSignedURL(bucket, getBackupContentsKey(directory, target.Name), ttl)
|
||||
case api.DownloadTargetKindBackupLog:
|
||||
return br.objectStore.CreateSignedURL(bucket, getBackupLogKey(directory, target.Name), ttl)
|
||||
return objectStore.CreateSignedURL(bucket, getBackupLogKey(directory, target.Name), ttl)
|
||||
case api.DownloadTargetKindRestoreLog:
|
||||
return br.objectStore.CreateSignedURL(bucket, getRestoreLogKey(directory, target.Name), ttl)
|
||||
return objectStore.CreateSignedURL(bucket, getRestoreLogKey(directory, target.Name), ttl)
|
||||
case api.DownloadTargetKindRestoreResults:
|
||||
return br.objectStore.CreateSignedURL(bucket, getRestoreResultsKey(directory, target.Name), ttl)
|
||||
return objectStore.CreateSignedURL(bucket, getRestoreResultsKey(directory, target.Name), ttl)
|
||||
default:
|
||||
return "", errors.Errorf("unsupported download target kind %q", target.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func (br *backupService) UploadRestoreLog(bucket, backup, restore string, log io.Reader) error {
|
||||
// UploadRestoreLogFunc is a function that can upload a restore log to a bucket in object storage.
|
||||
type UploadRestoreLogFunc func(objectStore ObjectStore, bucket, backup, restore string, log io.Reader) error
|
||||
|
||||
// UploadRestoreLog uploads the restore's log file to object storage.
|
||||
func UploadRestoreLog(objectStore ObjectStore, bucket, backup, restore string, log io.Reader) error {
|
||||
key := getRestoreLogKey(backup, restore)
|
||||
return br.objectStore.PutObject(bucket, key, log)
|
||||
return objectStore.PutObject(bucket, key, log)
|
||||
}
|
||||
|
||||
func (br *backupService) UploadRestoreResults(bucket, backup, restore string, results io.Reader) error {
|
||||
// UploadRestoreResultsFunc is a function that can upload restore results to a bucket in object storage.
|
||||
type UploadRestoreResultsFunc func(objectStore ObjectStore, bucket, backup, restore string, results io.Reader) error
|
||||
|
||||
// UploadRestoreResults uploads the restore's results file to object storage.
|
||||
func UploadRestoreResults(objectStore ObjectStore, bucket, backup, restore string, results io.Reader) error {
|
||||
key := getRestoreResultsKey(backup, restore)
|
||||
return br.objectStore.PutObject(bucket, key, results)
|
||||
}
|
||||
|
||||
// cachedBackupService wraps a real backup service with a cache for getting cloud backups.
|
||||
type cachedBackupService struct {
|
||||
BackupService
|
||||
cache BackupGetter
|
||||
}
|
||||
|
||||
// NewBackupServiceWithCachedBackupGetter returns a BackupService that uses a cache for
|
||||
// GetAllBackups().
|
||||
func NewBackupServiceWithCachedBackupGetter(
|
||||
ctx context.Context,
|
||||
delegate BackupService,
|
||||
resyncPeriod time.Duration,
|
||||
logger logrus.FieldLogger,
|
||||
) BackupService {
|
||||
return &cachedBackupService{
|
||||
BackupService: delegate,
|
||||
cache: NewBackupCache(ctx, delegate, resyncPeriod, logger),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cachedBackupService) GetAllBackups(bucketName string) ([]*api.Backup, error) {
|
||||
return c.cache.GetAllBackups(bucketName)
|
||||
return objectStore.PutObject(bucket, key, results)
|
||||
}
|
||||
|
||||
@@ -92,29 +92,27 @@ func TestUploadBackup(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
objStore = &testutil.ObjectStore{}
|
||||
bucket = "test-bucket"
|
||||
backupName = "test-backup"
|
||||
logger = arktest.NewLogger()
|
||||
objectStore = &testutil.ObjectStore{}
|
||||
bucket = "test-bucket"
|
||||
backupName = "test-backup"
|
||||
logger = arktest.NewLogger()
|
||||
)
|
||||
defer objStore.AssertExpectations(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
|
||||
if test.metadata != nil {
|
||||
objStore.On("PutObject", bucket, backupName+"/ark-backup.json", test.metadata).Return(test.metadataError)
|
||||
objectStore.On("PutObject", bucket, backupName+"/ark-backup.json", test.metadata).Return(test.metadataError)
|
||||
}
|
||||
if test.backup != nil && test.expectBackupUpload {
|
||||
objStore.On("PutObject", bucket, backupName+"/"+backupName+".tar.gz", test.backup).Return(test.backupError)
|
||||
objectStore.On("PutObject", bucket, backupName+"/"+backupName+".tar.gz", test.backup).Return(test.backupError)
|
||||
}
|
||||
if test.log != nil {
|
||||
objStore.On("PutObject", bucket, backupName+"/"+backupName+"-logs.gz", test.log).Return(test.logError)
|
||||
objectStore.On("PutObject", bucket, backupName+"/"+backupName+"-logs.gz", test.log).Return(test.logError)
|
||||
}
|
||||
if test.expectMetadataDelete {
|
||||
objStore.On("DeleteObject", bucket, backupName+"/ark-backup.json").Return(nil)
|
||||
objectStore.On("DeleteObject", bucket, backupName+"/ark-backup.json").Return(nil)
|
||||
}
|
||||
|
||||
backupService := NewBackupService(objStore, logger)
|
||||
|
||||
err := backupService.UploadBackup(bucket, backupName, test.metadata, test.backup, test.log)
|
||||
err := UploadBackup(logger, objectStore, bucket, backupName, test.metadata, test.backup, test.log)
|
||||
|
||||
if test.expectedErr != "" {
|
||||
assert.EqualError(t, err, test.expectedErr)
|
||||
@@ -128,21 +126,19 @@ func TestUploadBackup(t *testing.T) {
|
||||
|
||||
func TestDownloadBackup(t *testing.T) {
|
||||
var (
|
||||
o = &testutil.ObjectStore{}
|
||||
bucket = "b"
|
||||
backup = "bak"
|
||||
logger = arktest.NewLogger()
|
||||
objectStore = &testutil.ObjectStore{}
|
||||
bucket = "b"
|
||||
backup = "bak"
|
||||
)
|
||||
o.On("GetObject", bucket, backup+"/"+backup+".tar.gz").Return(ioutil.NopCloser(strings.NewReader("foo")), nil)
|
||||
objectStore.On("GetObject", bucket, backup+"/"+backup+".tar.gz").Return(ioutil.NopCloser(strings.NewReader("foo")), nil)
|
||||
|
||||
s := NewBackupService(o, logger)
|
||||
rc, err := s.DownloadBackup(bucket, backup)
|
||||
rc, err := DownloadBackup(objectStore, bucket, backup)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, rc)
|
||||
data, err := ioutil.ReadAll(rc)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "foo", string(data))
|
||||
o.AssertExpectations(t)
|
||||
objectStore.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestDeleteBackup(t *testing.T) {
|
||||
@@ -165,26 +161,24 @@ func TestDeleteBackup(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
bucket = "bucket"
|
||||
backup = "bak"
|
||||
objects = []string{"bak/ark-backup.json", "bak/bak.tar.gz", "bak/bak.log.gz"}
|
||||
objStore = &testutil.ObjectStore{}
|
||||
logger = arktest.NewLogger()
|
||||
bucket = "bucket"
|
||||
backup = "bak"
|
||||
objects = []string{"bak/ark-backup.json", "bak/bak.tar.gz", "bak/bak.log.gz"}
|
||||
objectStore = &testutil.ObjectStore{}
|
||||
logger = arktest.NewLogger()
|
||||
)
|
||||
|
||||
objStore.On("ListObjects", bucket, backup+"/").Return(objects, test.listObjectsError)
|
||||
for i, o := range objects {
|
||||
objectStore.On("ListObjects", bucket, backup+"/").Return(objects, test.listObjectsError)
|
||||
for i, obj := range objects {
|
||||
var err error
|
||||
if i < len(test.deleteErrors) {
|
||||
err = test.deleteErrors[i]
|
||||
}
|
||||
|
||||
objStore.On("DeleteObject", bucket, o).Return(err)
|
||||
objectStore.On("DeleteObject", bucket, obj).Return(err)
|
||||
}
|
||||
|
||||
backupService := NewBackupService(objStore, logger)
|
||||
|
||||
err := backupService.DeleteBackupDir(bucket, backup)
|
||||
err := DeleteBackupDir(logger, objectStore, bucket, backup)
|
||||
|
||||
if test.expectedErr != "" {
|
||||
assert.EqualError(t, err, test.expectedErr)
|
||||
@@ -192,7 +186,7 @@ func TestDeleteBackup(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
objStore.AssertExpectations(t)
|
||||
objectStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -239,18 +233,16 @@ func TestGetAllBackups(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
bucket = "bucket"
|
||||
objStore = &testutil.ObjectStore{}
|
||||
logger = arktest.NewLogger()
|
||||
bucket = "bucket"
|
||||
objectStore = &testutil.ObjectStore{}
|
||||
logger = arktest.NewLogger()
|
||||
)
|
||||
|
||||
objStore.On("ListCommonPrefixes", bucket, "/").Return([]string{"backup-1", "backup-2"}, nil)
|
||||
objStore.On("GetObject", bucket, "backup-1/ark-backup.json").Return(ioutil.NopCloser(bytes.NewReader(test.storageData["backup-1/ark-backup.json"])), nil)
|
||||
objStore.On("GetObject", bucket, "backup-2/ark-backup.json").Return(ioutil.NopCloser(bytes.NewReader(test.storageData["backup-2/ark-backup.json"])), nil)
|
||||
objectStore.On("ListCommonPrefixes", bucket, "/").Return([]string{"backup-1", "backup-2"}, nil)
|
||||
objectStore.On("GetObject", bucket, "backup-1/ark-backup.json").Return(ioutil.NopCloser(bytes.NewReader(test.storageData["backup-1/ark-backup.json"])), nil)
|
||||
objectStore.On("GetObject", bucket, "backup-2/ark-backup.json").Return(ioutil.NopCloser(bytes.NewReader(test.storageData["backup-2/ark-backup.json"])), nil)
|
||||
|
||||
backupService := NewBackupService(objStore, logger)
|
||||
|
||||
res, err := backupService.GetAllBackups(bucket)
|
||||
res, err := ListBackups(logger, objectStore, bucket)
|
||||
|
||||
if test.expectedErr != "" {
|
||||
assert.EqualError(t, err, test.expectedErr)
|
||||
@@ -260,7 +252,7 @@ func TestGetAllBackups(t *testing.T) {
|
||||
|
||||
assert.Equal(t, test.expectedRes, res)
|
||||
|
||||
objStore.AssertExpectations(t)
|
||||
objectStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -327,20 +319,18 @@ func TestCreateSignedURL(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
objectStorage = &testutil.ObjectStore{}
|
||||
logger = arktest.NewLogger()
|
||||
backupService = NewBackupService(objectStorage, logger)
|
||||
objectStore = &testutil.ObjectStore{}
|
||||
)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
|
||||
target := api.DownloadTarget{
|
||||
Kind: test.targetKind,
|
||||
Name: test.targetName,
|
||||
}
|
||||
objectStorage.On("CreateSignedURL", "bucket", test.expectedKey, time.Duration(0)).Return("url", nil)
|
||||
url, err := backupService.CreateSignedURL(target, "bucket", test.directory, 0)
|
||||
objectStore.On("CreateSignedURL", "bucket", test.expectedKey, time.Duration(0)).Return("url", nil)
|
||||
url, err := CreateSignedURL(objectStore, target, "bucket", test.directory, 0)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "url", url)
|
||||
objectStorage.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ type blockStore struct {
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
func NewBlockStore(log logrus.FieldLogger) cloudprovider.BlockStore {
|
||||
return &blockStore{log: log}
|
||||
func NewBlockStore(logger logrus.FieldLogger) cloudprovider.BlockStore {
|
||||
return &blockStore{log: logger}
|
||||
}
|
||||
|
||||
func (b *blockStore) Init(config map[string]string) error {
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/oauth2/google"
|
||||
"google.golang.org/api/iterator"
|
||||
"google.golang.org/api/option"
|
||||
@@ -50,14 +51,15 @@ func (w *writer) getWriteCloser(bucket, key string) io.WriteCloser {
|
||||
}
|
||||
|
||||
type objectStore struct {
|
||||
log logrus.FieldLogger
|
||||
client *storage.Client
|
||||
googleAccessID string
|
||||
privateKey []byte
|
||||
bucketWriter bucketWriter
|
||||
}
|
||||
|
||||
func NewObjectStore() cloudprovider.ObjectStore {
|
||||
return &objectStore{}
|
||||
func NewObjectStore(logger logrus.FieldLogger) cloudprovider.ObjectStore {
|
||||
return &objectStore{log: logger}
|
||||
}
|
||||
|
||||
func (o *objectStore) Init(config map[string]string) error {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -87,7 +88,7 @@ func TestPutObject(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
wc := newMockWriteCloser(test.writeErr, test.closeErr)
|
||||
o := NewObjectStore().(*objectStore)
|
||||
o := NewObjectStore(arktest.NewLogger()).(*objectStore)
|
||||
o.bucketWriter = newFakeWriter(wc)
|
||||
|
||||
err := o.PutObject("bucket", "key", strings.NewReader("contents"))
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
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 mockery v1.0.0. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import v1 "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
|
||||
// BackupLister is an autogenerated mock type for the BackupLister type
|
||||
type BackupLister struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// ListBackups provides a mock function with given fields: bucket
|
||||
func (_m *BackupLister) ListBackups(bucket string) ([]*v1.Backup, error) {
|
||||
ret := _m.Called(bucket)
|
||||
|
||||
var r0 []*v1.Backup
|
||||
if rf, ok := ret.Get(0).(func(string) []*v1.Backup); ok {
|
||||
r0 = rf(bucket)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*v1.Backup)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(bucket)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
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 mockery v1.0.0. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
// BlockStore is an autogenerated mock type for the BlockStore type
|
||||
type BlockStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CreateSnapshot provides a mock function with given fields: volumeID, volumeAZ, tags
|
||||
func (_m *BlockStore) CreateSnapshot(volumeID string, volumeAZ string, tags map[string]string) (string, error) {
|
||||
ret := _m.Called(volumeID, volumeAZ, tags)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string, string, map[string]string) string); ok {
|
||||
r0 = rf(volumeID, volumeAZ, tags)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, map[string]string) error); ok {
|
||||
r1 = rf(volumeID, volumeAZ, tags)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CreateVolumeFromSnapshot provides a mock function with given fields: snapshotID, volumeType, volumeAZ, iops
|
||||
func (_m *BlockStore) CreateVolumeFromSnapshot(snapshotID string, volumeType string, volumeAZ string, iops *int64) (string, error) {
|
||||
ret := _m.Called(snapshotID, volumeType, volumeAZ, iops)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, *int64) string); ok {
|
||||
r0 = rf(snapshotID, volumeType, volumeAZ, iops)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string, string, *int64) error); ok {
|
||||
r1 = rf(snapshotID, volumeType, volumeAZ, iops)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteSnapshot provides a mock function with given fields: snapshotID
|
||||
func (_m *BlockStore) DeleteSnapshot(snapshotID string) error {
|
||||
ret := _m.Called(snapshotID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(snapshotID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetVolumeID provides a mock function with given fields: pv
|
||||
func (_m *BlockStore) GetVolumeID(pv runtime.Unstructured) (string, error) {
|
||||
ret := _m.Called(pv)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(runtime.Unstructured) string); ok {
|
||||
r0 = rf(pv)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(runtime.Unstructured) error); ok {
|
||||
r1 = rf(pv)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetVolumeInfo provides a mock function with given fields: volumeID, volumeAZ
|
||||
func (_m *BlockStore) GetVolumeInfo(volumeID string, volumeAZ string) (string, *int64, error) {
|
||||
ret := _m.Called(volumeID, volumeAZ)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(string, string) string); ok {
|
||||
r0 = rf(volumeID, volumeAZ)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 *int64
|
||||
if rf, ok := ret.Get(1).(func(string, string) *int64); ok {
|
||||
r1 = rf(volumeID, volumeAZ)
|
||||
} else {
|
||||
if ret.Get(1) != nil {
|
||||
r1 = ret.Get(1).(*int64)
|
||||
}
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(string, string) error); ok {
|
||||
r2 = rf(volumeID, volumeAZ)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// Init provides a mock function with given fields: config
|
||||
func (_m *BlockStore) Init(config map[string]string) error {
|
||||
ret := _m.Called(config)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(map[string]string) error); ok {
|
||||
r0 = rf(config)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// IsVolumeReady provides a mock function with given fields: volumeID, volumeAZ
|
||||
func (_m *BlockStore) IsVolumeReady(volumeID string, volumeAZ string) (bool, error) {
|
||||
ret := _m.Called(volumeID, volumeAZ)
|
||||
|
||||
var r0 bool
|
||||
if rf, ok := ret.Get(0).(func(string, string) bool); ok {
|
||||
r0 = rf(volumeID, volumeAZ)
|
||||
} else {
|
||||
r0 = ret.Get(0).(bool)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(volumeID, volumeAZ)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetVolumeID provides a mock function with given fields: pv, volumeID
|
||||
func (_m *BlockStore) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) {
|
||||
ret := _m.Called(pv, volumeID)
|
||||
|
||||
var r0 runtime.Unstructured
|
||||
if rf, ok := ret.Get(0).(func(runtime.Unstructured, string) runtime.Unstructured); ok {
|
||||
r0 = rf(pv, volumeID)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(runtime.Unstructured)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(runtime.Unstructured, string) error); ok {
|
||||
r1 = rf(pv, volumeID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 restic
|
||||
|
||||
import (
|
||||
|
||||
@@ -17,17 +17,14 @@ limitations under the License.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
"github.com/heptio/ark/pkg/client"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/cloudprovider/aws"
|
||||
"github.com/heptio/ark/pkg/cloudprovider/azure"
|
||||
"github.com/heptio/ark/pkg/cloudprovider/gcp"
|
||||
"github.com/heptio/ark/pkg/cmd"
|
||||
arkdiscovery "github.com/heptio/ark/pkg/discovery"
|
||||
arkplugin "github.com/heptio/ark/pkg/plugin"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
@@ -37,98 +34,102 @@ func NewCommand(f client.Factory) *cobra.Command {
|
||||
logger := arkplugin.NewLogger()
|
||||
|
||||
c := &cobra.Command{
|
||||
Use: "run-plugin [KIND] [NAME]",
|
||||
Use: "run-plugins",
|
||||
Hidden: true,
|
||||
Short: "INTERNAL COMMAND ONLY - not intended to be run directly by users",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(c *cobra.Command, args []string) {
|
||||
kind := args[0]
|
||||
name := args[1]
|
||||
logger.Debug("Executing run-plugins command")
|
||||
|
||||
logger = logger.WithFields(logrus.Fields{"kind": kind, "name": name})
|
||||
|
||||
serveConfig := &plugin.ServeConfig{
|
||||
HandshakeConfig: arkplugin.Handshake,
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
}
|
||||
|
||||
logger.Debug("Executing run-plugin command")
|
||||
|
||||
switch kind {
|
||||
case "cloudprovider":
|
||||
var (
|
||||
objectStore cloudprovider.ObjectStore
|
||||
blockStore cloudprovider.BlockStore
|
||||
)
|
||||
|
||||
switch name {
|
||||
case "aws":
|
||||
objectStore, blockStore = aws.NewObjectStore(), aws.NewBlockStore()
|
||||
case "azure":
|
||||
objectStore, blockStore = azure.NewObjectStore(), azure.NewBlockStore()
|
||||
case "gcp":
|
||||
objectStore, blockStore = gcp.NewObjectStore(), gcp.NewBlockStore(logger)
|
||||
default:
|
||||
logger.Fatal("Unrecognized plugin name")
|
||||
}
|
||||
|
||||
serveConfig.Plugins = map[string]plugin.Plugin{
|
||||
string(arkplugin.PluginKindObjectStore): arkplugin.NewObjectStorePlugin(objectStore),
|
||||
string(arkplugin.PluginKindBlockStore): arkplugin.NewBlockStorePlugin(blockStore),
|
||||
}
|
||||
case arkplugin.PluginKindBackupItemAction.String():
|
||||
var action backup.ItemAction
|
||||
|
||||
switch name {
|
||||
case "pv":
|
||||
action = backup.NewBackupPVAction(logger)
|
||||
case "pod":
|
||||
action = backup.NewPodAction(logger)
|
||||
case "serviceaccount":
|
||||
clientset, err := f.KubeClient()
|
||||
cmd.CheckError(err)
|
||||
|
||||
discoveryHelper, err := arkdiscovery.NewHelper(clientset.Discovery(), logger)
|
||||
cmd.CheckError(err)
|
||||
|
||||
action, err = backup.NewServiceAccountAction(
|
||||
logger,
|
||||
backup.NewClusterRoleBindingListerMap(clientset),
|
||||
discoveryHelper)
|
||||
cmd.CheckError(err)
|
||||
default:
|
||||
logger.Fatal("Unrecognized plugin name")
|
||||
}
|
||||
|
||||
serveConfig.Plugins = map[string]plugin.Plugin{
|
||||
kind: arkplugin.NewBackupItemActionPlugin(action),
|
||||
}
|
||||
case arkplugin.PluginKindRestoreItemAction.String():
|
||||
var action restore.ItemAction
|
||||
|
||||
switch name {
|
||||
case "job":
|
||||
action = restore.NewJobAction(logger)
|
||||
case "pod":
|
||||
action = restore.NewPodAction(logger)
|
||||
case "svc":
|
||||
action = restore.NewServiceAction(logger)
|
||||
case "restic":
|
||||
action = restore.NewResticRestoreAction(logger)
|
||||
default:
|
||||
logger.Fatal("Unrecognized plugin name")
|
||||
}
|
||||
|
||||
serveConfig.Plugins = map[string]plugin.Plugin{
|
||||
kind: arkplugin.NewRestoreItemActionPlugin(action),
|
||||
}
|
||||
default:
|
||||
logger.Fatal("Unsupported plugin kind")
|
||||
}
|
||||
|
||||
plugin.Serve(serveConfig)
|
||||
arkplugin.NewServer(logger).
|
||||
RegisterObjectStore("aws", newAwsObjectStore).
|
||||
RegisterObjectStore("azure", newAzureObjectStore).
|
||||
RegisterObjectStore("gcp", newGcpObjectStore).
|
||||
RegisterBlockStore("aws", newAwsBlockStore).
|
||||
RegisterBlockStore("azure", newAzureBlockStore).
|
||||
RegisterBlockStore("gcp", newGcpBlockStore).
|
||||
RegisterBackupItemAction("pv", newPVBackupItemAction).
|
||||
RegisterBackupItemAction("pod", newPodBackupItemAction).
|
||||
RegisterBackupItemAction("serviceaccount", newServiceAccountBackupItemAction(f)).
|
||||
RegisterRestoreItemAction("job", newJobRestoreItemAction).
|
||||
RegisterRestoreItemAction("pod", newPodRestoreItemAction).
|
||||
RegisterRestoreItemAction("restic", newResticRestoreItemAction).
|
||||
RegisterRestoreItemAction("service", newServiceRestoreItemAction).
|
||||
Serve()
|
||||
},
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func newAwsObjectStore(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return aws.NewObjectStore(logger), nil
|
||||
}
|
||||
|
||||
func newAzureObjectStore(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return azure.NewObjectStore(logger), nil
|
||||
}
|
||||
|
||||
func newGcpObjectStore(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return gcp.NewObjectStore(logger), nil
|
||||
}
|
||||
|
||||
func newAwsBlockStore(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return aws.NewBlockStore(logger), nil
|
||||
}
|
||||
|
||||
func newAzureBlockStore(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return azure.NewBlockStore(logger), nil
|
||||
}
|
||||
|
||||
func newGcpBlockStore(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return gcp.NewBlockStore(logger), nil
|
||||
}
|
||||
|
||||
func newPVBackupItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return backup.NewBackupPVAction(logger), nil
|
||||
}
|
||||
|
||||
func newPodBackupItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return backup.NewPodAction(logger), nil
|
||||
}
|
||||
|
||||
func newServiceAccountBackupItemAction(f client.Factory) arkplugin.HandlerInitializer {
|
||||
return func(logger logrus.FieldLogger) (interface{}, error) {
|
||||
// TODO(ncdc): consider a k8s style WantsKubernetesClientSet initialization approach
|
||||
clientset, err := f.KubeClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discoveryHelper, err := arkdiscovery.NewHelper(clientset.Discovery(), logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
action, err := backup.NewServiceAccountAction(
|
||||
logger,
|
||||
backup.NewClusterRoleBindingListerMap(clientset),
|
||||
discoveryHelper)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return action, nil
|
||||
}
|
||||
}
|
||||
|
||||
func newJobRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewJobAction(logger), nil
|
||||
}
|
||||
|
||||
func newPodRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewPodAction(logger), nil
|
||||
}
|
||||
|
||||
func newResticRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewResticRestoreAction(logger), nil
|
||||
}
|
||||
|
||||
func newServiceRestoreItemAction(logger logrus.FieldLogger) (interface{}, error) {
|
||||
return restore.NewServiceAction(logger), nil
|
||||
}
|
||||
|
||||
+29
-33
@@ -149,7 +149,6 @@ type server struct {
|
||||
kubeClient kubernetes.Interface
|
||||
arkClient clientset.Interface
|
||||
objectStore cloudprovider.ObjectStore
|
||||
backupService cloudprovider.BackupService
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
discoveryClient discovery.DiscoveryInterface
|
||||
discoveryHelper arkdiscovery.Helper
|
||||
@@ -158,6 +157,8 @@ type server struct {
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
pluginRegistry plugin.Registry
|
||||
pluginManager plugin.Manager
|
||||
resticManager restic.RepositoryManager
|
||||
metrics *metrics.ServerMetrics
|
||||
@@ -179,7 +180,11 @@ func newServer(namespace, baseName, pluginDir, metricsAddr string, logger *logru
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
pluginManager, err := plugin.NewManager(logger, logger.Level, pluginDir)
|
||||
pluginRegistry := plugin.NewRegistry(pluginDir, logger, logger.Level)
|
||||
if err := pluginRegistry.DiscoverPlugins(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pluginManager := plugin.NewManager(logger, logger.Level, pluginRegistry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -200,10 +205,12 @@ func newServer(namespace, baseName, pluginDir, metricsAddr string, logger *logru
|
||||
discoveryClient: arkClient.Discovery(),
|
||||
dynamicClient: dynamicClient,
|
||||
sharedInformerFactory: informers.NewFilteredSharedInformerFactory(arkClient, 0, namespace, nil),
|
||||
ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
logger: logger,
|
||||
pluginManager: pluginManager,
|
||||
ctx: ctx,
|
||||
cancelFunc: cancelFunc,
|
||||
logger: logger,
|
||||
logLevel: logger.Level,
|
||||
pluginRegistry: pluginRegistry,
|
||||
pluginManager: pluginManager,
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@@ -242,9 +249,11 @@ func (s *server) run() error {
|
||||
|
||||
s.watchConfig(originalConfig)
|
||||
|
||||
if err := s.initBackupService(config); err != nil {
|
||||
objectStore, err := getObjectStore(config.BackupStorageProvider.CloudProviderConfig, s.pluginManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.objectStore = objectStore
|
||||
|
||||
if err := s.initSnapshotService(config); err != nil {
|
||||
return err
|
||||
@@ -455,18 +464,6 @@ func (s *server) watchConfig(config *api.Config) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *server) initBackupService(config *api.Config) error {
|
||||
s.logger.Info("Configuring cloud provider for backup service")
|
||||
objectStore, err := getObjectStore(config.BackupStorageProvider.CloudProviderConfig, s.pluginManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.objectStore = objectStore
|
||||
s.backupService = cloudprovider.NewBackupService(objectStore, s.logger)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) initSnapshotService(config *api.Config) error {
|
||||
if config.PersistentVolumeProvider == nil {
|
||||
s.logger.Info("PersistentVolumeProvider config not provided, volume snapshots and restores are disabled")
|
||||
@@ -584,12 +581,9 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
|
||||
cloudBackupCacheResyncPeriod := durationMin(config.GCSyncPeriod.Duration, config.BackupSyncPeriod.Duration)
|
||||
s.logger.Infof("Caching cloud backups every %s", cloudBackupCacheResyncPeriod)
|
||||
s.backupService = cloudprovider.NewBackupServiceWithCachedBackupGetter(
|
||||
ctx,
|
||||
s.backupService,
|
||||
cloudBackupCacheResyncPeriod,
|
||||
s.logger,
|
||||
)
|
||||
|
||||
liveBackupLister := cloudprovider.NewLiveBackupLister(s.logger, s.objectStore)
|
||||
cachedBackupLister := cloudprovider.NewBackupCache(ctx, liveBackupLister, cloudBackupCacheResyncPeriod, s.logger)
|
||||
|
||||
go func() {
|
||||
metricsMux := http.NewServeMux()
|
||||
@@ -604,7 +598,7 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
|
||||
backupSyncController := controller.NewBackupSyncController(
|
||||
s.arkClient.ArkV1(),
|
||||
s.backupService,
|
||||
cachedBackupLister,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
config.BackupSyncPeriod.Duration,
|
||||
s.namespace,
|
||||
@@ -636,11 +630,12 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
s.sharedInformerFactory.Ark().V1().Backups(),
|
||||
s.arkClient.ArkV1(),
|
||||
backupper,
|
||||
s.backupService,
|
||||
config.BackupStorageProvider.CloudProviderConfig,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.snapshotService != nil,
|
||||
s.logger,
|
||||
s.pluginManager,
|
||||
s.logLevel,
|
||||
s.pluginRegistry,
|
||||
backupTracker,
|
||||
s.metrics,
|
||||
)
|
||||
@@ -684,7 +679,7 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
s.arkClient.ArkV1(), // deleteBackupRequestClient
|
||||
s.arkClient.ArkV1(), // backupClient
|
||||
s.snapshotService,
|
||||
s.backupService,
|
||||
s.objectStore,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.sharedInformerFactory.Ark().V1().Restores(),
|
||||
s.arkClient.ArkV1(), // restoreClient
|
||||
@@ -703,7 +698,6 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
restorer, err := restore.NewKubernetesRestorer(
|
||||
s.discoveryHelper,
|
||||
client.NewDynamicFactory(s.dynamicClient),
|
||||
s.backupService,
|
||||
s.snapshotService,
|
||||
config.ResourcePriorities,
|
||||
s.arkClient.ArkV1(),
|
||||
@@ -720,14 +714,16 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
s.arkClient.ArkV1(),
|
||||
s.arkClient.ArkV1(),
|
||||
restorer,
|
||||
s.backupService,
|
||||
config.BackupStorageProvider.CloudProviderConfig,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.sharedInformerFactory.Ark().V1().Backups(),
|
||||
s.snapshotService != nil,
|
||||
s.logger,
|
||||
s.pluginManager,
|
||||
s.logLevel,
|
||||
s.pluginRegistry,
|
||||
s.metrics,
|
||||
)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
restoreController.Run(ctx, 1)
|
||||
@@ -738,7 +734,7 @@ func (s *server) runControllers(config *api.Config) error {
|
||||
s.arkClient.ArkV1(),
|
||||
s.sharedInformerFactory.Ark().V1().DownloadRequests(),
|
||||
s.sharedInformerFactory.Ark().V1().Restores(),
|
||||
s.backupService,
|
||||
s.objectStore,
|
||||
config.BackupStorageProvider.Bucket,
|
||||
s.logger,
|
||||
)
|
||||
|
||||
@@ -18,6 +18,7 @@ package controller
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -50,53 +51,63 @@ import (
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
"github.com/heptio/ark/pkg/util/encode"
|
||||
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
)
|
||||
|
||||
const backupVersion = 1
|
||||
|
||||
type backupController struct {
|
||||
backupper backup.Backupper
|
||||
backupService cloudprovider.BackupService
|
||||
bucket string
|
||||
pvProviderExists bool
|
||||
lister listers.BackupLister
|
||||
listerSynced cache.InformerSynced
|
||||
client arkv1client.BackupsGetter
|
||||
syncHandler func(backupName string) error
|
||||
queue workqueue.RateLimitingInterface
|
||||
clock clock.Clock
|
||||
logger logrus.FieldLogger
|
||||
pluginManager plugin.Manager
|
||||
backupTracker BackupTracker
|
||||
metrics *metrics.ServerMetrics
|
||||
backupper backup.Backupper
|
||||
objectStoreConfig api.CloudProviderConfig
|
||||
bucket string
|
||||
pvProviderExists bool
|
||||
lister listers.BackupLister
|
||||
listerSynced cache.InformerSynced
|
||||
client arkv1client.BackupsGetter
|
||||
syncHandler func(backupName string) error
|
||||
queue workqueue.RateLimitingInterface
|
||||
clock clock.Clock
|
||||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
pluginRegistry plugin.Registry
|
||||
backupTracker BackupTracker
|
||||
metrics *metrics.ServerMetrics
|
||||
|
||||
newPluginManager func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager
|
||||
}
|
||||
|
||||
func NewBackupController(
|
||||
backupInformer informers.BackupInformer,
|
||||
client arkv1client.BackupsGetter,
|
||||
backupper backup.Backupper,
|
||||
backupService cloudprovider.BackupService,
|
||||
objectStoreConfig api.CloudProviderConfig,
|
||||
bucket string,
|
||||
pvProviderExists bool,
|
||||
logger logrus.FieldLogger,
|
||||
pluginManager plugin.Manager,
|
||||
logLevel logrus.Level,
|
||||
pluginRegistry plugin.Registry,
|
||||
backupTracker BackupTracker,
|
||||
metrics *metrics.ServerMetrics,
|
||||
) Interface {
|
||||
c := &backupController{
|
||||
backupper: backupper,
|
||||
backupService: backupService,
|
||||
bucket: bucket,
|
||||
pvProviderExists: pvProviderExists,
|
||||
lister: backupInformer.Lister(),
|
||||
listerSynced: backupInformer.Informer().HasSynced,
|
||||
client: client,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "backup"),
|
||||
clock: &clock.RealClock{},
|
||||
logger: logger,
|
||||
pluginManager: pluginManager,
|
||||
backupTracker: backupTracker,
|
||||
metrics: metrics,
|
||||
backupper: backupper,
|
||||
objectStoreConfig: objectStoreConfig,
|
||||
bucket: bucket,
|
||||
pvProviderExists: pvProviderExists,
|
||||
lister: backupInformer.Lister(),
|
||||
listerSynced: backupInformer.Informer().HasSynced,
|
||||
client: client,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "backup"),
|
||||
clock: &clock.RealClock{},
|
||||
logger: logger,
|
||||
logLevel: logLevel,
|
||||
pluginRegistry: pluginRegistry,
|
||||
backupTracker: backupTracker,
|
||||
metrics: metrics,
|
||||
|
||||
newPluginManager: func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager {
|
||||
return plugin.NewManager(logger, logLevel, pluginRegistry)
|
||||
},
|
||||
}
|
||||
|
||||
c.syncHandler = c.processBackup
|
||||
@@ -343,7 +354,22 @@ func (controller *backupController) runBackup(backup *api.Backup, bucket string)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error creating temp file for backup log")
|
||||
}
|
||||
defer closeAndRemoveFile(logFile, log)
|
||||
gzippedLogFile := gzip.NewWriter(logFile)
|
||||
// Assuming we successfully uploaded the log file, this will have already been closed below. It is safe to call
|
||||
// close multiple times. If we get an error closing this, there's not really anything we can do about it.
|
||||
defer gzippedLogFile.Close()
|
||||
defer closeAndRemoveFile(logFile, controller.logger)
|
||||
|
||||
// Log the backup to both a backup log file and to stdout. This will help see what happened if the upload of the
|
||||
// backup log failed for whatever reason.
|
||||
logger := logging.DefaultLogger(controller.logLevel)
|
||||
logger.Out = io.MultiWriter(os.Stdout, gzippedLogFile)
|
||||
log = logger.WithField("backup", kubeutil.NamespaceAndName(backup))
|
||||
|
||||
log.Info("Starting backup")
|
||||
|
||||
pluginManager := controller.newPluginManager(log, log.Level, controller.pluginRegistry)
|
||||
defer pluginManager.CleanupClients()
|
||||
|
||||
backupFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
@@ -351,18 +377,22 @@ func (controller *backupController) runBackup(backup *api.Backup, bucket string)
|
||||
}
|
||||
defer closeAndRemoveFile(backupFile, log)
|
||||
|
||||
actions, err := controller.pluginManager.GetBackupItemActions(backup.Name)
|
||||
actions, err := pluginManager.GetBackupItemActions()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectStore, err := getObjectStore(controller.objectStoreConfig, pluginManager)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer controller.pluginManager.CloseBackupItemActions(backup.Name)
|
||||
|
||||
var errs []error
|
||||
|
||||
var backupJSONToUpload, backupFileToUpload io.Reader
|
||||
|
||||
// Do the actual backup
|
||||
if err := controller.backupper.Backup(backup, backupFile, logFile, actions); err != nil {
|
||||
if err := controller.backupper.Backup(log, backup, backupFile, actions); err != nil {
|
||||
errs = append(errs, err)
|
||||
|
||||
backup.Status.Phase = api.BackupPhaseFailed
|
||||
@@ -390,7 +420,11 @@ func (controller *backupController) runBackup(backup *api.Backup, bucket string)
|
||||
backupSizeBytes = backupFileStat.Size()
|
||||
}
|
||||
|
||||
if err := controller.backupService.UploadBackup(bucket, backup.Name, backupJSONToUpload, backupFileToUpload, logFile); err != nil {
|
||||
if err := gzippedLogFile.Close(); err != nil {
|
||||
controller.logger.WithError(err).Error("error closing gzippedLogFile")
|
||||
}
|
||||
|
||||
if err := cloudprovider.UploadBackup(log, objectStore, bucket, backup.Name, backupJSONToUpload, backupFileToUpload, logFile); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
@@ -406,6 +440,24 @@ func (controller *backupController) runBackup(backup *api.Backup, bucket string)
|
||||
return kerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// TODO(ncdc): move this to a better location that isn't backup specific
|
||||
func getObjectStore(cloudConfig api.CloudProviderConfig, manager plugin.Manager) (cloudprovider.ObjectStore, error) {
|
||||
if cloudConfig.Name == "" {
|
||||
return nil, errors.New("object storage provider name must not be empty")
|
||||
}
|
||||
|
||||
objectStore, err := manager.GetObjectStore(cloudConfig.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := objectStore.Init(cloudConfig.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return objectStore, nil
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
@@ -19,28 +19,31 @@ package controller
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
core "k8s.io/client-go/testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
"github.com/heptio/ark/pkg/metrics"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
"github.com/heptio/ark/pkg/plugin"
|
||||
pluginmocks "github.com/heptio/ark/pkg/plugin/mocks"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
)
|
||||
|
||||
@@ -48,8 +51,8 @@ type fakeBackupper struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (b *fakeBackupper) Backup(backup *v1.Backup, data, log io.Writer, actions []backup.ItemAction) error {
|
||||
args := b.Called(backup, data, log, actions)
|
||||
func (b *fakeBackupper) Backup(logger logrus.FieldLogger, backup *v1.Backup, backupFile io.Writer, actions []backup.ItemAction) error {
|
||||
args := b.Called(logger, backup, backupFile, actions)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
@@ -156,27 +159,35 @@ func TestProcessBackup(t *testing.T) {
|
||||
var (
|
||||
client = fake.NewSimpleClientset()
|
||||
backupper = &fakeBackupper{}
|
||||
cloudBackups = &arktest.BackupService{}
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
logger = arktest.NewLogger()
|
||||
pluginManager = &MockManager{}
|
||||
logger = logging.DefaultLogger(logrus.DebugLevel)
|
||||
pluginRegistry = plugin.NewRegistry("/dir", logger, logrus.InfoLevel)
|
||||
clockTime, _ = time.Parse("Mon Jan 2 15:04:05 2006", "Mon Jan 2 15:04:05 2006")
|
||||
objectStore = &arktest.ObjectStore{}
|
||||
pluginManager = &pluginmocks.Manager{}
|
||||
)
|
||||
defer backupper.AssertExpectations(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
defer pluginManager.AssertExpectations(t)
|
||||
|
||||
c := NewBackupController(
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
client.ArkV1(),
|
||||
backupper,
|
||||
cloudBackups,
|
||||
v1.CloudProviderConfig{Name: "myCloud"},
|
||||
"bucket",
|
||||
test.allowSnapshots,
|
||||
logger,
|
||||
pluginManager,
|
||||
logrus.InfoLevel,
|
||||
pluginRegistry,
|
||||
NewBackupTracker(),
|
||||
metrics.NewServerMetrics(),
|
||||
).(*backupController)
|
||||
|
||||
c.clock = clock.NewFakeClock(clockTime)
|
||||
c.newPluginManager = func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager {
|
||||
return pluginManager
|
||||
}
|
||||
|
||||
var expiration, startTime time.Time
|
||||
|
||||
@@ -190,6 +201,11 @@ func TestProcessBackup(t *testing.T) {
|
||||
if test.backup.Spec.TTL.Duration > 0 {
|
||||
expiration = c.clock.Now().Add(test.backup.Spec.TTL.Duration)
|
||||
}
|
||||
}
|
||||
|
||||
if test.expectBackup {
|
||||
pluginManager.On("GetObjectStore", "myCloud").Return(objectStore, nil)
|
||||
objectStore.On("Init", mock.Anything).Return(nil)
|
||||
|
||||
// set up a Backup object to represent what we expect to be passed to backupper.Backup()
|
||||
backup := test.backup.DeepCopy()
|
||||
@@ -201,7 +217,14 @@ func TestProcessBackup(t *testing.T) {
|
||||
backup.Status.Expiration.Time = expiration
|
||||
backup.Status.StartTimestamp.Time = startTime
|
||||
backup.Status.Version = 1
|
||||
backupper.On("Backup", backup, mock.Anything, mock.Anything, mock.Anything).Return(nil)
|
||||
backupper.On("Backup",
|
||||
mock.Anything, // logger
|
||||
backup,
|
||||
mock.Anything, // backup file
|
||||
mock.Anything, // actions
|
||||
).Return(nil)
|
||||
|
||||
pluginManager.On("GetBackupItemActions").Return(nil, nil)
|
||||
|
||||
// Ensure we have a CompletionTimestamp when uploading.
|
||||
// Failures will display the bytes in buf.
|
||||
@@ -211,10 +234,11 @@ func TestProcessBackup(t *testing.T) {
|
||||
|
||||
return strings.Contains(json, timeString)
|
||||
}
|
||||
cloudBackups.On("UploadBackup", "bucket", backup.Name, mock.MatchedBy(completionTimestampIsPresent), mock.Anything, mock.Anything).Return(nil)
|
||||
objectStore.On("PutObject", "bucket", fmt.Sprintf("%s/%s-logs.gz", test.backup.Name, test.backup.Name), mock.Anything).Return(nil)
|
||||
objectStore.On("PutObject", "bucket", fmt.Sprintf("%s/ark-backup.json", test.backup.Name), mock.MatchedBy(completionTimestampIsPresent)).Return(nil)
|
||||
objectStore.On("PutObject", "bucket", fmt.Sprintf("%s/%s.tar.gz", test.backup.Name, test.backup.Name), mock.Anything).Return(nil)
|
||||
|
||||
pluginManager.On("GetBackupItemActions", backup.Name).Return(nil, nil)
|
||||
pluginManager.On("CloseBackupItemActions", backup.Name).Return(nil)
|
||||
pluginManager.On("CleanupClients")
|
||||
}
|
||||
|
||||
// this is necessary so the Patch() call returns the appropriate object
|
||||
@@ -273,8 +297,7 @@ func TestProcessBackup(t *testing.T) {
|
||||
require.NoError(t, err, "processBackup unexpected error: %v", err)
|
||||
|
||||
if !test.expectBackup {
|
||||
assert.Empty(t, backupper.Calls)
|
||||
assert.Empty(t, cloudBackups.Calls)
|
||||
// the AssertExpectations calls above make sure we aren't running anything we shouldn't be
|
||||
return
|
||||
}
|
||||
|
||||
@@ -324,134 +347,3 @@ func TestProcessBackup(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// MockManager is an autogenerated mock type for the Manager type
|
||||
type MockManager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CloseBackupItemActions provides a mock function with given fields: backupName
|
||||
func (_m *MockManager) CloseBackupItemActions(backupName string) error {
|
||||
ret := _m.Called(backupName)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(backupName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetBackupItemActions provides a mock function with given fields: backupName, logger, level
|
||||
func (_m *MockManager) GetBackupItemActions(backupName string) ([]backup.ItemAction, error) {
|
||||
ret := _m.Called(backupName)
|
||||
|
||||
var r0 []backup.ItemAction
|
||||
if rf, ok := ret.Get(0).(func(string) []backup.ItemAction); ok {
|
||||
r0 = rf(backupName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]backup.ItemAction)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(backupName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CloseRestoreItemActions provides a mock function with given fields: restoreName
|
||||
func (_m *MockManager) CloseRestoreItemActions(restoreName string) error {
|
||||
ret := _m.Called(restoreName)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(restoreName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetRestoreItemActions provides a mock function with given fields: restoreName, logger, level
|
||||
func (_m *MockManager) GetRestoreItemActions(restoreName string) ([]restore.ItemAction, error) {
|
||||
ret := _m.Called(restoreName)
|
||||
|
||||
var r0 []restore.ItemAction
|
||||
if rf, ok := ret.Get(0).(func(string) []restore.ItemAction); ok {
|
||||
r0 = rf(restoreName)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]restore.ItemAction)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(restoreName)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBlockStore provides a mock function with given fields: name
|
||||
func (_m *MockManager) GetBlockStore(name string) (cloudprovider.BlockStore, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 cloudprovider.BlockStore
|
||||
if rf, ok := ret.Get(0).(func(string) cloudprovider.BlockStore); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(cloudprovider.BlockStore)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetObjectStore provides a mock function with given fields: name
|
||||
func (_m *MockManager) GetObjectStore(name string) (cloudprovider.ObjectStore, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 cloudprovider.ObjectStore
|
||||
if rf, ok := ret.Get(0).(func(string) cloudprovider.ObjectStore); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(cloudprovider.ObjectStore)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// CleanupClients provides a mock function
|
||||
func (_m *MockManager) CleanupClients() {
|
||||
_ = _m.Called()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ type backupDeletionController struct {
|
||||
deleteBackupRequestLister listers.DeleteBackupRequestLister
|
||||
backupClient arkv1client.BackupsGetter
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
backupService cloudprovider.BackupService
|
||||
objectStore cloudprovider.ObjectStore
|
||||
bucket string
|
||||
restoreLister listers.RestoreLister
|
||||
restoreClient arkv1client.RestoresGetter
|
||||
@@ -58,6 +58,7 @@ type backupDeletionController struct {
|
||||
resticMgr restic.RepositoryManager
|
||||
podvolumeBackupLister listers.PodVolumeBackupLister
|
||||
|
||||
deleteBackupDir cloudprovider.DeleteBackupDirFunc
|
||||
processRequestFunc func(*v1.DeleteBackupRequest) error
|
||||
clock clock.Clock
|
||||
}
|
||||
@@ -69,7 +70,7 @@ func NewBackupDeletionController(
|
||||
deleteBackupRequestClient arkv1client.DeleteBackupRequestsGetter,
|
||||
backupClient arkv1client.BackupsGetter,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
backupService cloudprovider.BackupService,
|
||||
objectStore cloudprovider.ObjectStore,
|
||||
bucket string,
|
||||
restoreInformer informers.RestoreInformer,
|
||||
restoreClient arkv1client.RestoresGetter,
|
||||
@@ -83,14 +84,16 @@ func NewBackupDeletionController(
|
||||
deleteBackupRequestLister: deleteBackupRequestInformer.Lister(),
|
||||
backupClient: backupClient,
|
||||
snapshotService: snapshotService,
|
||||
backupService: backupService,
|
||||
objectStore: objectStore,
|
||||
bucket: bucket,
|
||||
restoreLister: restoreInformer.Lister(),
|
||||
restoreClient: restoreClient,
|
||||
backupTracker: backupTracker,
|
||||
resticMgr: resticMgr,
|
||||
podvolumeBackupLister: podvolumeBackupInformer.Lister(),
|
||||
clock: &clock.RealClock{},
|
||||
|
||||
podvolumeBackupLister: podvolumeBackupInformer.Lister(),
|
||||
deleteBackupDir: cloudprovider.DeleteBackupDir,
|
||||
clock: &clock.RealClock{},
|
||||
}
|
||||
|
||||
c.syncHandler = c.processQueueItem
|
||||
@@ -254,10 +257,10 @@ func (c *backupDeletionController) processRequest(req *v1.DeleteBackupRequest) e
|
||||
}
|
||||
}
|
||||
|
||||
// Try to delete backup from object storage
|
||||
log.Info("Removing backup from object storage")
|
||||
if err := c.backupService.DeleteBackupDir(c.bucket, backup.Name); err != nil {
|
||||
errs = append(errs, errors.Wrap(err, "error deleting backup from object storage").Error())
|
||||
// Try to delete backup from backup storage
|
||||
log.Info("Removing backup from backup storage")
|
||||
if err := c.deleteBackupDir(log, c.objectStore, c.bucket, backup.Name); err != nil {
|
||||
errs = append(errs, errors.Wrap(err, "error deleting backup from backup storage").Error())
|
||||
}
|
||||
|
||||
// Try to delete restores
|
||||
|
||||
@@ -23,10 +23,12 @@ import (
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
pkgbackup "github.com/heptio/ark/pkg/backup"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
@@ -106,7 +108,6 @@ func TestBackupDeletionControllerProcessQueueItem(t *testing.T) {
|
||||
type backupDeletionControllerTestData struct {
|
||||
client *fake.Clientset
|
||||
sharedInformers informers.SharedInformerFactory
|
||||
backupService *arktest.BackupService
|
||||
snapshotService *arktest.FakeSnapshotService
|
||||
controller *backupDeletionController
|
||||
req *v1.DeleteBackupRequest
|
||||
@@ -115,14 +116,12 @@ type backupDeletionControllerTestData struct {
|
||||
func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletionControllerTestData {
|
||||
client := fake.NewSimpleClientset(objects...)
|
||||
sharedInformers := informers.NewSharedInformerFactory(client, 0)
|
||||
backupService := &arktest.BackupService{}
|
||||
snapshotService := &arktest.FakeSnapshotService{SnapshotsTaken: sets.NewString()}
|
||||
req := pkgbackup.NewDeleteBackupRequest("foo", "uid")
|
||||
|
||||
data := &backupDeletionControllerTestData{
|
||||
client: client,
|
||||
sharedInformers: sharedInformers,
|
||||
backupService: backupService,
|
||||
snapshotService: snapshotService,
|
||||
controller: NewBackupDeletionController(
|
||||
arktest.NewLogger(),
|
||||
@@ -130,7 +129,7 @@ func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletio
|
||||
client.ArkV1(), // deleteBackupRequestClient
|
||||
client.ArkV1(), // backupClient
|
||||
snapshotService,
|
||||
backupService,
|
||||
nil, // objectStore
|
||||
"bucket",
|
||||
sharedInformers.Ark().V1().Restores(),
|
||||
client.ArkV1(), // restoreClient
|
||||
@@ -150,7 +149,6 @@ func setupBackupDeletionControllerTest(objects ...runtime.Object) *backupDeletio
|
||||
func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
t.Run("missing spec.backupName", func(t *testing.T) {
|
||||
td := setupBackupDeletionControllerTest()
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
td.req.Spec.BackupName = ""
|
||||
|
||||
@@ -171,7 +169,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
|
||||
t.Run("existing deletion requests for the backup are deleted", func(t *testing.T) {
|
||||
td := setupBackupDeletionControllerTest()
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
// add the backup to the tracker so the execution of processRequest doesn't progress
|
||||
// past checking for an in-progress backup. this makes validation easier.
|
||||
@@ -227,7 +224,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
|
||||
t.Run("deleting an in progress backup isn't allowed", func(t *testing.T) {
|
||||
td := setupBackupDeletionControllerTest()
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
td.controller.backupTracker.Add(td.req.Namespace, td.req.Spec.BackupName)
|
||||
|
||||
@@ -248,7 +244,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
|
||||
t.Run("patching to InProgress fails", func(t *testing.T) {
|
||||
td := setupBackupDeletionControllerTest()
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
td.client.PrependReactor("patch", "deletebackuprequests", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, errors.New("bad")
|
||||
@@ -261,7 +256,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
t.Run("patching backup to Deleting fails", func(t *testing.T) {
|
||||
backup := arktest.NewTestBackup().WithName("foo").WithSnapshot("pv-1", "snap-1").Backup
|
||||
td := setupBackupDeletionControllerTest(backup)
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
td.client.PrependReactor("patch", "deletebackuprequests", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, td.req, nil
|
||||
@@ -276,7 +270,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
|
||||
t.Run("unable to find backup", func(t *testing.T) {
|
||||
td := setupBackupDeletionControllerTest()
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
td.client.PrependReactor("get", "backups", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, nil, apierrors.NewNotFound(v1.SchemeGroupVersion.WithResource("backups").GroupResource(), "foo")
|
||||
@@ -315,7 +308,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
t.Run("no snapshot service, backup has snapshots", func(t *testing.T) {
|
||||
td := setupBackupDeletionControllerTest()
|
||||
td.controller.snapshotService = nil
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
td.client.PrependReactor("get", "backups", func(action core.Action) (bool, runtime.Object, error) {
|
||||
backup := arktest.NewTestBackup().WithName("backup-1").WithSnapshot("pv-1", "snap-1").Backup
|
||||
@@ -366,8 +358,6 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
td.sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(restore2)
|
||||
td.sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(restore3)
|
||||
|
||||
defer td.backupService.AssertExpectations(t)
|
||||
|
||||
// Clear out req labels to make sure the controller adds them
|
||||
td.req.Labels = make(map[string]string)
|
||||
|
||||
@@ -384,7 +374,11 @@ func TestBackupDeletionControllerProcessRequest(t *testing.T) {
|
||||
return true, backup, nil
|
||||
})
|
||||
|
||||
td.backupService.On("DeleteBackupDir", td.controller.bucket, td.req.Spec.BackupName).Return(nil)
|
||||
td.controller.deleteBackupDir = func(_ logrus.FieldLogger, _ cloudprovider.ObjectStore, bucket, backupName string) error {
|
||||
require.Equal(t, "bucket", bucket)
|
||||
require.Equal(t, td.req.Spec.BackupName, backupName)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := td.controller.processRequest(td.req)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -40,7 +40,7 @@ import (
|
||||
|
||||
type backupSyncController struct {
|
||||
client arkv1client.BackupsGetter
|
||||
backupService cloudprovider.BackupService
|
||||
cloudBackupLister cloudprovider.BackupLister
|
||||
bucket string
|
||||
syncPeriod time.Duration
|
||||
namespace string
|
||||
@@ -51,7 +51,7 @@ type backupSyncController struct {
|
||||
|
||||
func NewBackupSyncController(
|
||||
client arkv1client.BackupsGetter,
|
||||
backupService cloudprovider.BackupService,
|
||||
cloudBackupLister cloudprovider.BackupLister,
|
||||
bucket string,
|
||||
syncPeriod time.Duration,
|
||||
namespace string,
|
||||
@@ -64,7 +64,7 @@ func NewBackupSyncController(
|
||||
}
|
||||
return &backupSyncController{
|
||||
client: client,
|
||||
backupService: backupService,
|
||||
cloudBackupLister: cloudBackupLister,
|
||||
bucket: bucket,
|
||||
syncPeriod: syncPeriod,
|
||||
namespace: namespace,
|
||||
@@ -92,7 +92,7 @@ const gcFinalizer = "gc.ark.heptio.com"
|
||||
|
||||
func (c *backupSyncController) run() {
|
||||
c.logger.Info("Syncing backups from object storage")
|
||||
backups, err := c.backupService.GetAllBackups(c.bucket)
|
||||
backups, err := c.cloudBackupLister.ListBackups(c.bucket)
|
||||
if err != nil {
|
||||
c.logger.WithError(err).Error("error listing backups")
|
||||
return
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
core "k8s.io/client-go/testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
cloudprovidermocks "github.com/heptio/ark/pkg/cloudprovider/mocks"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
"github.com/heptio/ark/pkg/util/stringslice"
|
||||
@@ -37,18 +38,18 @@ import (
|
||||
|
||||
func TestBackupSyncControllerRun(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
getAllBackupsError error
|
||||
cloudBackups []*v1.Backup
|
||||
namespace string
|
||||
existingBackups sets.String
|
||||
name string
|
||||
listBackupsError error
|
||||
cloudBackups []*v1.Backup
|
||||
namespace string
|
||||
existingBackups sets.String
|
||||
}{
|
||||
{
|
||||
name: "no cloud backups",
|
||||
},
|
||||
{
|
||||
name: "backup service returns error on GetAllBackups",
|
||||
getAllBackupsError: errors.New("getAllBackups"),
|
||||
name: "backup lister returns error on ListBackups",
|
||||
listBackupsError: errors.New("listBackups"),
|
||||
},
|
||||
{
|
||||
name: "normal case",
|
||||
@@ -96,7 +97,7 @@ func TestBackupSyncControllerRun(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
bs = &arktest.BackupService{}
|
||||
backupLister = &cloudprovidermocks.BackupLister{}
|
||||
client = fake.NewSimpleClientset()
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
logger = arktest.NewLogger()
|
||||
@@ -104,7 +105,7 @@ func TestBackupSyncControllerRun(t *testing.T) {
|
||||
|
||||
c := NewBackupSyncController(
|
||||
client.ArkV1(),
|
||||
bs,
|
||||
backupLister,
|
||||
"bucket",
|
||||
time.Duration(0),
|
||||
test.namespace,
|
||||
@@ -112,7 +113,7 @@ func TestBackupSyncControllerRun(t *testing.T) {
|
||||
logger,
|
||||
).(*backupSyncController)
|
||||
|
||||
bs.On("GetAllBackups", "bucket").Return(test.cloudBackups, test.getAllBackupsError)
|
||||
backupLister.On("ListBackups", "bucket").Return(test.cloudBackups, test.listBackupsError)
|
||||
|
||||
expectedActions := make([]core.Action, 0)
|
||||
|
||||
@@ -154,7 +155,6 @@ func TestBackupSyncControllerRun(t *testing.T) {
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedActions, client.Actions())
|
||||
bs.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -217,7 +217,7 @@ func TestDeleteUnused(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
bs = &arktest.BackupService{}
|
||||
backupLister = &cloudprovidermocks.BackupLister{}
|
||||
client = fake.NewSimpleClientset()
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
logger = arktest.NewLogger()
|
||||
@@ -225,7 +225,7 @@ func TestDeleteUnused(t *testing.T) {
|
||||
|
||||
c := NewBackupSyncController(
|
||||
client.ArkV1(),
|
||||
bs,
|
||||
backupLister,
|
||||
"bucket",
|
||||
time.Duration(0),
|
||||
test.namespace,
|
||||
|
||||
@@ -49,12 +49,14 @@ type downloadRequestController struct {
|
||||
downloadRequestListerSynced cache.InformerSynced
|
||||
restoreLister listers.RestoreLister
|
||||
restoreListerSynced cache.InformerSynced
|
||||
backupService cloudprovider.BackupService
|
||||
objectStore cloudprovider.ObjectStore
|
||||
bucket string
|
||||
syncHandler func(key string) error
|
||||
queue workqueue.RateLimitingInterface
|
||||
clock clock.Clock
|
||||
logger logrus.FieldLogger
|
||||
|
||||
createSignedURL cloudprovider.CreateSignedURLFunc
|
||||
}
|
||||
|
||||
// NewDownloadRequestController creates a new DownloadRequestController.
|
||||
@@ -62,7 +64,7 @@ func NewDownloadRequestController(
|
||||
downloadRequestClient arkv1client.DownloadRequestsGetter,
|
||||
downloadRequestInformer informers.DownloadRequestInformer,
|
||||
restoreInformer informers.RestoreInformer,
|
||||
backupService cloudprovider.BackupService,
|
||||
objectStore cloudprovider.ObjectStore,
|
||||
bucket string,
|
||||
logger logrus.FieldLogger,
|
||||
) Interface {
|
||||
@@ -72,11 +74,13 @@ func NewDownloadRequestController(
|
||||
downloadRequestListerSynced: downloadRequestInformer.Informer().HasSynced,
|
||||
restoreLister: restoreInformer.Lister(),
|
||||
restoreListerSynced: restoreInformer.Informer().HasSynced,
|
||||
backupService: backupService,
|
||||
objectStore: objectStore,
|
||||
bucket: bucket,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "downloadrequest"),
|
||||
clock: &clock.RealClock{},
|
||||
logger: logger,
|
||||
|
||||
createSignedURL: cloudprovider.CreateSignedURL,
|
||||
}
|
||||
|
||||
c.syncHandler = c.processDownloadRequest
|
||||
@@ -236,7 +240,7 @@ func (c *downloadRequestController) generatePreSignedURL(downloadRequest *v1.Dow
|
||||
directory = downloadRequest.Spec.Target.Name
|
||||
}
|
||||
|
||||
update.Status.DownloadURL, err = c.backupService.CreateSignedURL(downloadRequest.Spec.Target, c.bucket, directory, signedURLTTL)
|
||||
update.Status.DownloadURL, err = c.createSignedURL(c.objectStore, downloadRequest.Spec.Target, c.bucket, directory, signedURLTTL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
@@ -106,17 +107,15 @@ func TestProcessDownloadRequest(t *testing.T) {
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
downloadRequestsInformer = sharedInformers.Ark().V1().DownloadRequests()
|
||||
restoresInformer = sharedInformers.Ark().V1().Restores()
|
||||
backupService = &arktest.BackupService{}
|
||||
logger = arktest.NewLogger()
|
||||
clockTime, _ = time.Parse("Mon Jan 2 15:04:05 2006", "Mon Jan 2 15:04:05 2006")
|
||||
)
|
||||
defer backupService.AssertExpectations(t)
|
||||
|
||||
c := NewDownloadRequestController(
|
||||
client.ArkV1(),
|
||||
downloadRequestsInformer,
|
||||
restoresInformer,
|
||||
backupService,
|
||||
nil, // objectStore
|
||||
"bucket",
|
||||
logger,
|
||||
).(*downloadRequestController)
|
||||
@@ -126,7 +125,7 @@ func TestProcessDownloadRequest(t *testing.T) {
|
||||
var downloadRequest *v1.DownloadRequest
|
||||
|
||||
if tc.expectedPhase == v1.DownloadRequestPhaseProcessed {
|
||||
target := v1.DownloadTarget{
|
||||
expectedTarget := v1.DownloadTarget{
|
||||
Kind: tc.targetKind,
|
||||
Name: tc.targetName,
|
||||
}
|
||||
@@ -137,7 +136,7 @@ func TestProcessDownloadRequest(t *testing.T) {
|
||||
Name: "dr1",
|
||||
},
|
||||
Spec: v1.DownloadRequestSpec{
|
||||
Target: target,
|
||||
Target: expectedTarget,
|
||||
},
|
||||
}
|
||||
downloadRequestsInformer.Informer().GetStore().Add(downloadRequest)
|
||||
@@ -146,7 +145,13 @@ func TestProcessDownloadRequest(t *testing.T) {
|
||||
restoresInformer.Informer().GetStore().Add(tc.restore)
|
||||
}
|
||||
|
||||
backupService.On("CreateSignedURL", target, "bucket", tc.expectedDir, 10*time.Minute).Return("signedURL", nil)
|
||||
c.createSignedURL = func(objectStore cloudprovider.ObjectStore, target v1.DownloadTarget, bucket, directory string, ttl time.Duration) (string, error) {
|
||||
require.Equal(t, expectedTarget, target)
|
||||
require.Equal(t, "bucket", bucket)
|
||||
require.Equal(t, tc.expectedDir, directory)
|
||||
require.Equal(t, 10*time.Minute, ttl)
|
||||
return "signedURL", nil
|
||||
}
|
||||
}
|
||||
|
||||
// method under test
|
||||
|
||||
@@ -51,6 +51,7 @@ import (
|
||||
"github.com/heptio/ark/pkg/util/boolptr"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
kubeutil "github.com/heptio/ark/pkg/util/kube"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
)
|
||||
|
||||
// nonRestorableResources is a blacklist for the restoration process. Any resources
|
||||
@@ -74,7 +75,7 @@ type restoreController struct {
|
||||
restoreClient arkv1client.RestoresGetter
|
||||
backupClient arkv1client.BackupsGetter
|
||||
restorer restore.Restorer
|
||||
backupService cloudprovider.BackupService
|
||||
objectStoreConfig api.CloudProviderConfig
|
||||
bucket string
|
||||
pvProviderExists bool
|
||||
backupLister listers.BackupLister
|
||||
@@ -84,8 +85,15 @@ type restoreController struct {
|
||||
syncHandler func(restoreName string) error
|
||||
queue workqueue.RateLimitingInterface
|
||||
logger logrus.FieldLogger
|
||||
pluginManager plugin.Manager
|
||||
logLevel logrus.Level
|
||||
pluginRegistry plugin.Registry
|
||||
metrics *metrics.ServerMetrics
|
||||
|
||||
getBackup cloudprovider.GetBackupFunc
|
||||
downloadBackup cloudprovider.DownloadBackupFunc
|
||||
uploadRestoreLog cloudprovider.UploadRestoreLogFunc
|
||||
uploadRestoreResults cloudprovider.UploadRestoreResultsFunc
|
||||
newPluginManager func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager
|
||||
}
|
||||
|
||||
func NewRestoreController(
|
||||
@@ -94,20 +102,22 @@ func NewRestoreController(
|
||||
restoreClient arkv1client.RestoresGetter,
|
||||
backupClient arkv1client.BackupsGetter,
|
||||
restorer restore.Restorer,
|
||||
backupService cloudprovider.BackupService,
|
||||
objectStoreConfig api.CloudProviderConfig,
|
||||
bucket string,
|
||||
backupInformer informers.BackupInformer,
|
||||
pvProviderExists bool,
|
||||
logger logrus.FieldLogger,
|
||||
pluginManager plugin.Manager,
|
||||
logLevel logrus.Level,
|
||||
pluginRegistry plugin.Registry,
|
||||
metrics *metrics.ServerMetrics,
|
||||
|
||||
) Interface {
|
||||
c := &restoreController{
|
||||
namespace: namespace,
|
||||
restoreClient: restoreClient,
|
||||
backupClient: backupClient,
|
||||
restorer: restorer,
|
||||
backupService: backupService,
|
||||
objectStoreConfig: objectStoreConfig,
|
||||
bucket: bucket,
|
||||
pvProviderExists: pvProviderExists,
|
||||
backupLister: backupInformer.Lister(),
|
||||
@@ -116,8 +126,17 @@ func NewRestoreController(
|
||||
restoreListerSynced: restoreInformer.Informer().HasSynced,
|
||||
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "restore"),
|
||||
logger: logger,
|
||||
pluginManager: pluginManager,
|
||||
logLevel: logLevel,
|
||||
pluginRegistry: pluginRegistry,
|
||||
metrics: metrics,
|
||||
|
||||
getBackup: cloudprovider.GetBackup,
|
||||
downloadBackup: cloudprovider.DownloadBackup,
|
||||
uploadRestoreLog: cloudprovider.UploadRestoreLog,
|
||||
uploadRestoreResults: cloudprovider.UploadRestoreResults,
|
||||
newPluginManager: func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager {
|
||||
return plugin.NewManager(logger, logLevel, pluginRegistry)
|
||||
},
|
||||
}
|
||||
|
||||
c.syncHandler = c.processRestore
|
||||
@@ -230,7 +249,9 @@ func (c *restoreController) processRestore(key string) error {
|
||||
logContext.Debug("Running processRestore")
|
||||
ns, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error splitting queue key")
|
||||
logContext.WithError(err).Error("unable to process restore: error splitting queue key")
|
||||
// Return nil here so we don't try to process the key any more
|
||||
return nil
|
||||
}
|
||||
|
||||
logContext.Debug("Getting Restore")
|
||||
@@ -259,8 +280,21 @@ func (c *restoreController) processRestore(key string) error {
|
||||
// don't modify items in the cache
|
||||
restore = restore.DeepCopy()
|
||||
|
||||
// validation
|
||||
if restore.Status.ValidationErrors = c.completeAndValidate(restore); len(restore.Status.ValidationErrors) > 0 {
|
||||
pluginManager := c.newPluginManager(logContext, logContext.Level, c.pluginRegistry)
|
||||
defer pluginManager.CleanupClients()
|
||||
|
||||
objectStore, err := getObjectStore(c.objectStoreConfig, pluginManager)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error initializing object store")
|
||||
}
|
||||
|
||||
actions, err := pluginManager.GetRestoreItemActions()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error initializing restore item actions")
|
||||
}
|
||||
|
||||
// complete & validate restore
|
||||
if restore.Status.ValidationErrors = c.completeAndValidate(objectStore, restore); len(restore.Status.ValidationErrors) > 0 {
|
||||
restore.Status.Phase = api.RestorePhaseFailedValidation
|
||||
} else {
|
||||
restore.Status.Phase = api.RestorePhaseInProgress
|
||||
@@ -285,7 +319,11 @@ func (c *restoreController) processRestore(key string) error {
|
||||
}
|
||||
logContext.Debug("Running restore")
|
||||
// execution & upload of restore
|
||||
restoreWarnings, restoreErrors, restoreFailure := c.runRestore(restore, c.bucket)
|
||||
restoreWarnings, restoreErrors, restoreFailure := c.runRestore(
|
||||
restore,
|
||||
actions,
|
||||
objectStore,
|
||||
)
|
||||
|
||||
restore.Status.Warnings = len(restoreWarnings.Ark) + len(restoreWarnings.Cluster)
|
||||
for _, w := range restoreWarnings.Namespaces {
|
||||
@@ -317,7 +355,7 @@ func (c *restoreController) processRestore(key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *restoreController) completeAndValidate(restore *api.Restore) []string {
|
||||
func (c *restoreController) completeAndValidate(objectStore cloudprovider.ObjectStore, restore *api.Restore) []string {
|
||||
// add non-restorable resources to restore's excluded resources
|
||||
excludedResources := sets.NewString(restore.Spec.ExcludedResources...)
|
||||
for _, nonrestorable := range nonRestorableResources {
|
||||
@@ -381,7 +419,7 @@ func (c *restoreController) completeAndValidate(restore *api.Restore) []string {
|
||||
backup *api.Backup
|
||||
err error
|
||||
)
|
||||
if backup, err = c.fetchBackup(c.bucket, restore.Spec.BackupName); err != nil {
|
||||
if backup, err = c.fetchBackup(objectStore, restore.Spec.BackupName); err != nil {
|
||||
return append(validationErrors, fmt.Sprintf("Error retrieving backup: %v", err))
|
||||
}
|
||||
|
||||
@@ -424,7 +462,7 @@ func mostRecentCompletedBackup(backups []*api.Backup) *api.Backup {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *restoreController) fetchBackup(bucket, name string) (*api.Backup, error) {
|
||||
func (c *restoreController) fetchBackup(objectStore cloudprovider.ObjectStore, name string) (*api.Backup, error) {
|
||||
backup, err := c.backupLister.Backups(c.namespace).Get(name)
|
||||
if err == nil {
|
||||
return backup, nil
|
||||
@@ -437,7 +475,7 @@ func (c *restoreController) fetchBackup(bucket, name string) (*api.Backup, error
|
||||
logContext := c.logger.WithField("backupName", name)
|
||||
|
||||
logContext.Debug("Backup not found in backupLister, checking object storage directly")
|
||||
backup, err = c.backupService.GetBackup(bucket, name)
|
||||
backup, err = c.getBackup(objectStore, c.bucket, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -457,39 +495,56 @@ func (c *restoreController) fetchBackup(bucket, name string) (*api.Backup, error
|
||||
return backup, nil
|
||||
}
|
||||
|
||||
func (c *restoreController) runRestore(restore *api.Restore, bucket string) (restoreWarnings, restoreErrors api.RestoreResult, restoreFailure error) {
|
||||
logContext := c.logger.WithFields(
|
||||
func (c *restoreController) runRestore(
|
||||
restore *api.Restore,
|
||||
actions []restore.ItemAction,
|
||||
objectStore cloudprovider.ObjectStore,
|
||||
) (restoreWarnings, restoreErrors api.RestoreResult, restoreFailure error) {
|
||||
logFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
c.logger.
|
||||
WithFields(
|
||||
logrus.Fields{
|
||||
"restore": kubeutil.NamespaceAndName(restore),
|
||||
"backup": restore.Spec.BackupName,
|
||||
},
|
||||
).
|
||||
WithError(errors.WithStack(err)).
|
||||
Error("Error creating log temp file")
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, err.Error())
|
||||
return
|
||||
}
|
||||
gzippedLogFile := gzip.NewWriter(logFile)
|
||||
// Assuming we successfully uploaded the log file, this will have already been closed below. It is safe to call
|
||||
// close multiple times. If we get an error closing this, there's not really anything we can do about it.
|
||||
defer gzippedLogFile.Close()
|
||||
defer closeAndRemoveFile(logFile, c.logger)
|
||||
|
||||
// Log the backup to both a backup log file and to stdout. This will help see what happened if the upload of the
|
||||
// backup log failed for whatever reason.
|
||||
logger := logging.DefaultLogger(c.logLevel)
|
||||
logger.Out = io.MultiWriter(os.Stdout, gzippedLogFile)
|
||||
logContext := logger.WithFields(
|
||||
logrus.Fields{
|
||||
"restore": kubeutil.NamespaceAndName(restore),
|
||||
"backup": restore.Spec.BackupName,
|
||||
})
|
||||
|
||||
backup, err := c.fetchBackup(bucket, restore.Spec.BackupName)
|
||||
backup, err := c.fetchBackup(objectStore, restore.Spec.BackupName)
|
||||
if err != nil {
|
||||
logContext.WithError(err).Error("Error getting backup")
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var tempFiles []*os.File
|
||||
|
||||
backupFile, err := downloadToTempFile(restore.Spec.BackupName, c.backupService, bucket, c.logger)
|
||||
backupFile, err := downloadToTempFile(objectStore, c.bucket, restore.Spec.BackupName, c.downloadBackup, c.logger)
|
||||
if err != nil {
|
||||
logContext.WithError(err).Error("Error downloading backup")
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, err.Error())
|
||||
restoreFailure = err
|
||||
return
|
||||
}
|
||||
tempFiles = append(tempFiles, backupFile)
|
||||
|
||||
logFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
logContext.WithError(errors.WithStack(err)).Error("Error creating log temp file")
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, err.Error())
|
||||
restoreFailure = err
|
||||
return
|
||||
}
|
||||
tempFiles = append(tempFiles, logFile)
|
||||
defer closeAndRemoveFile(backupFile, c.logger)
|
||||
|
||||
resultsFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
@@ -498,44 +553,25 @@ func (c *restoreController) runRestore(restore *api.Restore, bucket string) (res
|
||||
restoreFailure = err
|
||||
return
|
||||
}
|
||||
tempFiles = append(tempFiles, resultsFile)
|
||||
|
||||
defer func() {
|
||||
for _, file := range tempFiles {
|
||||
if err := file.Close(); err != nil {
|
||||
logContext.WithError(errors.WithStack(err)).WithField("file", file.Name()).Error("Error closing file")
|
||||
restoreFailure = err
|
||||
}
|
||||
|
||||
if err := os.Remove(file.Name()); err != nil {
|
||||
logContext.WithError(errors.WithStack(err)).WithField("file", file.Name()).Error("Error removing file")
|
||||
restoreFailure = err
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
actions, err := c.pluginManager.GetRestoreItemActions(restore.Name)
|
||||
if err != nil {
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, err.Error())
|
||||
return
|
||||
}
|
||||
defer c.pluginManager.CloseRestoreItemActions(restore.Name)
|
||||
defer closeAndRemoveFile(resultsFile, c.logger)
|
||||
|
||||
// Any return statement above this line means a total restore failure
|
||||
// Some failures after this line *may* be a total restore failure
|
||||
logContext.Info("starting restore")
|
||||
restoreWarnings, restoreErrors = c.restorer.Restore(restore, backup, backupFile, logFile, actions)
|
||||
restoreWarnings, restoreErrors = c.restorer.Restore(logContext, restore, backup, backupFile, actions)
|
||||
logContext.Info("restore completed")
|
||||
|
||||
// Try to upload the log file. This is best-effort. If we fail, we'll add to the ark errors.
|
||||
|
||||
if err := gzippedLogFile.Close(); err != nil {
|
||||
c.logger.WithError(err).Error("error closing gzippedLogFile")
|
||||
}
|
||||
// Reset the offset to 0 for reading
|
||||
if _, err = logFile.Seek(0, 0); err != nil {
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, fmt.Sprintf("error resetting log file offset to 0: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.backupService.UploadRestoreLog(bucket, restore.Spec.BackupName, restore.Name, logFile); err != nil {
|
||||
if err := c.uploadRestoreLog(objectStore, c.bucket, restore.Spec.BackupName, restore.Name, logFile); err != nil {
|
||||
restoreErrors.Ark = append(restoreErrors.Ark, fmt.Sprintf("error uploading log file to object storage: %v", err))
|
||||
}
|
||||
|
||||
@@ -556,15 +592,20 @@ func (c *restoreController) runRestore(restore *api.Restore, bucket string) (res
|
||||
logContext.WithError(errors.WithStack(err)).Error("Error resetting results file offset to 0")
|
||||
return
|
||||
}
|
||||
if err := c.backupService.UploadRestoreResults(bucket, restore.Spec.BackupName, restore.Name, resultsFile); err != nil {
|
||||
if err := c.uploadRestoreResults(objectStore, c.bucket, restore.Spec.BackupName, restore.Name, resultsFile); err != nil {
|
||||
logContext.WithError(errors.WithStack(err)).Error("Error uploading results files to object storage")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func downloadToTempFile(backupName string, backupService cloudprovider.BackupService, bucket string, logger logrus.FieldLogger) (*os.File, error) {
|
||||
readCloser, err := backupService.DownloadBackup(bucket, backupName)
|
||||
func downloadToTempFile(
|
||||
objectStore cloudprovider.ObjectStore,
|
||||
bucket, backupName string,
|
||||
downloadBackup cloudprovider.DownloadBackupFunc,
|
||||
logger logrus.FieldLogger,
|
||||
) (*os.File, error) {
|
||||
readCloser, err := downloadBackup(objectStore, bucket, backupName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -35,9 +36,12 @@ import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/heptio/ark/pkg/generated/clientset/versioned/fake"
|
||||
informers "github.com/heptio/ark/pkg/generated/informers/externalversions"
|
||||
"github.com/heptio/ark/pkg/metrics"
|
||||
"github.com/heptio/ark/pkg/plugin"
|
||||
pluginmocks "github.com/heptio/ark/pkg/plugin/mocks"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
@@ -79,9 +83,7 @@ func TestFetchBackup(t *testing.T) {
|
||||
client = fake.NewSimpleClientset()
|
||||
restorer = &fakeRestorer{}
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
backupSvc = &arktest.BackupService{}
|
||||
logger = arktest.NewLogger()
|
||||
pluginManager = &MockManager{}
|
||||
)
|
||||
|
||||
c := NewRestoreController(
|
||||
@@ -90,12 +92,13 @@ func TestFetchBackup(t *testing.T) {
|
||||
client.ArkV1(),
|
||||
client.ArkV1(),
|
||||
restorer,
|
||||
backupSvc,
|
||||
api.CloudProviderConfig{},
|
||||
"bucket",
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
false,
|
||||
logger,
|
||||
pluginManager,
|
||||
logrus.InfoLevel,
|
||||
nil, //pluginRegistry
|
||||
metrics.NewServerMetrics(),
|
||||
).(*restoreController)
|
||||
|
||||
@@ -104,20 +107,96 @@ func TestFetchBackup(t *testing.T) {
|
||||
}
|
||||
|
||||
if test.backupServiceBackup != nil || test.backupServiceError != nil {
|
||||
backupSvc.On("GetBackup", "bucket", test.backupName).Return(test.backupServiceBackup, test.backupServiceError)
|
||||
c.getBackup = func(_ cloudprovider.ObjectStore, bucket, backup string) (*api.Backup, error) {
|
||||
require.Equal(t, "bucket", bucket)
|
||||
require.Equal(t, test.backupName, backup)
|
||||
return test.backupServiceBackup, test.backupServiceError
|
||||
}
|
||||
}
|
||||
|
||||
backup, err := c.fetchBackup("bucket", test.backupName)
|
||||
backup, err := c.fetchBackup(nil, test.backupName)
|
||||
|
||||
if assert.Equal(t, test.expectedErr, err != nil) {
|
||||
assert.Equal(t, test.expectedRes, backup)
|
||||
}
|
||||
|
||||
backupSvc.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessRestoreSkips(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
restoreKey string
|
||||
restore *api.Restore
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "invalid key returns error",
|
||||
restoreKey: "invalid/key/value",
|
||||
},
|
||||
{
|
||||
name: "missing restore returns error",
|
||||
restoreKey: "foo/bar",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "restore with phase InProgress does not get processed",
|
||||
restoreKey: "foo/bar",
|
||||
restore: arktest.NewTestRestore("foo", "bar", api.RestorePhaseInProgress).Restore,
|
||||
},
|
||||
{
|
||||
name: "restore with phase Completed does not get processed",
|
||||
restoreKey: "foo/bar",
|
||||
restore: arktest.NewTestRestore("foo", "bar", api.RestorePhaseCompleted).Restore,
|
||||
},
|
||||
{
|
||||
name: "restore with phase FailedValidation does not get processed",
|
||||
restoreKey: "foo/bar",
|
||||
restore: arktest.NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).Restore,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var (
|
||||
client = fake.NewSimpleClientset()
|
||||
restorer = &fakeRestorer{}
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
logger = arktest.NewLogger()
|
||||
pluginManager = &pluginmocks.Manager{}
|
||||
objectStore = &arktest.ObjectStore{}
|
||||
)
|
||||
defer restorer.AssertExpectations(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
|
||||
c := NewRestoreController(
|
||||
api.DefaultNamespace,
|
||||
sharedInformers.Ark().V1().Restores(),
|
||||
client.ArkV1(),
|
||||
client.ArkV1(),
|
||||
restorer,
|
||||
api.CloudProviderConfig{Name: "myCloud"},
|
||||
"bucket",
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
false, // pvProviderExists
|
||||
logger,
|
||||
logrus.InfoLevel,
|
||||
nil, // pluginRegistry
|
||||
metrics.NewServerMetrics(),
|
||||
).(*restoreController)
|
||||
c.newPluginManager = func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager {
|
||||
return pluginManager
|
||||
}
|
||||
|
||||
if test.restore != nil {
|
||||
sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(test.restore)
|
||||
}
|
||||
|
||||
err := c.processRestore(test.restoreKey)
|
||||
assert.Equal(t, test.expectError, err != nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestProcessRestore(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -136,31 +215,6 @@ func TestProcessRestore(t *testing.T) {
|
||||
backupServiceDownloadBackupError error
|
||||
expectedFinalPhase string
|
||||
}{
|
||||
{
|
||||
name: "invalid key returns error",
|
||||
restoreKey: "invalid/key/value",
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "missing restore returns error",
|
||||
restoreKey: "foo/bar",
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "restore with phase InProgress does not get processed",
|
||||
restore: arktest.NewTestRestore("foo", "bar", api.RestorePhaseInProgress).Restore,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "restore with phase Completed does not get processed",
|
||||
restore: arktest.NewTestRestore("foo", "bar", api.RestorePhaseCompleted).Restore,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "restore with phase FailedValidation does not get processed",
|
||||
restore: arktest.NewTestRestore("foo", "bar", api.RestorePhaseFailedValidation).Restore,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "restore with both namespace in both includedNamespaces and excludedNamespaces fails validation",
|
||||
restore: NewRestore("foo", "bar", "backup-1", "another-1", "*", api.RestorePhaseNew).WithExcludedNamespace("another-1").Restore,
|
||||
@@ -318,13 +372,12 @@ func TestProcessRestore(t *testing.T) {
|
||||
client = fake.NewSimpleClientset()
|
||||
restorer = &fakeRestorer{}
|
||||
sharedInformers = informers.NewSharedInformerFactory(client, 0)
|
||||
backupSvc = &arktest.BackupService{}
|
||||
logger = arktest.NewLogger()
|
||||
pluginManager = &MockManager{}
|
||||
pluginManager = &pluginmocks.Manager{}
|
||||
objectStore = &arktest.ObjectStore{}
|
||||
)
|
||||
|
||||
defer restorer.AssertExpectations(t)
|
||||
defer backupSvc.AssertExpectations(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
|
||||
c := NewRestoreController(
|
||||
api.DefaultNamespace,
|
||||
@@ -332,16 +385,23 @@ func TestProcessRestore(t *testing.T) {
|
||||
client.ArkV1(),
|
||||
client.ArkV1(),
|
||||
restorer,
|
||||
backupSvc,
|
||||
api.CloudProviderConfig{Name: "myCloud"},
|
||||
"bucket",
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
test.allowRestoreSnapshots,
|
||||
logger,
|
||||
pluginManager,
|
||||
logrus.InfoLevel,
|
||||
nil, // pluginRegistry
|
||||
metrics.NewServerMetrics(),
|
||||
).(*restoreController)
|
||||
c.newPluginManager = func(logger logrus.FieldLogger, logLevel logrus.Level, pluginRegistry plugin.Registry) plugin.Manager {
|
||||
return pluginManager
|
||||
}
|
||||
|
||||
if test.restore != nil {
|
||||
pluginManager.On("GetObjectStore", "myCloud").Return(objectStore, nil)
|
||||
objectStore.On("Init", mock.Anything).Return(nil)
|
||||
|
||||
sharedInformers.Ark().V1().Restores().Informer().GetStore().Add(test.restore)
|
||||
|
||||
// this is necessary so the Patch() call returns the appropriate object
|
||||
@@ -391,11 +451,24 @@ func TestProcessRestore(t *testing.T) {
|
||||
errors.Ark = append(errors.Ark, "error uploading log file to object storage: "+test.uploadLogError.Error())
|
||||
}
|
||||
if test.expectedRestorerCall != nil {
|
||||
downloadedBackup := ioutil.NopCloser(bytes.NewReader([]byte("hello world")))
|
||||
backupSvc.On("DownloadBackup", mock.Anything, mock.Anything).Return(downloadedBackup, nil)
|
||||
restorer.On("Restore", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(warnings, errors)
|
||||
backupSvc.On("UploadRestoreLog", "bucket", test.backup.Name, test.restore.Name, mock.Anything).Return(test.uploadLogError)
|
||||
backupSvc.On("UploadRestoreResults", "bucket", test.backup.Name, test.restore.Name, mock.Anything).Return(nil)
|
||||
c.downloadBackup = func(objectStore cloudprovider.ObjectStore, bucket, backup string) (io.ReadCloser, error) {
|
||||
require.Equal(t, test.backup.Name, backup)
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte("hello world"))), nil
|
||||
}
|
||||
|
||||
restorer.On("Restore", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(warnings, errors)
|
||||
|
||||
c.uploadRestoreLog = func(objectStore cloudprovider.ObjectStore, bucket, backup, restore string, log io.Reader) error {
|
||||
require.Equal(t, test.backup.Name, backup)
|
||||
require.Equal(t, test.restore.Name, restore)
|
||||
return test.uploadLogError
|
||||
}
|
||||
|
||||
c.uploadRestoreResults = func(objectStore cloudprovider.ObjectStore, bucket, backup, restore string, results io.Reader) error {
|
||||
require.Equal(t, test.backup.Name, backup)
|
||||
require.Equal(t, test.restore.Name, restore)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -410,21 +483,27 @@ func TestProcessRestore(t *testing.T) {
|
||||
}
|
||||
|
||||
if test.backupServiceGetBackupError != nil {
|
||||
backupSvc.On("GetBackup", "bucket", mock.Anything).Return(nil, test.backupServiceGetBackupError)
|
||||
c.getBackup = func(_ cloudprovider.ObjectStore, bucket, backup string) (*api.Backup, error) {
|
||||
require.Equal(t, "bucket", bucket)
|
||||
require.Equal(t, test.restore.Spec.BackupName, backup)
|
||||
return nil, test.backupServiceGetBackupError
|
||||
}
|
||||
}
|
||||
|
||||
if test.backupServiceDownloadBackupError != nil {
|
||||
backupSvc.On("DownloadBackup", "bucket", test.restore.Spec.BackupName).Return(nil, test.backupServiceDownloadBackupError)
|
||||
c.downloadBackup = func(_ cloudprovider.ObjectStore, bucket, backupName string) (io.ReadCloser, error) {
|
||||
require.Equal(t, "bucket", bucket)
|
||||
require.Equal(t, test.restore.Spec.BackupName, backupName)
|
||||
return nil, test.backupServiceDownloadBackupError
|
||||
}
|
||||
}
|
||||
|
||||
if test.restore != nil {
|
||||
pluginManager.On("GetRestoreItemActions", test.restore.Name).Return(nil, nil)
|
||||
pluginManager.On("CloseRestoreItemActions", test.restore.Name).Return(nil)
|
||||
pluginManager.On("GetRestoreItemActions").Return(nil, nil)
|
||||
pluginManager.On("CleanupClients")
|
||||
}
|
||||
|
||||
err = c.processRestore(key)
|
||||
backupSvc.AssertExpectations(t)
|
||||
restorer.AssertExpectations(t)
|
||||
|
||||
assert.Equal(t, test.expectedErr, err != nil, "got error %v", err)
|
||||
actions := client.Actions()
|
||||
@@ -524,11 +603,12 @@ func TestCompleteAndValidateWhenScheduleNameSpecified(t *testing.T) {
|
||||
client.ArkV1(),
|
||||
client.ArkV1(),
|
||||
nil,
|
||||
nil,
|
||||
api.CloudProviderConfig{Name: "myCloud"},
|
||||
"bucket",
|
||||
sharedInformers.Ark().V1().Backups(),
|
||||
false,
|
||||
logger,
|
||||
logrus.DebugLevel,
|
||||
nil,
|
||||
nil,
|
||||
).(*restoreController)
|
||||
@@ -552,7 +632,7 @@ func TestCompleteAndValidateWhenScheduleNameSpecified(t *testing.T) {
|
||||
Backup,
|
||||
))
|
||||
|
||||
errs := c.completeAndValidate(restore)
|
||||
errs := c.completeAndValidate(nil, restore)
|
||||
assert.Equal(t, []string{"No backups found for schedule"}, errs)
|
||||
assert.Empty(t, restore.Spec.BackupName)
|
||||
|
||||
@@ -565,7 +645,7 @@ func TestCompleteAndValidateWhenScheduleNameSpecified(t *testing.T) {
|
||||
Backup,
|
||||
))
|
||||
|
||||
errs = c.completeAndValidate(restore)
|
||||
errs = c.completeAndValidate(nil, restore)
|
||||
assert.Equal(t, []string{"No completed backups found for schedule"}, errs)
|
||||
assert.Empty(t, restore.Spec.BackupName)
|
||||
|
||||
@@ -589,7 +669,7 @@ func TestCompleteAndValidateWhenScheduleNameSpecified(t *testing.T) {
|
||||
Backup,
|
||||
))
|
||||
|
||||
errs = c.completeAndValidate(restore)
|
||||
errs = c.completeAndValidate(nil, restore)
|
||||
assert.Nil(t, errs)
|
||||
assert.Equal(t, "bar", restore.Spec.BackupName)
|
||||
}
|
||||
@@ -708,13 +788,13 @@ type fakeRestorer struct {
|
||||
}
|
||||
|
||||
func (r *fakeRestorer) Restore(
|
||||
log logrus.FieldLogger,
|
||||
restore *api.Restore,
|
||||
backup *api.Backup,
|
||||
backupReader io.Reader,
|
||||
logger io.Writer,
|
||||
actions []restore.ItemAction,
|
||||
) (api.RestoreResult, api.RestoreResult) {
|
||||
res := r.Called(restore, backup, backupReader, logger)
|
||||
res := r.Called(log, restore, backup, backupReader, actions)
|
||||
|
||||
r.calledWithArg = *restore
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -38,41 +38,41 @@ import (
|
||||
// interface.
|
||||
type BackupItemActionPlugin struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
impl arkbackup.ItemAction
|
||||
log *logrusAdapter
|
||||
*pluginBase
|
||||
}
|
||||
|
||||
// NewBackupItemActionPlugin constructs a BackupItemActionPlugin.
|
||||
func NewBackupItemActionPlugin(itemAction arkbackup.ItemAction) *BackupItemActionPlugin {
|
||||
func NewBackupItemActionPlugin(options ...pluginOption) *BackupItemActionPlugin {
|
||||
return &BackupItemActionPlugin{
|
||||
impl: itemAction,
|
||||
pluginBase: newPluginBase(options...),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BackupItemActionPlugin) Kind() PluginKind {
|
||||
return PluginKindBackupItemAction
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// client code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCServer registers a BackupItemAction gRPC server.
|
||||
func (p *BackupItemActionPlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterBackupItemActionServer(s, &BackupItemActionGRPCServer{impl: p.impl})
|
||||
return nil
|
||||
}
|
||||
|
||||
// GRPCClient returns a BackupItemAction gRPC client.
|
||||
// GRPCClient returns a clientDispenser for BackupItemAction gRPC clients.
|
||||
func (p *BackupItemActionPlugin) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
|
||||
return &BackupItemActionGRPCClient{grpcClient: proto.NewBackupItemActionClient(c), log: p.log}, nil
|
||||
return newClientDispenser(p.clientLogger, c, newBackupItemActionGRPCClient), nil
|
||||
}
|
||||
|
||||
// BackupItemActionGRPCClient implements the backup/ItemAction interface and uses a
|
||||
// gRPC client to make calls to the plugin server.
|
||||
type BackupItemActionGRPCClient struct {
|
||||
*clientBase
|
||||
grpcClient proto.BackupItemActionClient
|
||||
log *logrusAdapter
|
||||
}
|
||||
|
||||
func newBackupItemActionGRPCClient(base *clientBase, clientConn *grpc.ClientConn) interface{} {
|
||||
return &BackupItemActionGRPCClient{
|
||||
clientBase: base,
|
||||
grpcClient: proto.NewBackupItemActionClient(clientConn),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BackupItemActionGRPCClient) AppliesTo() (arkbackup.ResourceSelector, error) {
|
||||
res, err := c.grpcClient.AppliesTo(context.Background(), &proto.Empty{})
|
||||
res, err := c.grpcClient.AppliesTo(context.Background(), &proto.AppliesToRequest{Plugin: c.plugin})
|
||||
if err != nil {
|
||||
return arkbackup.ResourceSelector{}, err
|
||||
}
|
||||
@@ -98,6 +98,7 @@ func (c *BackupItemActionGRPCClient) Execute(item runtime.Unstructured, backup *
|
||||
}
|
||||
|
||||
req := &proto.ExecuteRequest{
|
||||
Plugin: c.plugin,
|
||||
Item: itemJSON,
|
||||
Backup: backupJSON,
|
||||
}
|
||||
@@ -130,18 +131,43 @@ func (c *BackupItemActionGRPCClient) Execute(item runtime.Unstructured, backup *
|
||||
return &updatedItem, additionalItems, nil
|
||||
}
|
||||
|
||||
func (c *BackupItemActionGRPCClient) SetLog(log logrus.FieldLogger) {
|
||||
c.log.impl = log
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// server code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCServer registers a BackupItemAction gRPC server.
|
||||
func (p *BackupItemActionPlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterBackupItemActionServer(s, &BackupItemActionGRPCServer{mux: p.serverMux})
|
||||
return nil
|
||||
}
|
||||
|
||||
// BackupItemActionGRPCServer implements the proto-generated BackupItemActionServer interface, and accepts
|
||||
// gRPC calls and forwards them to an implementation of the pluggable interface.
|
||||
type BackupItemActionGRPCServer struct {
|
||||
impl arkbackup.ItemAction
|
||||
mux *serverMux
|
||||
}
|
||||
|
||||
func (s *BackupItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.Empty) (*proto.AppliesToResponse, error) {
|
||||
resourceSelector, err := s.impl.AppliesTo()
|
||||
func (s *BackupItemActionGRPCServer) getImpl(name string) (arkbackup.ItemAction, error) {
|
||||
impl, err := s.mux.getHandler(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
itemAction, ok := impl.(arkbackup.ItemAction)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a backup item action", impl)
|
||||
}
|
||||
|
||||
return itemAction, nil
|
||||
}
|
||||
|
||||
func (s *BackupItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.AppliesToRequest) (*proto.AppliesToResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resourceSelector, err := impl.AppliesTo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -156,6 +182,11 @@ func (s *BackupItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.E
|
||||
}
|
||||
|
||||
func (s *BackupItemActionGRPCServer) Execute(ctx context.Context, req *proto.ExecuteRequest) (*proto.ExecuteResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var item unstructured.Unstructured
|
||||
var backup api.Backup
|
||||
|
||||
@@ -166,7 +197,7 @@ func (s *BackupItemActionGRPCServer) Execute(ctx context.Context, req *proto.Exe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedItem, additionalItems, err := s.impl.Execute(&item, &backup)
|
||||
updatedItem, additionalItems, err := impl.Execute(&item, &backup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
"github.com/heptio/ark/pkg/backup/mocks"
|
||||
proto "github.com/heptio/ark/pkg/plugin/generated"
|
||||
arktest "github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -151,9 +152,15 @@ func TestBackupItemActionGRPCServerExecute(t *testing.T) {
|
||||
itemAction.On("Execute", &validItemObject, &validBackupObject).Return(test.implUpdatedItem, test.implAdditionalItems, test.implError)
|
||||
}
|
||||
|
||||
s := &BackupItemActionGRPCServer{impl: itemAction}
|
||||
s := &BackupItemActionGRPCServer{mux: &serverMux{
|
||||
serverLog: arktest.NewLogger(),
|
||||
handlers: map[string]interface{}{
|
||||
"xyz": itemAction,
|
||||
},
|
||||
}}
|
||||
|
||||
req := &proto.ExecuteRequest{
|
||||
Plugin: "xyz",
|
||||
Item: test.item,
|
||||
Backup: test.backup,
|
||||
}
|
||||
@@ -162,9 +169,10 @@ func TestBackupItemActionGRPCServerExecute(t *testing.T) {
|
||||
|
||||
// Verify error
|
||||
assert.Equal(t, test.expectError, err != nil)
|
||||
if test.expectError {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
require.NotNil(t, resp)
|
||||
|
||||
// Verify updated item
|
||||
updatedItem := test.implUpdatedItem
|
||||
|
||||
+97
-27
@@ -20,6 +20,7 @@ import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
@@ -34,43 +35,44 @@ import (
|
||||
// interface.
|
||||
type BlockStorePlugin struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
|
||||
impl cloudprovider.BlockStore
|
||||
*pluginBase
|
||||
}
|
||||
|
||||
// NewBlockStorePlugin constructs a BlockStorePlugin.
|
||||
func NewBlockStorePlugin(blockStore cloudprovider.BlockStore) *BlockStorePlugin {
|
||||
func NewBlockStorePlugin(options ...pluginOption) *BlockStorePlugin {
|
||||
return &BlockStorePlugin{
|
||||
impl: blockStore,
|
||||
pluginBase: newPluginBase(options...),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BlockStorePlugin) Kind() PluginKind {
|
||||
return PluginKindBlockStore
|
||||
}
|
||||
|
||||
// GRPCServer registers a BlockStore gRPC server.
|
||||
func (p *BlockStorePlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterBlockStoreServer(s, &BlockStoreGRPCServer{impl: p.impl})
|
||||
return nil
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// client code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCClient returns a BlockStore gRPC client.
|
||||
func (p *BlockStorePlugin) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
|
||||
return &BlockStoreGRPCClient{grpcClient: proto.NewBlockStoreClient(c)}, nil
|
||||
return newClientDispenser(p.clientLogger, c, newBlockStoreGRPCClient), nil
|
||||
}
|
||||
|
||||
// BlockStoreGRPCClient implements the cloudprovider.BlockStore interface and uses a
|
||||
// gRPC client to make calls to the plugin server.
|
||||
type BlockStoreGRPCClient struct {
|
||||
*clientBase
|
||||
grpcClient proto.BlockStoreClient
|
||||
}
|
||||
|
||||
func newBlockStoreGRPCClient(base *clientBase, clientConn *grpc.ClientConn) interface{} {
|
||||
return &BlockStoreGRPCClient{
|
||||
clientBase: base,
|
||||
grpcClient: proto.NewBlockStoreClient(clientConn),
|
||||
}
|
||||
}
|
||||
|
||||
// Init prepares the BlockStore for usage using the provided map of
|
||||
// configuration key-value pairs. It returns an error if the BlockStore
|
||||
// cannot be initialized from the provided config.
|
||||
func (c *BlockStoreGRPCClient) Init(config map[string]string) error {
|
||||
_, err := c.grpcClient.Init(context.Background(), &proto.InitRequest{Config: config})
|
||||
_, err := c.grpcClient.Init(context.Background(), &proto.InitRequest{Plugin: c.plugin, Config: config})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -79,6 +81,7 @@ func (c *BlockStoreGRPCClient) Init(config map[string]string) error {
|
||||
// and with the specified type and IOPS (if using provisioned IOPS).
|
||||
func (c *BlockStoreGRPCClient) CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ string, iops *int64) (string, error) {
|
||||
req := &proto.CreateVolumeRequest{
|
||||
Plugin: c.plugin,
|
||||
SnapshotID: snapshotID,
|
||||
VolumeType: volumeType,
|
||||
VolumeAZ: volumeAZ,
|
||||
@@ -101,7 +104,7 @@ func (c *BlockStoreGRPCClient) CreateVolumeFromSnapshot(snapshotID, volumeType,
|
||||
// GetVolumeInfo returns the type and IOPS (if using provisioned IOPS) for a specified block
|
||||
// volume.
|
||||
func (c *BlockStoreGRPCClient) GetVolumeInfo(volumeID, volumeAZ string) (string, *int64, error) {
|
||||
res, err := c.grpcClient.GetVolumeInfo(context.Background(), &proto.GetVolumeInfoRequest{VolumeID: volumeID, VolumeAZ: volumeAZ})
|
||||
res, err := c.grpcClient.GetVolumeInfo(context.Background(), &proto.GetVolumeInfoRequest{Plugin: c.plugin, VolumeID: volumeID, VolumeAZ: volumeAZ})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -116,7 +119,7 @@ func (c *BlockStoreGRPCClient) GetVolumeInfo(volumeID, volumeAZ string) (string,
|
||||
|
||||
// IsVolumeReady returns whether the specified volume is ready to be used.
|
||||
func (c *BlockStoreGRPCClient) IsVolumeReady(volumeID, volumeAZ string) (bool, error) {
|
||||
res, err := c.grpcClient.IsVolumeReady(context.Background(), &proto.IsVolumeReadyRequest{VolumeID: volumeID, VolumeAZ: volumeAZ})
|
||||
res, err := c.grpcClient.IsVolumeReady(context.Background(), &proto.IsVolumeReadyRequest{Plugin: c.plugin, VolumeID: volumeID, VolumeAZ: volumeAZ})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -128,6 +131,7 @@ func (c *BlockStoreGRPCClient) IsVolumeReady(volumeID, volumeAZ string) (bool, e
|
||||
// set of tags to the snapshot.
|
||||
func (c *BlockStoreGRPCClient) CreateSnapshot(volumeID, volumeAZ string, tags map[string]string) (string, error) {
|
||||
req := &proto.CreateSnapshotRequest{
|
||||
Plugin: c.plugin,
|
||||
VolumeID: volumeID,
|
||||
VolumeAZ: volumeAZ,
|
||||
Tags: tags,
|
||||
@@ -143,7 +147,7 @@ func (c *BlockStoreGRPCClient) CreateSnapshot(volumeID, volumeAZ string, tags ma
|
||||
|
||||
// DeleteSnapshot deletes the specified volume snapshot.
|
||||
func (c *BlockStoreGRPCClient) DeleteSnapshot(snapshotID string) error {
|
||||
_, err := c.grpcClient.DeleteSnapshot(context.Background(), &proto.DeleteSnapshotRequest{SnapshotID: snapshotID})
|
||||
_, err := c.grpcClient.DeleteSnapshot(context.Background(), &proto.DeleteSnapshotRequest{Plugin: c.plugin, SnapshotID: snapshotID})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -155,6 +159,7 @@ func (c *BlockStoreGRPCClient) GetVolumeID(pv runtime.Unstructured) (string, err
|
||||
}
|
||||
|
||||
req := &proto.GetVolumeIDRequest{
|
||||
Plugin: c.plugin,
|
||||
PersistentVolume: encodedPV,
|
||||
}
|
||||
|
||||
@@ -173,6 +178,7 @@ func (c *BlockStoreGRPCClient) SetVolumeID(pv runtime.Unstructured, volumeID str
|
||||
}
|
||||
|
||||
req := &proto.SetVolumeIDRequest{
|
||||
Plugin: c.plugin,
|
||||
PersistentVolume: encodedPV,
|
||||
VolumeID: volumeID,
|
||||
}
|
||||
@@ -191,17 +197,46 @@ func (c *BlockStoreGRPCClient) SetVolumeID(pv runtime.Unstructured, volumeID str
|
||||
return &updatedPV, nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// server code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCServer registers a BlockStore gRPC server.
|
||||
func (p *BlockStorePlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterBlockStoreServer(s, &BlockStoreGRPCServer{mux: p.serverMux})
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockStoreGRPCServer implements the proto-generated BlockStoreServer interface, and accepts
|
||||
// gRPC calls and forwards them to an implementation of the pluggable interface.
|
||||
type BlockStoreGRPCServer struct {
|
||||
impl cloudprovider.BlockStore
|
||||
mux *serverMux
|
||||
}
|
||||
|
||||
func (s *BlockStoreGRPCServer) getImpl(name string) (cloudprovider.BlockStore, error) {
|
||||
impl, err := s.mux.getHandler(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockStore, ok := impl.(cloudprovider.BlockStore)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a block store", impl)
|
||||
}
|
||||
|
||||
return blockStore, nil
|
||||
}
|
||||
|
||||
// Init prepares the BlockStore for usage using the provided map of
|
||||
// configuration key-value pairs. It returns an error if the BlockStore
|
||||
// cannot be initialized from the provided config.
|
||||
func (s *BlockStoreGRPCServer) Init(ctx context.Context, req *proto.InitRequest) (*proto.Empty, error) {
|
||||
if err := s.impl.Init(req.Config); err != nil {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := impl.Init(req.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -211,6 +246,11 @@ func (s *BlockStoreGRPCServer) Init(ctx context.Context, req *proto.InitRequest)
|
||||
// CreateVolumeFromSnapshot creates a new block volume, initialized from the provided snapshot,
|
||||
// and with the specified type and IOPS (if using provisioned IOPS).
|
||||
func (s *BlockStoreGRPCServer) CreateVolumeFromSnapshot(ctx context.Context, req *proto.CreateVolumeRequest) (*proto.CreateVolumeResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snapshotID := req.SnapshotID
|
||||
volumeType := req.VolumeType
|
||||
volumeAZ := req.VolumeAZ
|
||||
@@ -220,7 +260,7 @@ func (s *BlockStoreGRPCServer) CreateVolumeFromSnapshot(ctx context.Context, req
|
||||
iops = &req.Iops
|
||||
}
|
||||
|
||||
volumeID, err := s.impl.CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ, iops)
|
||||
volumeID, err := impl.CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ, iops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -231,7 +271,12 @@ func (s *BlockStoreGRPCServer) CreateVolumeFromSnapshot(ctx context.Context, req
|
||||
// GetVolumeInfo returns the type and IOPS (if using provisioned IOPS) for a specified block
|
||||
// volume.
|
||||
func (s *BlockStoreGRPCServer) GetVolumeInfo(ctx context.Context, req *proto.GetVolumeInfoRequest) (*proto.GetVolumeInfoResponse, error) {
|
||||
volumeType, iops, err := s.impl.GetVolumeInfo(req.VolumeID, req.VolumeAZ)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeType, iops, err := impl.GetVolumeInfo(req.VolumeID, req.VolumeAZ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -249,7 +294,12 @@ func (s *BlockStoreGRPCServer) GetVolumeInfo(ctx context.Context, req *proto.Get
|
||||
|
||||
// IsVolumeReady returns whether the specified volume is ready to be used.
|
||||
func (s *BlockStoreGRPCServer) IsVolumeReady(ctx context.Context, req *proto.IsVolumeReadyRequest) (*proto.IsVolumeReadyResponse, error) {
|
||||
ready, err := s.impl.IsVolumeReady(req.VolumeID, req.VolumeAZ)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ready, err := impl.IsVolumeReady(req.VolumeID, req.VolumeAZ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -260,7 +310,12 @@ func (s *BlockStoreGRPCServer) IsVolumeReady(ctx context.Context, req *proto.IsV
|
||||
// CreateSnapshot creates a snapshot of the specified block volume, and applies the provided
|
||||
// set of tags to the snapshot.
|
||||
func (s *BlockStoreGRPCServer) CreateSnapshot(ctx context.Context, req *proto.CreateSnapshotRequest) (*proto.CreateSnapshotResponse, error) {
|
||||
snapshotID, err := s.impl.CreateSnapshot(req.VolumeID, req.VolumeAZ, req.Tags)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snapshotID, err := impl.CreateSnapshot(req.VolumeID, req.VolumeAZ, req.Tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -270,7 +325,12 @@ func (s *BlockStoreGRPCServer) CreateSnapshot(ctx context.Context, req *proto.Cr
|
||||
|
||||
// DeleteSnapshot deletes the specified volume snapshot.
|
||||
func (s *BlockStoreGRPCServer) DeleteSnapshot(ctx context.Context, req *proto.DeleteSnapshotRequest) (*proto.Empty, error) {
|
||||
if err := s.impl.DeleteSnapshot(req.SnapshotID); err != nil {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := impl.DeleteSnapshot(req.SnapshotID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -278,13 +338,18 @@ func (s *BlockStoreGRPCServer) DeleteSnapshot(ctx context.Context, req *proto.De
|
||||
}
|
||||
|
||||
func (s *BlockStoreGRPCServer) GetVolumeID(ctx context.Context, req *proto.GetVolumeIDRequest) (*proto.GetVolumeIDResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pv unstructured.Unstructured
|
||||
|
||||
if err := json.Unmarshal(req.PersistentVolume, &pv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumeID, err := s.impl.GetVolumeID(&pv)
|
||||
volumeID, err := impl.GetVolumeID(&pv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -293,13 +358,18 @@ func (s *BlockStoreGRPCServer) GetVolumeID(ctx context.Context, req *proto.GetVo
|
||||
}
|
||||
|
||||
func (s *BlockStoreGRPCServer) SetVolumeID(ctx context.Context, req *proto.SetVolumeIDRequest) (*proto.SetVolumeIDResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pv unstructured.Unstructured
|
||||
|
||||
if err := json.Unmarshal(req.PersistentVolume, &pv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedPV, err := s.impl.SetVolumeID(&pv, req.VolumeID)
|
||||
updatedPV, err := impl.SetVolumeID(&pv, req.VolumeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,43 +1,74 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
hcplugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// clientBuilder builds go-plugin Clients.
|
||||
type clientBuilder struct {
|
||||
config *hcplugin.ClientConfig
|
||||
commandName string
|
||||
commandArgs []string
|
||||
clientLogger logrus.FieldLogger
|
||||
pluginLogger hclog.Logger
|
||||
}
|
||||
|
||||
func newClientBuilder(baseConfig *hcplugin.ClientConfig) *clientBuilder {
|
||||
return &clientBuilder{
|
||||
config: baseConfig,
|
||||
// newClientBuilder returns a new clientBuilder with commandName to name. If the command matches the currently running
|
||||
// process (i.e. ark), this also sets commandArgs to the internal Ark command to run plugins.
|
||||
func newClientBuilder(command string, logger logrus.FieldLogger, logLevel logrus.Level) *clientBuilder {
|
||||
b := &clientBuilder{
|
||||
commandName: command,
|
||||
clientLogger: logger,
|
||||
pluginLogger: newLogrusAdapter(logger, logLevel),
|
||||
}
|
||||
if command == os.Args[0] {
|
||||
// For plugins compiled into the ark executable, we need to run "ark run-plugins"
|
||||
b.commandArgs = []string{"run-plugins"}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func newLogrusAdapter(pluginLogger logrus.FieldLogger, logLevel logrus.Level) *logrusAdapter {
|
||||
return &logrusAdapter{impl: pluginLogger, level: logLevel}
|
||||
}
|
||||
|
||||
func (b *clientBuilder) clientConfig() *hcplugin.ClientConfig {
|
||||
return &hcplugin.ClientConfig{
|
||||
HandshakeConfig: Handshake,
|
||||
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
|
||||
Plugins: map[string]hcplugin.Plugin{
|
||||
string(PluginKindBackupItemAction): NewBackupItemActionPlugin(clientLogger(b.clientLogger)),
|
||||
string(PluginKindBlockStore): NewBlockStorePlugin(clientLogger(b.clientLogger)),
|
||||
string(PluginKindObjectStore): NewObjectStorePlugin(clientLogger(b.clientLogger)),
|
||||
string(PluginKindPluginLister): &PluginListerPlugin{},
|
||||
string(PluginKindRestoreItemAction): NewRestoreItemActionPlugin(clientLogger(b.clientLogger)),
|
||||
},
|
||||
Logger: b.pluginLogger,
|
||||
Cmd: exec.Command(b.commandName, b.commandArgs...),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *clientBuilder) withPlugin(kind PluginKind, plugin hcplugin.Plugin) *clientBuilder {
|
||||
if b.config.Plugins == nil {
|
||||
b.config.Plugins = make(map[string]hcplugin.Plugin)
|
||||
}
|
||||
b.config.Plugins[string(kind)] = plugin
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *clientBuilder) withLogger(logger hclog.Logger) *clientBuilder {
|
||||
b.config.Logger = logger
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *clientBuilder) withCommand(name string, args ...string) *clientBuilder {
|
||||
b.config.Cmd = exec.Command(name, args...)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// client creates a new go-plugin Client with support for all of Ark's plugin kinds (BackupItemAction, BlockStore,
|
||||
// ObjectStore, PluginLister, RestoreItemAction).
|
||||
func (b *clientBuilder) client() *hcplugin.Client {
|
||||
return hcplugin.NewClient(b.config)
|
||||
return hcplugin.NewClient(b.clientConfig())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
hcplugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewClientBuilder(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
cb := newClientBuilder("ark", logger, logLevel)
|
||||
assert.Equal(t, cb.commandName, "ark")
|
||||
assert.Empty(t, cb.commandArgs)
|
||||
assert.Equal(t, newLogrusAdapter(logger, logLevel), cb.pluginLogger)
|
||||
|
||||
cb = newClientBuilder(os.Args[0], logger, logLevel)
|
||||
assert.Equal(t, cb.commandName, os.Args[0])
|
||||
assert.Equal(t, []string{"run-plugins"}, cb.commandArgs)
|
||||
assert.Equal(t, newLogrusAdapter(logger, logLevel), cb.pluginLogger)
|
||||
}
|
||||
|
||||
func TestClientConfig(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
cb := newClientBuilder("ark", logger, logLevel)
|
||||
|
||||
expected := &hcplugin.ClientConfig{
|
||||
HandshakeConfig: Handshake,
|
||||
AllowedProtocols: []hcplugin.Protocol{hcplugin.ProtocolGRPC},
|
||||
Plugins: map[string]hcplugin.Plugin{
|
||||
string(PluginKindBackupItemAction): NewBackupItemActionPlugin(clientLogger(logger)),
|
||||
string(PluginKindBlockStore): NewBlockStorePlugin(clientLogger(logger)),
|
||||
string(PluginKindObjectStore): NewObjectStorePlugin(clientLogger(logger)),
|
||||
string(PluginKindPluginLister): &PluginListerPlugin{},
|
||||
string(PluginKindRestoreItemAction): NewRestoreItemActionPlugin(clientLogger(logger)),
|
||||
},
|
||||
Logger: cb.pluginLogger,
|
||||
Cmd: exec.Command(cb.commandName, cb.commandArgs...),
|
||||
}
|
||||
|
||||
cc := cb.clientConfig()
|
||||
assert.Equal(t, expected, cc)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// clientBase implements client and contains shared fields common to all clients.
|
||||
type clientBase struct {
|
||||
plugin string
|
||||
logger logrus.FieldLogger
|
||||
}
|
||||
|
||||
type ClientDispenser interface {
|
||||
clientFor(name string) interface{}
|
||||
}
|
||||
|
||||
// clientDispenser supports the initialization and retrieval of multiple implementations for a single plugin kind, such as
|
||||
// "aws" and "azure" implementations of the object store plugin.
|
||||
type clientDispenser struct {
|
||||
// logger is the log the plugin should use.
|
||||
logger logrus.FieldLogger
|
||||
// clienConn is shared among all implementations for this client.
|
||||
clientConn *grpc.ClientConn
|
||||
// initFunc returns a client that implements a plugin interface, such as cloudprovider.ObjectStore.
|
||||
initFunc clientInitFunc
|
||||
// clients keeps track of all the initialized implementations.
|
||||
clients map[string]interface{}
|
||||
}
|
||||
|
||||
type clientInitFunc func(base *clientBase, clientConn *grpc.ClientConn) interface{}
|
||||
|
||||
// newClientDispenser creates a new clientDispenser.
|
||||
func newClientDispenser(logger logrus.FieldLogger, clientConn *grpc.ClientConn, initFunc clientInitFunc) *clientDispenser {
|
||||
return &clientDispenser{
|
||||
clientConn: clientConn,
|
||||
logger: logger,
|
||||
initFunc: initFunc,
|
||||
clients: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// clientFor returns a gRPC client stub for the implementation of a plugin named name. If the client stub does not
|
||||
// currently exist, clientFor creates it.
|
||||
func (cd *clientDispenser) clientFor(name string) interface{} {
|
||||
if client, found := cd.clients[name]; found {
|
||||
return client
|
||||
}
|
||||
|
||||
base := &clientBase{
|
||||
plugin: name,
|
||||
logger: cd.logger,
|
||||
}
|
||||
// Initialize the plugin (e.g. newBackupItemActionGRPCClient())
|
||||
client := cd.initFunc(base, cd.clientConn)
|
||||
cd.clients[name] = client
|
||||
|
||||
return client
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
base *clientBase
|
||||
clientConn *grpc.ClientConn
|
||||
}
|
||||
|
||||
func TestNewClientDispenser(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
|
||||
clientConn := new(grpc.ClientConn)
|
||||
|
||||
c := 3
|
||||
initFunc := func(base *clientBase, clientConn *grpc.ClientConn) interface{} {
|
||||
return c
|
||||
}
|
||||
|
||||
cd := newClientDispenser(logger, clientConn, initFunc)
|
||||
assert.Equal(t, clientConn, cd.clientConn)
|
||||
assert.NotNil(t, cd.clients)
|
||||
assert.Empty(t, cd.clients)
|
||||
}
|
||||
|
||||
func TestClientFor(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
clientConn := new(grpc.ClientConn)
|
||||
|
||||
c := new(fakeClient)
|
||||
count := 0
|
||||
initFunc := func(base *clientBase, clientConn *grpc.ClientConn) interface{} {
|
||||
c.base = base
|
||||
c.clientConn = clientConn
|
||||
count++
|
||||
return c
|
||||
}
|
||||
|
||||
cd := newClientDispenser(logger, clientConn, initFunc)
|
||||
|
||||
actual := cd.clientFor("pod")
|
||||
require.IsType(t, &fakeClient{}, actual)
|
||||
typed := actual.(*fakeClient)
|
||||
assert.Equal(t, 1, count)
|
||||
assert.Equal(t, &typed, &c)
|
||||
expectedBase := &clientBase{
|
||||
plugin: "pod",
|
||||
logger: logger,
|
||||
}
|
||||
assert.Equal(t, expectedBase, typed.base)
|
||||
assert.Equal(t, clientConn, typed.clientConn)
|
||||
|
||||
// Make sure we reuse a previous client
|
||||
actual = cd.clientFor("pod")
|
||||
require.IsType(t, &fakeClient{}, actual)
|
||||
typed = actual.(*fakeClient)
|
||||
assert.Equal(t, 1, count)
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// clientKey is a unique ID for a plugin client.
|
||||
type clientKey struct {
|
||||
kind PluginKind
|
||||
|
||||
// scope is an additional identifier that allows multiple clients
|
||||
// for the same kind/name to be differentiated. It will typically
|
||||
// be the name of the applicable backup/restore for ItemAction
|
||||
// clients, and blank for Object/BlockStore clients.
|
||||
scope string
|
||||
}
|
||||
|
||||
func newClientStore() *clientStore {
|
||||
return &clientStore{
|
||||
clients: make(map[clientKey]map[string]*plugin.Client),
|
||||
lock: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
// clientStore is a repository of active plugin clients.
|
||||
type clientStore struct {
|
||||
// clients is a nested map, keyed first by clientKey (a
|
||||
// combo of kind and "scope"), and second by plugin name.
|
||||
// This enables easy listing of all clients for a given
|
||||
// kind and scope (e.g. all BackupItemActions for a given
|
||||
// backup), and efficient lookup by kind+name+scope (e.g.
|
||||
// the AWS ObjectStore.)
|
||||
clients map[clientKey]map[string]*plugin.Client
|
||||
lock *sync.RWMutex
|
||||
}
|
||||
|
||||
// get returns a plugin client for the given kind/name/scope, or an error if none
|
||||
// is found.
|
||||
func (s *clientStore) get(kind PluginKind, name, scope string) (*plugin.Client, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
if forScope, found := s.clients[clientKey{kind, scope}]; found {
|
||||
if client, found := forScope[name]; found {
|
||||
return client, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("client not found")
|
||||
}
|
||||
|
||||
// list returns all plugin clients for the given kind/scope, or an
|
||||
// error if none are found.
|
||||
func (s *clientStore) list(kind PluginKind, scope string) ([]*plugin.Client, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
if forScope, found := s.clients[clientKey{kind, scope}]; found {
|
||||
var clients []*plugin.Client
|
||||
|
||||
for _, client := range forScope {
|
||||
clients = append(clients, client)
|
||||
}
|
||||
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("clients not found")
|
||||
}
|
||||
|
||||
// add stores a plugin client for the given kind/name/scope.
|
||||
func (s *clientStore) add(client *plugin.Client, kind PluginKind, name, scope string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
key := clientKey{kind, scope}
|
||||
|
||||
if _, found := s.clients[key]; !found {
|
||||
s.clients[key] = make(map[string]*plugin.Client)
|
||||
}
|
||||
|
||||
s.clients[key][name] = client
|
||||
}
|
||||
|
||||
// delete removes the client with the given kind/name/scope from the store.
|
||||
func (s *clientStore) delete(kind PluginKind, name, scope string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if forScope, found := s.clients[clientKey{kind, scope}]; found {
|
||||
delete(forScope, name)
|
||||
}
|
||||
}
|
||||
|
||||
// deleteAll removes all clients with the given kind/scope from
|
||||
// the store.
|
||||
func (s *clientStore) deleteAll(kind PluginKind, scope string) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
delete(s.clients, clientKey{kind, scope})
|
||||
}
|
||||
@@ -8,6 +8,7 @@ It is generated from these files:
|
||||
BackupItemAction.proto
|
||||
BlockStore.proto
|
||||
ObjectStore.proto
|
||||
PluginLister.proto
|
||||
RestoreItemAction.proto
|
||||
Shared.proto
|
||||
|
||||
@@ -38,10 +39,13 @@ It has these top-level messages:
|
||||
DeleteObjectRequest
|
||||
CreateSignedURLRequest
|
||||
CreateSignedURLResponse
|
||||
PluginIdentifier
|
||||
ListPluginsResponse
|
||||
RestoreExecuteRequest
|
||||
RestoreExecuteResponse
|
||||
Empty
|
||||
InitRequest
|
||||
AppliesToRequest
|
||||
AppliesToResponse
|
||||
*/
|
||||
package generated
|
||||
@@ -67,8 +71,9 @@ var _ = math.Inf
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type ExecuteRequest struct {
|
||||
Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"`
|
||||
Backup []byte `protobuf:"bytes,2,opt,name=backup,proto3" json:"backup,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
|
||||
Backup []byte `protobuf:"bytes,3,opt,name=backup,proto3" json:"backup,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ExecuteRequest) Reset() { *m = ExecuteRequest{} }
|
||||
@@ -76,6 +81,13 @@ func (m *ExecuteRequest) String() string { return proto.CompactTextSt
|
||||
func (*ExecuteRequest) ProtoMessage() {}
|
||||
func (*ExecuteRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||
|
||||
func (m *ExecuteRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ExecuteRequest) GetItem() []byte {
|
||||
if m != nil {
|
||||
return m.Item
|
||||
@@ -171,7 +183,7 @@ const _ = grpc.SupportPackageIsVersion4
|
||||
// Client API for BackupItemAction service
|
||||
|
||||
type BackupItemActionClient interface {
|
||||
AppliesTo(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*AppliesToResponse, error)
|
||||
AppliesTo(ctx context.Context, in *AppliesToRequest, opts ...grpc.CallOption) (*AppliesToResponse, error)
|
||||
Execute(ctx context.Context, in *ExecuteRequest, opts ...grpc.CallOption) (*ExecuteResponse, error)
|
||||
}
|
||||
|
||||
@@ -183,7 +195,7 @@ func NewBackupItemActionClient(cc *grpc.ClientConn) BackupItemActionClient {
|
||||
return &backupItemActionClient{cc}
|
||||
}
|
||||
|
||||
func (c *backupItemActionClient) AppliesTo(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*AppliesToResponse, error) {
|
||||
func (c *backupItemActionClient) AppliesTo(ctx context.Context, in *AppliesToRequest, opts ...grpc.CallOption) (*AppliesToResponse, error) {
|
||||
out := new(AppliesToResponse)
|
||||
err := grpc.Invoke(ctx, "/generated.BackupItemAction/AppliesTo", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
@@ -204,7 +216,7 @@ func (c *backupItemActionClient) Execute(ctx context.Context, in *ExecuteRequest
|
||||
// Server API for BackupItemAction service
|
||||
|
||||
type BackupItemActionServer interface {
|
||||
AppliesTo(context.Context, *Empty) (*AppliesToResponse, error)
|
||||
AppliesTo(context.Context, *AppliesToRequest) (*AppliesToResponse, error)
|
||||
Execute(context.Context, *ExecuteRequest) (*ExecuteResponse, error)
|
||||
}
|
||||
|
||||
@@ -213,7 +225,7 @@ func RegisterBackupItemActionServer(s *grpc.Server, srv BackupItemActionServer)
|
||||
}
|
||||
|
||||
func _BackupItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
in := new(AppliesToRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -225,7 +237,7 @@ func _BackupItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context, d
|
||||
FullMethod: "/generated.BackupItemAction/AppliesTo",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(BackupItemActionServer).AppliesTo(ctx, req.(*Empty))
|
||||
return srv.(BackupItemActionServer).AppliesTo(ctx, req.(*AppliesToRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@@ -268,23 +280,24 @@ var _BackupItemAction_serviceDesc = grpc.ServiceDesc{
|
||||
func init() { proto.RegisterFile("BackupItemAction.proto", fileDescriptor0) }
|
||||
|
||||
var fileDescriptor0 = []byte{
|
||||
// 288 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0xc1, 0x4e, 0xc2, 0x40,
|
||||
0x10, 0x4d, 0x01, 0xd1, 0x8e, 0x44, 0xc8, 0xc4, 0x90, 0xda, 0x60, 0x42, 0x7a, 0xe2, 0xd4, 0x03,
|
||||
0x1e, 0xf5, 0x20, 0x26, 0xc4, 0x70, 0x5d, 0xfd, 0x81, 0xa5, 0x1d, 0x71, 0x23, 0xdd, 0x5d, 0x77,
|
||||
0xb7, 0x09, 0x7e, 0x86, 0x7f, 0x6c, 0xba, 0x6d, 0x6a, 0x45, 0x6e, 0xfb, 0xe6, 0xcd, 0x9b, 0x7d,
|
||||
0xf3, 0x06, 0xa6, 0x4f, 0x3c, 0xfb, 0x28, 0xf5, 0xc6, 0x51, 0xb1, 0xca, 0x9c, 0x50, 0x32, 0xd5,
|
||||
0x46, 0x39, 0x85, 0xe1, 0x8e, 0x24, 0x19, 0xee, 0x28, 0x8f, 0x47, 0x2f, 0xef, 0xdc, 0x50, 0x5e,
|
||||
0x13, 0xc9, 0x03, 0x5c, 0xad, 0x0f, 0x94, 0x95, 0x8e, 0x18, 0x7d, 0x96, 0x64, 0x1d, 0x22, 0x0c,
|
||||
0x84, 0xa3, 0x22, 0x0a, 0xe6, 0xc1, 0x62, 0xc4, 0xfc, 0x1b, 0xa7, 0x30, 0xdc, 0xfa, 0xc1, 0x51,
|
||||
0xcf, 0x57, 0x1b, 0x94, 0x48, 0x18, 0xb7, 0x6a, 0xab, 0x95, 0xb4, 0x74, 0x52, 0xfe, 0x0c, 0x63,
|
||||
0x9e, 0xe7, 0xa2, 0xf2, 0xc3, 0xf7, 0x95, 0x37, 0x1b, 0xf5, 0xe6, 0xfd, 0xc5, 0xe5, 0xf2, 0x36,
|
||||
0x6d, 0x7d, 0xa5, 0x8c, 0xac, 0x2a, 0x4d, 0x46, 0x9b, 0x9c, 0xa4, 0x13, 0x6f, 0x82, 0x0c, 0x3b,
|
||||
0x56, 0x25, 0x07, 0xc0, 0xff, 0x6d, 0x78, 0x0d, 0x67, 0x3b, 0xa3, 0x4a, 0xed, 0xff, 0x0c, 0x59,
|
||||
0x0d, 0x30, 0x86, 0x0b, 0xd3, 0xf4, 0x7a, 0xd7, 0x21, 0x6b, 0x31, 0xce, 0x20, 0x94, 0xbc, 0x20,
|
||||
0xab, 0x79, 0x46, 0x51, 0xdf, 0x93, 0xbf, 0x85, 0x6a, 0x85, 0x0a, 0x44, 0x03, 0x4f, 0xf8, 0xf7,
|
||||
0xf2, 0x3b, 0x80, 0xc9, 0x71, 0xb6, 0x78, 0x0f, 0xe1, 0x4a, 0xeb, 0xbd, 0x20, 0xfb, 0xaa, 0x70,
|
||||
0xd2, 0xd9, 0x65, 0x5d, 0x68, 0xf7, 0x15, 0xcf, 0x3a, 0x95, 0xb6, 0xaf, 0x0d, 0xea, 0x11, 0xce,
|
||||
0x9b, 0xec, 0xf0, 0xa6, 0x2b, 0xfd, 0x73, 0x8d, 0x38, 0x3e, 0x45, 0xd5, 0x13, 0xb6, 0x43, 0x7f,
|
||||
0xc2, 0xbb, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x44, 0x09, 0x4d, 0x36, 0xf5, 0x01, 0x00, 0x00,
|
||||
// 298 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x51, 0x4d, 0x4f, 0x02, 0x31,
|
||||
0x10, 0xcd, 0x02, 0xa2, 0x1d, 0x89, 0x98, 0xc6, 0x90, 0x75, 0xc5, 0x84, 0x70, 0xe2, 0xc4, 0x01,
|
||||
0xff, 0x80, 0x98, 0xa8, 0xe1, 0x5a, 0xf9, 0x03, 0x65, 0x77, 0xc4, 0xc6, 0xdd, 0xb6, 0xf6, 0x23,
|
||||
0xe1, 0xc7, 0xf8, 0x63, 0xcd, 0x76, 0x9b, 0x75, 0x45, 0x6e, 0x7d, 0xf3, 0xde, 0x4c, 0xe7, 0xbd,
|
||||
0x81, 0xc9, 0x13, 0xcf, 0x3f, 0xbd, 0xde, 0x38, 0xac, 0xd6, 0xb9, 0x13, 0x4a, 0x2e, 0xb5, 0x51,
|
||||
0x4e, 0x51, 0xb2, 0x47, 0x89, 0x86, 0x3b, 0x2c, 0xb2, 0xd1, 0xdb, 0x07, 0x37, 0x58, 0x34, 0xc4,
|
||||
0x7c, 0x0b, 0x57, 0xcf, 0x07, 0xcc, 0xbd, 0x43, 0x86, 0x5f, 0x1e, 0xad, 0xa3, 0x13, 0x18, 0xea,
|
||||
0xd2, 0xef, 0x85, 0x4c, 0x93, 0x59, 0xb2, 0x20, 0x2c, 0x22, 0x4a, 0x61, 0x20, 0x1c, 0x56, 0x69,
|
||||
0x6f, 0x96, 0x2c, 0x46, 0x2c, 0xbc, 0x6b, 0xed, 0x2e, 0x7c, 0x98, 0xf6, 0x43, 0x35, 0xa2, 0xb9,
|
||||
0x84, 0x71, 0x3b, 0xd5, 0x6a, 0x25, 0x2d, 0xb6, 0xed, 0x49, 0xa7, 0xfd, 0x15, 0xc6, 0xbc, 0x28,
|
||||
0x44, 0xbd, 0x27, 0x2f, 0xeb, 0x9d, 0x6d, 0xda, 0x9b, 0xf5, 0x17, 0x97, 0xab, 0xfb, 0x65, 0xbb,
|
||||
0xef, 0x92, 0xa1, 0x55, 0xde, 0xe4, 0xb8, 0x29, 0x50, 0x3a, 0xf1, 0x2e, 0xd0, 0xb0, 0xe3, 0xae,
|
||||
0xf9, 0x01, 0xe8, 0x7f, 0x19, 0xbd, 0x81, 0xb3, 0xbd, 0x51, 0x5e, 0x47, 0x23, 0x0d, 0xa0, 0x19,
|
||||
0x5c, 0x98, 0xa8, 0x0d, 0x5e, 0x08, 0x6b, 0x31, 0x9d, 0x02, 0x91, 0xbc, 0x42, 0xab, 0x79, 0x8e,
|
||||
0xc1, 0x12, 0x61, 0xbf, 0x85, 0xda, 0x42, 0x0d, 0xd2, 0x41, 0x20, 0xc2, 0x7b, 0xf5, 0x9d, 0xc0,
|
||||
0xf5, 0x71, 0xe6, 0xf4, 0x05, 0xc8, 0x5a, 0xeb, 0x52, 0xa0, 0xdd, 0x2a, 0x7a, 0xd7, 0xf1, 0xd2,
|
||||
0x56, 0x63, 0xd8, 0xd9, 0xf4, 0x34, 0x19, 0x33, 0x7b, 0x84, 0xf3, 0x18, 0x23, 0xbd, 0xed, 0x08,
|
||||
0xff, 0x1e, 0x2c, 0xcb, 0x4e, 0x51, 0xcd, 0x84, 0xdd, 0x30, 0x5c, 0xf9, 0xe1, 0x27, 0x00, 0x00,
|
||||
0xff, 0xff, 0x56, 0x1b, 0x70, 0xd6, 0x18, 0x02, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -18,10 +18,11 @@ var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type CreateVolumeRequest struct {
|
||||
SnapshotID string `protobuf:"bytes,1,opt,name=snapshotID" json:"snapshotID,omitempty"`
|
||||
VolumeType string `protobuf:"bytes,2,opt,name=volumeType" json:"volumeType,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,3,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
Iops int64 `protobuf:"varint,4,opt,name=iops" json:"iops,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
SnapshotID string `protobuf:"bytes,2,opt,name=snapshotID" json:"snapshotID,omitempty"`
|
||||
VolumeType string `protobuf:"bytes,3,opt,name=volumeType" json:"volumeType,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,4,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
Iops int64 `protobuf:"varint,5,opt,name=iops" json:"iops,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CreateVolumeRequest) Reset() { *m = CreateVolumeRequest{} }
|
||||
@@ -29,6 +30,13 @@ func (m *CreateVolumeRequest) String() string { return proto.CompactT
|
||||
func (*CreateVolumeRequest) ProtoMessage() {}
|
||||
func (*CreateVolumeRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{0} }
|
||||
|
||||
func (m *CreateVolumeRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *CreateVolumeRequest) GetSnapshotID() string {
|
||||
if m != nil {
|
||||
return m.SnapshotID
|
||||
@@ -74,8 +82,9 @@ func (m *CreateVolumeResponse) GetVolumeID() string {
|
||||
}
|
||||
|
||||
type GetVolumeInfoRequest struct {
|
||||
VolumeID string `protobuf:"bytes,1,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,2,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
VolumeID string `protobuf:"bytes,2,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,3,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
}
|
||||
|
||||
func (m *GetVolumeInfoRequest) Reset() { *m = GetVolumeInfoRequest{} }
|
||||
@@ -83,6 +92,13 @@ func (m *GetVolumeInfoRequest) String() string { return proto.Compact
|
||||
func (*GetVolumeInfoRequest) ProtoMessage() {}
|
||||
func (*GetVolumeInfoRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{2} }
|
||||
|
||||
func (m *GetVolumeInfoRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *GetVolumeInfoRequest) GetVolumeID() string {
|
||||
if m != nil {
|
||||
return m.VolumeID
|
||||
@@ -122,8 +138,9 @@ func (m *GetVolumeInfoResponse) GetIops() int64 {
|
||||
}
|
||||
|
||||
type IsVolumeReadyRequest struct {
|
||||
VolumeID string `protobuf:"bytes,1,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,2,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
VolumeID string `protobuf:"bytes,2,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,3,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
}
|
||||
|
||||
func (m *IsVolumeReadyRequest) Reset() { *m = IsVolumeReadyRequest{} }
|
||||
@@ -131,6 +148,13 @@ func (m *IsVolumeReadyRequest) String() string { return proto.Compact
|
||||
func (*IsVolumeReadyRequest) ProtoMessage() {}
|
||||
func (*IsVolumeReadyRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{4} }
|
||||
|
||||
func (m *IsVolumeReadyRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *IsVolumeReadyRequest) GetVolumeID() string {
|
||||
if m != nil {
|
||||
return m.VolumeID
|
||||
@@ -162,9 +186,10 @@ func (m *IsVolumeReadyResponse) GetReady() bool {
|
||||
}
|
||||
|
||||
type CreateSnapshotRequest struct {
|
||||
VolumeID string `protobuf:"bytes,1,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,2,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
Tags map[string]string `protobuf:"bytes,3,rep,name=tags" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
VolumeID string `protobuf:"bytes,2,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
VolumeAZ string `protobuf:"bytes,3,opt,name=volumeAZ" json:"volumeAZ,omitempty"`
|
||||
Tags map[string]string `protobuf:"bytes,4,rep,name=tags" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
}
|
||||
|
||||
func (m *CreateSnapshotRequest) Reset() { *m = CreateSnapshotRequest{} }
|
||||
@@ -172,6 +197,13 @@ func (m *CreateSnapshotRequest) String() string { return proto.Compac
|
||||
func (*CreateSnapshotRequest) ProtoMessage() {}
|
||||
func (*CreateSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{6} }
|
||||
|
||||
func (m *CreateSnapshotRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *CreateSnapshotRequest) GetVolumeID() string {
|
||||
if m != nil {
|
||||
return m.VolumeID
|
||||
@@ -210,7 +242,8 @@ func (m *CreateSnapshotResponse) GetSnapshotID() string {
|
||||
}
|
||||
|
||||
type DeleteSnapshotRequest struct {
|
||||
SnapshotID string `protobuf:"bytes,1,opt,name=snapshotID" json:"snapshotID,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
SnapshotID string `protobuf:"bytes,2,opt,name=snapshotID" json:"snapshotID,omitempty"`
|
||||
}
|
||||
|
||||
func (m *DeleteSnapshotRequest) Reset() { *m = DeleteSnapshotRequest{} }
|
||||
@@ -218,6 +251,13 @@ func (m *DeleteSnapshotRequest) String() string { return proto.Compac
|
||||
func (*DeleteSnapshotRequest) ProtoMessage() {}
|
||||
func (*DeleteSnapshotRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{8} }
|
||||
|
||||
func (m *DeleteSnapshotRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *DeleteSnapshotRequest) GetSnapshotID() string {
|
||||
if m != nil {
|
||||
return m.SnapshotID
|
||||
@@ -226,7 +266,8 @@ func (m *DeleteSnapshotRequest) GetSnapshotID() string {
|
||||
}
|
||||
|
||||
type GetVolumeIDRequest struct {
|
||||
PersistentVolume []byte `protobuf:"bytes,1,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
PersistentVolume []byte `protobuf:"bytes,2,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"`
|
||||
}
|
||||
|
||||
func (m *GetVolumeIDRequest) Reset() { *m = GetVolumeIDRequest{} }
|
||||
@@ -234,6 +275,13 @@ func (m *GetVolumeIDRequest) String() string { return proto.CompactTe
|
||||
func (*GetVolumeIDRequest) ProtoMessage() {}
|
||||
func (*GetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{9} }
|
||||
|
||||
func (m *GetVolumeIDRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *GetVolumeIDRequest) GetPersistentVolume() []byte {
|
||||
if m != nil {
|
||||
return m.PersistentVolume
|
||||
@@ -258,8 +306,9 @@ func (m *GetVolumeIDResponse) GetVolumeID() string {
|
||||
}
|
||||
|
||||
type SetVolumeIDRequest struct {
|
||||
PersistentVolume []byte `protobuf:"bytes,1,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"`
|
||||
VolumeID string `protobuf:"bytes,2,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
PersistentVolume []byte `protobuf:"bytes,2,opt,name=persistentVolume,proto3" json:"persistentVolume,omitempty"`
|
||||
VolumeID string `protobuf:"bytes,3,opt,name=volumeID" json:"volumeID,omitempty"`
|
||||
}
|
||||
|
||||
func (m *SetVolumeIDRequest) Reset() { *m = SetVolumeIDRequest{} }
|
||||
@@ -267,6 +316,13 @@ func (m *SetVolumeIDRequest) String() string { return proto.CompactTe
|
||||
func (*SetVolumeIDRequest) ProtoMessage() {}
|
||||
func (*SetVolumeIDRequest) Descriptor() ([]byte, []int) { return fileDescriptor1, []int{11} }
|
||||
|
||||
func (m *SetVolumeIDRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *SetVolumeIDRequest) GetPersistentVolume() []byte {
|
||||
if m != nil {
|
||||
return m.PersistentVolume
|
||||
@@ -619,39 +675,41 @@ var _BlockStore_serviceDesc = grpc.ServiceDesc{
|
||||
func init() { proto.RegisterFile("BlockStore.proto", fileDescriptor1) }
|
||||
|
||||
var fileDescriptor1 = []byte{
|
||||
// 540 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x55, 0x4d, 0x6f, 0xd3, 0x40,
|
||||
0x10, 0x95, 0x3f, 0x40, 0xcd, 0xa4, 0x54, 0xd1, 0x26, 0xa9, 0x2c, 0x4b, 0x04, 0xe3, 0x53, 0x54,
|
||||
0x89, 0x08, 0xc2, 0xa1, 0x15, 0x07, 0x44, 0x21, 0x05, 0x45, 0x54, 0x3d, 0xd8, 0x85, 0x03, 0x70,
|
||||
0x31, 0x64, 0x48, 0xa3, 0x26, 0x5e, 0xe3, 0xdd, 0x54, 0xf2, 0x0f, 0xe0, 0xbf, 0xf1, 0xa3, 0x38,
|
||||
0x20, 0xdb, 0xeb, 0x8f, 0x4d, 0x36, 0x09, 0x55, 0x6e, 0x9e, 0x99, 0x7d, 0x6f, 0xdf, 0xec, 0xbe,
|
||||
0x59, 0x43, 0xeb, 0xed, 0x9c, 0xfe, 0xb8, 0xf5, 0x39, 0x8d, 0x71, 0x10, 0xc5, 0x94, 0x53, 0xd2,
|
||||
0x98, 0x62, 0x88, 0x71, 0xc0, 0x71, 0x62, 0x1f, 0xfa, 0x37, 0x41, 0x8c, 0x93, 0xbc, 0xe0, 0xfe,
|
||||
0xd6, 0xa0, 0xfd, 0x2e, 0xc6, 0x80, 0xe3, 0x67, 0x3a, 0x5f, 0x2e, 0xd0, 0xc3, 0x5f, 0x4b, 0x64,
|
||||
0x9c, 0xf4, 0x00, 0x58, 0x18, 0x44, 0xec, 0x86, 0xf2, 0xf1, 0xc8, 0xd2, 0x1c, 0xad, 0xdf, 0xf0,
|
||||
0x6a, 0x99, 0xb4, 0x7e, 0x97, 0x01, 0xae, 0x93, 0x08, 0x2d, 0x3d, 0xaf, 0x57, 0x19, 0x62, 0xc3,
|
||||
0x41, 0x1e, 0x9d, 0x7f, 0xb1, 0x8c, 0xac, 0x5a, 0xc6, 0x84, 0x80, 0x39, 0xa3, 0x11, 0xb3, 0x4c,
|
||||
0x47, 0xeb, 0x1b, 0x5e, 0xf6, 0xed, 0x0e, 0xa1, 0x23, 0xcb, 0x60, 0x11, 0x0d, 0x59, 0x8d, 0xa7,
|
||||
0x54, 0x51, 0xc6, 0xee, 0x15, 0x74, 0x3e, 0x20, 0xcf, 0x01, 0xe3, 0xf0, 0x27, 0x2d, 0xb4, 0x6f,
|
||||
0xc1, 0x48, 0xba, 0x74, 0x59, 0x97, 0xfb, 0x11, 0xba, 0x2b, 0x7c, 0x42, 0x84, 0xdc, 0xac, 0xb6,
|
||||
0xd6, 0x6c, 0xd1, 0x90, 0x5e, 0x6b, 0xe8, 0x0a, 0x3a, 0x63, 0x56, 0x34, 0x13, 0x4c, 0x92, 0x7d,
|
||||
0xc5, 0x3d, 0x83, 0xee, 0x0a, 0x9f, 0x10, 0xd7, 0x81, 0x07, 0x71, 0x9a, 0xc8, 0xd8, 0x0e, 0xbc,
|
||||
0x3c, 0x70, 0xff, 0x68, 0xd0, 0xcd, 0x0f, 0xd4, 0x17, 0x97, 0xb6, 0xa7, 0x00, 0xf2, 0x1a, 0x4c,
|
||||
0x1e, 0x4c, 0x99, 0x65, 0x38, 0x46, 0xbf, 0x39, 0x3c, 0x19, 0x94, 0x8e, 0x1a, 0x28, 0xf7, 0x19,
|
||||
0x5c, 0x07, 0x53, 0x76, 0x11, 0xf2, 0x38, 0xf1, 0x32, 0x9c, 0x7d, 0x0a, 0x8d, 0x32, 0x45, 0x5a,
|
||||
0x60, 0xdc, 0x62, 0x22, 0xf6, 0x4f, 0x3f, 0xd3, 0x36, 0xee, 0x82, 0xf9, 0xb2, 0xf0, 0x52, 0x1e,
|
||||
0xbc, 0xd2, 0xcf, 0x34, 0xf7, 0x0c, 0x8e, 0x57, 0x77, 0xa8, 0xee, 0x65, 0x9b, 0x49, 0xdd, 0x53,
|
||||
0xe8, 0x8e, 0x70, 0x8e, 0xeb, 0x67, 0xb0, 0x0b, 0xf8, 0x06, 0x48, 0xe5, 0x84, 0x51, 0x81, 0x3a,
|
||||
0x81, 0x56, 0x84, 0x31, 0x9b, 0x31, 0x8e, 0xa1, 0x28, 0x66, 0xd8, 0x43, 0x6f, 0x2d, 0xef, 0xbe,
|
||||
0x80, 0xb6, 0xc4, 0xf0, 0x1f, 0x76, 0xfe, 0x06, 0xc4, 0xdf, 0x6b, 0x53, 0x89, 0x5d, 0x5f, 0x61,
|
||||
0x3f, 0x87, 0xb6, 0xaf, 0x10, 0x74, 0x0f, 0xfa, 0xe1, 0x5f, 0x13, 0xa0, 0x7a, 0x59, 0xc8, 0x73,
|
||||
0x30, 0xc7, 0xe1, 0x8c, 0x93, 0xe3, 0x9a, 0x15, 0xd2, 0x84, 0x50, 0x6e, 0xb7, 0x6a, 0xf9, 0x8b,
|
||||
0x45, 0xc4, 0x13, 0xf2, 0x15, 0xac, 0xfa, 0x90, 0xbf, 0x8f, 0xe9, 0xa2, 0xb8, 0x19, 0xd2, 0x5b,
|
||||
0x33, 0x94, 0xf4, 0x20, 0xd9, 0x4f, 0x36, 0xd6, 0x45, 0x27, 0x1e, 0x3c, 0x92, 0xa6, 0x97, 0xd4,
|
||||
0x11, 0xaa, 0x77, 0xc2, 0x76, 0x36, 0x2f, 0xa8, 0x38, 0xa5, 0xa1, 0x93, 0x38, 0x55, 0xe3, 0x2d,
|
||||
0x71, 0xaa, 0xe7, 0xf5, 0x13, 0x1c, 0xc9, 0x76, 0x26, 0xce, 0xae, 0x59, 0xb2, 0x9f, 0x6e, 0x59,
|
||||
0x21, 0x68, 0x47, 0x70, 0x24, 0x7b, 0x5d, 0xa2, 0x55, 0x8e, 0x81, 0xe2, 0x86, 0x2e, 0xa1, 0x59,
|
||||
0xb3, 0x2d, 0x79, 0xac, 0x3c, 0xa1, 0xc2, 0x9b, 0x76, 0x6f, 0x53, 0x59, 0x68, 0xba, 0x84, 0xa6,
|
||||
0xbf, 0x81, 0xcd, 0xdf, 0xce, 0xa6, 0xb0, 0xea, 0xf7, 0x87, 0xd9, 0x1f, 0xeb, 0xe5, 0xbf, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0x76, 0x14, 0x93, 0xa6, 0xde, 0x06, 0x00, 0x00,
|
||||
// 566 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x55, 0xcd, 0x6e, 0xd3, 0x40,
|
||||
0x10, 0x96, 0x63, 0xb7, 0x6a, 0x26, 0xa5, 0x8a, 0x36, 0x3f, 0xb2, 0x2c, 0x11, 0x8c, 0x4f, 0x51,
|
||||
0x25, 0x22, 0x08, 0x07, 0x2a, 0x0e, 0x48, 0x05, 0x17, 0x64, 0x51, 0x09, 0xc9, 0x2e, 0x08, 0xc1,
|
||||
0xc9, 0x90, 0x4d, 0x1a, 0xd5, 0xf1, 0x1a, 0xef, 0xa6, 0x92, 0x1f, 0x86, 0x77, 0xe3, 0x41, 0x38,
|
||||
0x54, 0xb6, 0x37, 0xf1, 0xae, 0xb3, 0x69, 0x72, 0xc9, 0xcd, 0x33, 0xb3, 0xfb, 0xcd, 0x37, 0x3b,
|
||||
0xdf, 0x8c, 0xa1, 0xfd, 0x3e, 0x22, 0xbf, 0xef, 0x02, 0x46, 0x52, 0x3c, 0x4a, 0x52, 0xc2, 0x08,
|
||||
0x6a, 0xce, 0x70, 0x8c, 0xd3, 0x90, 0xe1, 0x89, 0x75, 0x1a, 0xdc, 0x86, 0x29, 0x9e, 0x94, 0x01,
|
||||
0xe7, 0xaf, 0x06, 0x9d, 0x0f, 0x29, 0x0e, 0x19, 0xfe, 0x46, 0xa2, 0xe5, 0x02, 0xfb, 0xf8, 0xcf,
|
||||
0x12, 0x53, 0x86, 0xfa, 0x70, 0x9c, 0x44, 0xcb, 0xd9, 0x3c, 0x36, 0x35, 0x5b, 0x1b, 0x36, 0x7d,
|
||||
0x6e, 0xa1, 0x01, 0x00, 0x8d, 0xc3, 0x84, 0xde, 0x12, 0xe6, 0xb9, 0x66, 0xa3, 0x88, 0x09, 0x9e,
|
||||
0x3c, 0x7e, 0x5f, 0x00, 0xdd, 0x64, 0x09, 0x36, 0xf5, 0x32, 0x5e, 0x79, 0x90, 0x05, 0x27, 0xa5,
|
||||
0x75, 0xf9, 0xc3, 0x34, 0x8a, 0xe8, 0xda, 0x46, 0x08, 0x8c, 0x39, 0x49, 0xa8, 0x79, 0x64, 0x6b,
|
||||
0x43, 0xdd, 0x2f, 0xbe, 0x9d, 0x31, 0x74, 0x65, 0x7a, 0x34, 0x21, 0x31, 0x15, 0x70, 0x3c, 0x97,
|
||||
0x33, 0x5c, 0xdb, 0xce, 0x14, 0xba, 0x9f, 0x30, 0x2b, 0x2f, 0x78, 0xf1, 0x94, 0xec, 0xaa, 0x49,
|
||||
0xc4, 0x6a, 0xc8, 0x58, 0x12, 0x5f, 0x5d, 0xe6, 0xeb, 0x7c, 0x86, 0x5e, 0x2d, 0x0f, 0x27, 0x27,
|
||||
0x3f, 0x82, 0xb6, 0xf1, 0x08, 0xab, 0x42, 0x1b, 0x42, 0xa1, 0x53, 0xe8, 0x7a, 0x74, 0x55, 0x64,
|
||||
0x38, 0xc9, 0x0e, 0x45, 0xfa, 0x05, 0xf4, 0x6a, 0x79, 0x38, 0xe9, 0x2e, 0x1c, 0xa5, 0xb9, 0xa3,
|
||||
0xc8, 0x73, 0xe2, 0x97, 0x86, 0xf3, 0x4f, 0x83, 0x5e, 0xd9, 0x80, 0x80, 0x37, 0xf9, 0x40, 0xc4,
|
||||
0xd0, 0x3b, 0x30, 0x58, 0x38, 0xa3, 0xa6, 0x61, 0xeb, 0xc3, 0xd6, 0xf8, 0x7c, 0xb4, 0x56, 0xec,
|
||||
0x48, 0x99, 0x7f, 0x74, 0x13, 0xce, 0xe8, 0x55, 0xcc, 0xd2, 0xcc, 0x2f, 0xee, 0x59, 0x6f, 0xa0,
|
||||
0xb9, 0x76, 0xa1, 0x36, 0xe8, 0x77, 0x38, 0xe3, 0xcc, 0xf2, 0xcf, 0xbc, 0xbc, 0xfb, 0x30, 0x5a,
|
||||
0x62, 0xce, 0xa9, 0x34, 0xde, 0x36, 0x2e, 0x34, 0xe7, 0x02, 0xfa, 0xf5, 0x0c, 0x55, 0x1f, 0x05,
|
||||
0xb1, 0x6b, 0x75, 0xb1, 0x3b, 0x5f, 0xa0, 0xe7, 0xe2, 0x08, 0xef, 0xff, 0x36, 0x3b, 0xa6, 0xc7,
|
||||
0xf9, 0x0e, 0xa8, 0x52, 0x94, 0xbb, 0x0b, 0xed, 0x1c, 0xda, 0x09, 0x4e, 0xe9, 0x9c, 0x32, 0x1c,
|
||||
0xf3, 0x4b, 0x05, 0xe6, 0xa9, 0xbf, 0xe1, 0x77, 0x5e, 0x41, 0x47, 0x42, 0xde, 0x63, 0x8c, 0x18,
|
||||
0xa0, 0xe0, 0x20, 0x64, 0xa4, 0xac, 0x7a, 0x2d, 0xeb, 0x25, 0x74, 0x02, 0x05, 0x51, 0x15, 0xbc,
|
||||
0xa6, 0x86, 0x1f, 0xff, 0x37, 0x00, 0xaa, 0x0d, 0x88, 0x5e, 0x82, 0xe1, 0xc5, 0x73, 0x86, 0xfa,
|
||||
0x82, 0xa4, 0x72, 0x07, 0xaf, 0xc8, 0x6a, 0x0b, 0xfe, 0xab, 0x45, 0xc2, 0x32, 0xf4, 0x13, 0x4c,
|
||||
0x71, 0xe9, 0x7c, 0x4c, 0xc9, 0x62, 0xd5, 0x61, 0x34, 0xd8, 0x10, 0xa6, 0xb4, 0x38, 0xad, 0x67,
|
||||
0x5b, 0xe3, 0xbc, 0x12, 0x1f, 0x9e, 0x48, 0x5b, 0x03, 0x89, 0x37, 0x54, 0x7b, 0xcb, 0xb2, 0xb7,
|
||||
0x1f, 0xa8, 0x30, 0xa5, 0xa1, 0x96, 0x30, 0x55, 0x6b, 0x45, 0xc2, 0x54, 0xef, 0x83, 0xaf, 0x70,
|
||||
0x26, 0x8f, 0x05, 0xb2, 0x77, 0xcd, 0xa4, 0xf5, 0xfc, 0x91, 0x13, 0x1c, 0xd6, 0x85, 0x33, 0x79,
|
||||
0x66, 0x24, 0x58, 0xe5, 0x38, 0x29, 0x3a, 0x74, 0x0d, 0x2d, 0x41, 0xce, 0xe8, 0xa9, 0xf2, 0x85,
|
||||
0x56, 0x9a, 0xb5, 0x06, 0xdb, 0xc2, 0x9c, 0xd3, 0x35, 0xb4, 0x82, 0x2d, 0x68, 0xc1, 0xe3, 0x68,
|
||||
0x0a, 0xa9, 0xfe, 0x3a, 0x2e, 0xfe, 0xac, 0xaf, 0x1f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa2, 0x65,
|
||||
0xf8, 0xa4, 0x86, 0x07, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type PutObjectRequest struct {
|
||||
Bucket string `protobuf:"bytes,1,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"`
|
||||
Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Bucket string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,3,opt,name=key" json:"key,omitempty"`
|
||||
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PutObjectRequest) Reset() { *m = PutObjectRequest{} }
|
||||
@@ -28,6 +29,13 @@ func (m *PutObjectRequest) String() string { return proto.CompactText
|
||||
func (*PutObjectRequest) ProtoMessage() {}
|
||||
func (*PutObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{0} }
|
||||
|
||||
func (m *PutObjectRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PutObjectRequest) GetBucket() string {
|
||||
if m != nil {
|
||||
return m.Bucket
|
||||
@@ -50,8 +58,9 @@ func (m *PutObjectRequest) GetBody() []byte {
|
||||
}
|
||||
|
||||
type GetObjectRequest struct {
|
||||
Bucket string `protobuf:"bytes,1,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Bucket string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,3,opt,name=key" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (m *GetObjectRequest) Reset() { *m = GetObjectRequest{} }
|
||||
@@ -59,6 +68,13 @@ func (m *GetObjectRequest) String() string { return proto.CompactText
|
||||
func (*GetObjectRequest) ProtoMessage() {}
|
||||
func (*GetObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{1} }
|
||||
|
||||
func (m *GetObjectRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *GetObjectRequest) GetBucket() string {
|
||||
if m != nil {
|
||||
return m.Bucket
|
||||
@@ -90,8 +106,9 @@ func (m *Bytes) GetData() []byte {
|
||||
}
|
||||
|
||||
type ListCommonPrefixesRequest struct {
|
||||
Bucket string `protobuf:"bytes,1,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Delimiter string `protobuf:"bytes,2,opt,name=delimiter" json:"delimiter,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Bucket string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Delimiter string `protobuf:"bytes,3,opt,name=delimiter" json:"delimiter,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ListCommonPrefixesRequest) Reset() { *m = ListCommonPrefixesRequest{} }
|
||||
@@ -99,6 +116,13 @@ func (m *ListCommonPrefixesRequest) String() string { return proto.Co
|
||||
func (*ListCommonPrefixesRequest) ProtoMessage() {}
|
||||
func (*ListCommonPrefixesRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{3} }
|
||||
|
||||
func (m *ListCommonPrefixesRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ListCommonPrefixesRequest) GetBucket() string {
|
||||
if m != nil {
|
||||
return m.Bucket
|
||||
@@ -130,8 +154,9 @@ func (m *ListCommonPrefixesResponse) GetPrefixes() []string {
|
||||
}
|
||||
|
||||
type ListObjectsRequest struct {
|
||||
Bucket string `protobuf:"bytes,1,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Prefix string `protobuf:"bytes,2,opt,name=prefix" json:"prefix,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Bucket string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Prefix string `protobuf:"bytes,3,opt,name=prefix" json:"prefix,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ListObjectsRequest) Reset() { *m = ListObjectsRequest{} }
|
||||
@@ -139,6 +164,13 @@ func (m *ListObjectsRequest) String() string { return proto.CompactTe
|
||||
func (*ListObjectsRequest) ProtoMessage() {}
|
||||
func (*ListObjectsRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{5} }
|
||||
|
||||
func (m *ListObjectsRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *ListObjectsRequest) GetBucket() string {
|
||||
if m != nil {
|
||||
return m.Bucket
|
||||
@@ -170,8 +202,9 @@ func (m *ListObjectsResponse) GetKeys() []string {
|
||||
}
|
||||
|
||||
type DeleteObjectRequest struct {
|
||||
Bucket string `protobuf:"bytes,1,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Bucket string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,3,opt,name=key" json:"key,omitempty"`
|
||||
}
|
||||
|
||||
func (m *DeleteObjectRequest) Reset() { *m = DeleteObjectRequest{} }
|
||||
@@ -179,6 +212,13 @@ func (m *DeleteObjectRequest) String() string { return proto.CompactT
|
||||
func (*DeleteObjectRequest) ProtoMessage() {}
|
||||
func (*DeleteObjectRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{7} }
|
||||
|
||||
func (m *DeleteObjectRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *DeleteObjectRequest) GetBucket() string {
|
||||
if m != nil {
|
||||
return m.Bucket
|
||||
@@ -194,9 +234,10 @@ func (m *DeleteObjectRequest) GetKey() string {
|
||||
}
|
||||
|
||||
type CreateSignedURLRequest struct {
|
||||
Bucket string `protobuf:"bytes,1,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,2,opt,name=key" json:"key,omitempty"`
|
||||
Ttl int64 `protobuf:"varint,3,opt,name=ttl" json:"ttl,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Bucket string `protobuf:"bytes,2,opt,name=bucket" json:"bucket,omitempty"`
|
||||
Key string `protobuf:"bytes,3,opt,name=key" json:"key,omitempty"`
|
||||
Ttl int64 `protobuf:"varint,4,opt,name=ttl" json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (m *CreateSignedURLRequest) Reset() { *m = CreateSignedURLRequest{} }
|
||||
@@ -204,6 +245,13 @@ func (m *CreateSignedURLRequest) String() string { return proto.Compa
|
||||
func (*CreateSignedURLRequest) ProtoMessage() {}
|
||||
func (*CreateSignedURLRequest) Descriptor() ([]byte, []int) { return fileDescriptor2, []int{8} }
|
||||
|
||||
func (m *CreateSignedURLRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *CreateSignedURLRequest) GetBucket() string {
|
||||
if m != nil {
|
||||
return m.Bucket
|
||||
@@ -589,33 +637,34 @@ var _ObjectStore_serviceDesc = grpc.ServiceDesc{
|
||||
func init() { proto.RegisterFile("ObjectStore.proto", fileDescriptor2) }
|
||||
|
||||
var fileDescriptor2 = []byte{
|
||||
// 444 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x94, 0xdf, 0x8b, 0xd3, 0x40,
|
||||
0x10, 0xc7, 0x89, 0xa9, 0xc5, 0xcc, 0x15, 0x8c, 0x73, 0x50, 0x6b, 0x4e, 0xa5, 0x2e, 0x0a, 0x15,
|
||||
0xa1, 0x1c, 0xfa, 0xe2, 0xc3, 0x81, 0xe2, 0x9d, 0x88, 0x50, 0xb0, 0xa6, 0x0a, 0xbe, 0xa6, 0x97,
|
||||
0xf1, 0x8c, 0xcd, 0x2f, 0x37, 0x13, 0x30, 0xff, 0x81, 0x7f, 0xb6, 0x64, 0xb3, 0xc6, 0x4d, 0x2e,
|
||||
0x67, 0xf1, 0xde, 0x66, 0x67, 0xe7, 0xfb, 0x9d, 0xc9, 0xce, 0x87, 0xc0, 0x9d, 0x0f, 0xdb, 0xef,
|
||||
0x74, 0xce, 0x1b, 0xce, 0x24, 0x2d, 0x73, 0x99, 0x71, 0x86, 0xce, 0x05, 0xa5, 0x24, 0x03, 0xa6,
|
||||
0xd0, 0x9b, 0x6c, 0xbe, 0x05, 0x92, 0xc2, 0xe6, 0x42, 0xac, 0xc1, 0x5d, 0x97, 0xdc, 0x08, 0x7c,
|
||||
0xfa, 0x51, 0x52, 0xc1, 0x38, 0x85, 0xf1, 0xb6, 0x3c, 0xdf, 0x11, 0xcf, 0xac, 0xb9, 0xb5, 0x70,
|
||||
0x7c, 0x7d, 0x42, 0x17, 0xec, 0x1d, 0x55, 0xb3, 0x1b, 0x2a, 0x59, 0x87, 0x88, 0x30, 0xda, 0x66,
|
||||
0x61, 0x35, 0xb3, 0xe7, 0xd6, 0x62, 0xe2, 0xab, 0x58, 0x9c, 0x80, 0xfb, 0x8e, 0xae, 0xeb, 0x28,
|
||||
0x8e, 0xe0, 0xe6, 0x9b, 0x8a, 0xa9, 0xa8, 0xad, 0xc3, 0x80, 0x03, 0x25, 0x98, 0xf8, 0x2a, 0x16,
|
||||
0x1f, 0xe1, 0xde, 0x2a, 0x2a, 0xf8, 0x34, 0x4b, 0x92, 0x2c, 0x5d, 0x4b, 0xfa, 0x1a, 0xfd, 0xa4,
|
||||
0x62, 0x5f, 0x8f, 0xfb, 0xe0, 0x84, 0x14, 0x47, 0x49, 0xc4, 0x24, 0x75, 0xa7, 0xbf, 0x09, 0xf1,
|
||||
0x12, 0xbc, 0x21, 0xcb, 0x22, 0xcf, 0xd2, 0x82, 0xd0, 0x83, 0x5b, 0xb9, 0xce, 0xcd, 0xac, 0xb9,
|
||||
0xbd, 0x70, 0xfc, 0xf6, 0x2c, 0xce, 0x00, 0x6b, 0x65, 0xf3, 0xa1, 0x7b, 0xa7, 0x98, 0xc2, 0xb8,
|
||||
0x51, 0xea, 0x11, 0xf4, 0x49, 0x3c, 0x85, 0xc3, 0x8e, 0x8b, 0x6e, 0x8c, 0x30, 0xda, 0x51, 0xf5,
|
||||
0xa7, 0xa9, 0x8a, 0xc5, 0x2b, 0x38, 0x3c, 0xa3, 0x98, 0x98, 0xae, 0xfb, 0xb6, 0x9f, 0x60, 0x7a,
|
||||
0x2a, 0x29, 0x60, 0xda, 0x44, 0x17, 0x29, 0x85, 0x9f, 0xfd, 0xd5, 0xff, 0x6f, 0xdc, 0x05, 0x9b,
|
||||
0x39, 0x56, 0x0b, 0xb7, 0xfd, 0x3a, 0x14, 0xcf, 0xe0, 0xee, 0x25, 0x57, 0xfd, 0x15, 0x2e, 0xd8,
|
||||
0xa5, 0x8c, 0xb5, 0x67, 0x1d, 0x3e, 0xff, 0x35, 0x82, 0x03, 0x83, 0x4e, 0x3c, 0x86, 0xd1, 0xfb,
|
||||
0x34, 0x62, 0x9c, 0x2e, 0x5b, 0x40, 0x97, 0x75, 0x42, 0x0f, 0xe6, 0xb9, 0x46, 0xfe, 0x6d, 0x92,
|
||||
0x73, 0x85, 0x27, 0xe0, 0xb4, 0xc0, 0xe2, 0x91, 0x71, 0xdd, 0xc7, 0xf8, 0xb2, 0x76, 0x61, 0xd5,
|
||||
0xea, 0x16, 0xce, 0x8e, 0xba, 0x8f, 0x6c, 0x47, 0xad, 0x88, 0x3c, 0xb6, 0x30, 0x68, 0x56, 0xde,
|
||||
0x85, 0x05, 0x1f, 0x1b, 0x95, 0x57, 0xe2, 0xe9, 0x3d, 0xd9, 0x53, 0xa5, 0x9f, 0x6c, 0x05, 0x07,
|
||||
0x06, 0x0f, 0xf8, 0xa0, 0xa7, 0xea, 0xd2, 0xe6, 0x3d, 0xbc, 0xea, 0x5a, 0xbb, 0xbd, 0x86, 0x89,
|
||||
0x89, 0x0c, 0x9a, 0xf5, 0x03, 0x2c, 0x0d, 0x3c, 0xf7, 0x17, 0xb8, 0xdd, 0xdb, 0x2e, 0x3e, 0x32,
|
||||
0x8a, 0x86, 0x79, 0xf2, 0xc4, 0xbf, 0x4a, 0x9a, 0xd9, 0xb6, 0x63, 0xf5, 0x03, 0x7a, 0xf1, 0x3b,
|
||||
0x00, 0x00, 0xff, 0xff, 0xf3, 0xb2, 0x85, 0x9a, 0xae, 0x04, 0x00, 0x00,
|
||||
// 459 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x51, 0x8b, 0xd3, 0x40,
|
||||
0x10, 0x26, 0x26, 0x1e, 0x66, 0xae, 0x60, 0x9c, 0x83, 0x1a, 0x73, 0x2a, 0x75, 0x51, 0xa8, 0x08,
|
||||
0xe5, 0xd0, 0x17, 0x1f, 0x7c, 0x10, 0x4f, 0x11, 0xa1, 0xe0, 0x91, 0x2a, 0xfa, 0xe0, 0x4b, 0x7a,
|
||||
0x19, 0x7b, 0xb1, 0x69, 0x12, 0x37, 0x13, 0x30, 0xff, 0xc0, 0x9f, 0x2d, 0xbb, 0x59, 0xeb, 0xa6,
|
||||
0xd7, 0xf3, 0xa0, 0xf4, 0x6d, 0xe6, 0xdb, 0xf9, 0x66, 0xbe, 0xec, 0x7e, 0x13, 0xb8, 0xf3, 0x71,
|
||||
0xfe, 0x83, 0xce, 0x79, 0xc6, 0xa5, 0xa4, 0x49, 0x25, 0x4b, 0x2e, 0xd1, 0x5f, 0x50, 0x41, 0x32,
|
||||
0x61, 0x4a, 0xa3, 0xc1, 0xec, 0x22, 0x91, 0x94, 0x76, 0x07, 0xe2, 0x02, 0x82, 0xb3, 0x86, 0x3b,
|
||||
0x42, 0x4c, 0x3f, 0x1b, 0xaa, 0x19, 0x87, 0x70, 0x50, 0xe5, 0xcd, 0x22, 0x2b, 0x42, 0x67, 0xe4,
|
||||
0x8c, 0xfd, 0xd8, 0x64, 0x0a, 0x9f, 0x37, 0xe7, 0x4b, 0xe2, 0xf0, 0x46, 0x87, 0x77, 0x19, 0x06,
|
||||
0xe0, 0x2e, 0xa9, 0x0d, 0x5d, 0x0d, 0xaa, 0x10, 0x11, 0xbc, 0x79, 0x99, 0xb6, 0xa1, 0x37, 0x72,
|
||||
0xc6, 0x83, 0x58, 0xc7, 0xe2, 0x13, 0x04, 0xef, 0x69, 0xdf, 0x93, 0xc4, 0x31, 0xdc, 0x7c, 0xd3,
|
||||
0x32, 0xd5, 0x6a, 0x64, 0x9a, 0x70, 0xa2, 0x1b, 0x0d, 0x62, 0x1d, 0x8b, 0x0c, 0xee, 0x4d, 0xb3,
|
||||
0x9a, 0x4f, 0xcb, 0xd5, 0xaa, 0x2c, 0xce, 0x24, 0x7d, 0xcf, 0x7e, 0x51, 0xbd, 0xeb, 0xec, 0xfb,
|
||||
0xe0, 0xa7, 0x94, 0x67, 0xab, 0x8c, 0x49, 0x1a, 0x05, 0xff, 0x00, 0xf1, 0x12, 0xa2, 0x6d, 0xa3,
|
||||
0xea, 0xaa, 0x2c, 0x6a, 0xc2, 0x08, 0x6e, 0x55, 0x06, 0x0b, 0x9d, 0x91, 0x3b, 0xf6, 0xe3, 0x75,
|
||||
0x2e, 0xbe, 0x01, 0x2a, 0x66, 0x77, 0x31, 0x3b, 0xab, 0x53, 0xf5, 0xba, 0xa3, 0x91, 0x66, 0x32,
|
||||
0xf1, 0x14, 0x8e, 0x7a, 0xdd, 0x8d, 0x20, 0x04, 0x6f, 0x49, 0xed, 0x5f, 0x31, 0x3a, 0x16, 0x5f,
|
||||
0xe0, 0xe8, 0x2d, 0xe5, 0xc4, 0xb4, 0xef, 0x37, 0xca, 0x61, 0x78, 0x2a, 0x29, 0x61, 0x9a, 0x65,
|
||||
0x8b, 0x82, 0xd2, 0xcf, 0xf1, 0x74, 0x7f, 0x4e, 0x0b, 0xc0, 0x65, 0xce, 0xb5, 0xd1, 0xdc, 0x58,
|
||||
0x85, 0xe2, 0x19, 0xdc, 0xbd, 0x34, 0xcd, 0x7c, 0x75, 0x00, 0x6e, 0x23, 0x73, 0x33, 0x4b, 0x85,
|
||||
0xcf, 0x7f, 0x7b, 0x70, 0x68, 0x6d, 0x0b, 0x9e, 0x80, 0xf7, 0xa1, 0xc8, 0x18, 0x87, 0x93, 0xf5,
|
||||
0xc2, 0x4c, 0x14, 0x60, 0x04, 0x47, 0x81, 0x85, 0xbf, 0x5b, 0x55, 0xdc, 0xe2, 0x2b, 0xf0, 0xd7,
|
||||
0x0b, 0x84, 0xc7, 0xd6, 0xf1, 0xe6, 0x5a, 0x5d, 0xe6, 0x8e, 0x1d, 0xc5, 0x5e, 0x2f, 0x45, 0x8f,
|
||||
0xbd, 0xb9, 0x2a, 0x3d, 0xb6, 0x76, 0xfc, 0x89, 0x83, 0x49, 0x67, 0x9d, 0xbe, 0xe9, 0xf0, 0xb1,
|
||||
0x55, 0x79, 0xa5, 0xfd, 0xa3, 0x27, 0xd7, 0x54, 0x99, 0x2b, 0x9b, 0xc2, 0xa1, 0xe5, 0x1f, 0x7c,
|
||||
0xb0, 0xc1, 0xea, 0xbb, 0x36, 0x7a, 0x78, 0xd5, 0xb1, 0xe9, 0xf6, 0x1a, 0x06, 0xb6, 0xc5, 0xd0,
|
||||
0xae, 0xdf, 0xe2, 0xbd, 0x2d, 0xd7, 0xfd, 0x15, 0x6e, 0x6f, 0xbc, 0x2e, 0x3e, 0xb2, 0x8a, 0xb6,
|
||||
0xfb, 0x2c, 0x12, 0xff, 0x2b, 0xe9, 0xb4, 0xcd, 0x0f, 0xf4, 0x0f, 0xf1, 0xc5, 0x9f, 0x00, 0x00,
|
||||
0x00, 0xff, 0xff, 0x59, 0xaf, 0x2a, 0xa0, 0x3e, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// source: PluginLister.proto
|
||||
|
||||
package generated
|
||||
|
||||
import proto "github.com/golang/protobuf/proto"
|
||||
import fmt "fmt"
|
||||
import math "math"
|
||||
|
||||
import (
|
||||
context "golang.org/x/net/context"
|
||||
grpc "google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type PluginIdentifier struct {
|
||||
Command string `protobuf:"bytes,1,opt,name=command" json:"command,omitempty"`
|
||||
Kind string `protobuf:"bytes,2,opt,name=kind" json:"kind,omitempty"`
|
||||
Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *PluginIdentifier) Reset() { *m = PluginIdentifier{} }
|
||||
func (m *PluginIdentifier) String() string { return proto.CompactTextString(m) }
|
||||
func (*PluginIdentifier) ProtoMessage() {}
|
||||
func (*PluginIdentifier) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
|
||||
|
||||
func (m *PluginIdentifier) GetCommand() string {
|
||||
if m != nil {
|
||||
return m.Command
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PluginIdentifier) GetKind() string {
|
||||
if m != nil {
|
||||
return m.Kind
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *PluginIdentifier) GetName() string {
|
||||
if m != nil {
|
||||
return m.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type ListPluginsResponse struct {
|
||||
Plugins []*PluginIdentifier `protobuf:"bytes,1,rep,name=plugins" json:"plugins,omitempty"`
|
||||
}
|
||||
|
||||
func (m *ListPluginsResponse) Reset() { *m = ListPluginsResponse{} }
|
||||
func (m *ListPluginsResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*ListPluginsResponse) ProtoMessage() {}
|
||||
func (*ListPluginsResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} }
|
||||
|
||||
func (m *ListPluginsResponse) GetPlugins() []*PluginIdentifier {
|
||||
if m != nil {
|
||||
return m.Plugins
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*PluginIdentifier)(nil), "generated.PluginIdentifier")
|
||||
proto.RegisterType((*ListPluginsResponse)(nil), "generated.ListPluginsResponse")
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ context.Context
|
||||
var _ grpc.ClientConn
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion4
|
||||
|
||||
// Client API for PluginLister service
|
||||
|
||||
type PluginListerClient interface {
|
||||
ListPlugins(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListPluginsResponse, error)
|
||||
}
|
||||
|
||||
type pluginListerClient struct {
|
||||
cc *grpc.ClientConn
|
||||
}
|
||||
|
||||
func NewPluginListerClient(cc *grpc.ClientConn) PluginListerClient {
|
||||
return &pluginListerClient{cc}
|
||||
}
|
||||
|
||||
func (c *pluginListerClient) ListPlugins(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ListPluginsResponse, error) {
|
||||
out := new(ListPluginsResponse)
|
||||
err := grpc.Invoke(ctx, "/generated.PluginLister/ListPlugins", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for PluginLister service
|
||||
|
||||
type PluginListerServer interface {
|
||||
ListPlugins(context.Context, *Empty) (*ListPluginsResponse, error)
|
||||
}
|
||||
|
||||
func RegisterPluginListerServer(s *grpc.Server, srv PluginListerServer) {
|
||||
s.RegisterService(&_PluginLister_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _PluginLister_ListPlugins_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(PluginListerServer).ListPlugins(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/generated.PluginLister/ListPlugins",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(PluginListerServer).ListPlugins(ctx, req.(*Empty))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _PluginLister_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "generated.PluginLister",
|
||||
HandlerType: (*PluginListerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ListPlugins",
|
||||
Handler: _PluginLister_ListPlugins_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "PluginLister.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("PluginLister.proto", fileDescriptor3) }
|
||||
|
||||
var fileDescriptor3 = []byte{
|
||||
// 201 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x0a, 0xc8, 0x29, 0x4d,
|
||||
0xcf, 0xcc, 0xf3, 0xc9, 0x2c, 0x2e, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2,
|
||||
0x4c, 0x4f, 0xcd, 0x4b, 0x2d, 0x4a, 0x2c, 0x49, 0x4d, 0x91, 0xe2, 0x09, 0xce, 0x48, 0x2c, 0x4a,
|
||||
0x4d, 0x81, 0x48, 0x28, 0x85, 0x70, 0x09, 0x40, 0x94, 0x7b, 0xa6, 0xa4, 0xe6, 0x95, 0x64, 0xa6,
|
||||
0x65, 0xa6, 0x16, 0x09, 0x49, 0x70, 0xb1, 0x27, 0xe7, 0xe7, 0xe6, 0x26, 0xe6, 0xa5, 0x48, 0x30,
|
||||
0x2a, 0x30, 0x6a, 0x70, 0x06, 0xc1, 0xb8, 0x42, 0x42, 0x5c, 0x2c, 0xd9, 0x99, 0x79, 0x29, 0x12,
|
||||
0x4c, 0x60, 0x61, 0x30, 0x1b, 0x24, 0x96, 0x97, 0x98, 0x9b, 0x2a, 0xc1, 0x0c, 0x11, 0x03, 0xb1,
|
||||
0x95, 0x7c, 0xb8, 0x84, 0x41, 0xd6, 0x43, 0x4c, 0x2e, 0x0e, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b,
|
||||
0x4e, 0x15, 0x32, 0xe5, 0x62, 0x2f, 0x80, 0x08, 0x49, 0x30, 0x2a, 0x30, 0x6b, 0x70, 0x1b, 0x49,
|
||||
0xeb, 0xc1, 0xdd, 0xa5, 0x87, 0xee, 0x8c, 0x20, 0x98, 0x5a, 0x23, 0x7f, 0x2e, 0x1e, 0x64, 0x2f,
|
||||
0x09, 0xd9, 0x73, 0x71, 0x23, 0x99, 0x2e, 0x24, 0x80, 0x64, 0x88, 0x6b, 0x6e, 0x41, 0x49, 0xa5,
|
||||
0x94, 0x1c, 0x92, 0x08, 0x16, 0x77, 0x24, 0xb1, 0x81, 0xfd, 0x6e, 0x0c, 0x08, 0x00, 0x00, 0xff,
|
||||
0xff, 0x0e, 0xb5, 0xe4, 0x0c, 0x2a, 0x01, 0x00, 0x00,
|
||||
}
|
||||
@@ -18,14 +18,22 @@ var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type RestoreExecuteRequest struct {
|
||||
Item []byte `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"`
|
||||
Restore []byte `protobuf:"bytes,2,opt,name=restore,proto3" json:"restore,omitempty"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Item []byte `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"`
|
||||
Restore []byte `protobuf:"bytes,3,opt,name=restore,proto3" json:"restore,omitempty"`
|
||||
}
|
||||
|
||||
func (m *RestoreExecuteRequest) Reset() { *m = RestoreExecuteRequest{} }
|
||||
func (m *RestoreExecuteRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*RestoreExecuteRequest) ProtoMessage() {}
|
||||
func (*RestoreExecuteRequest) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{0} }
|
||||
func (*RestoreExecuteRequest) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
|
||||
|
||||
func (m *RestoreExecuteRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *RestoreExecuteRequest) GetItem() []byte {
|
||||
if m != nil {
|
||||
@@ -49,7 +57,7 @@ type RestoreExecuteResponse struct {
|
||||
func (m *RestoreExecuteResponse) Reset() { *m = RestoreExecuteResponse{} }
|
||||
func (m *RestoreExecuteResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*RestoreExecuteResponse) ProtoMessage() {}
|
||||
func (*RestoreExecuteResponse) Descriptor() ([]byte, []int) { return fileDescriptor3, []int{1} }
|
||||
func (*RestoreExecuteResponse) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{1} }
|
||||
|
||||
func (m *RestoreExecuteResponse) GetItem() []byte {
|
||||
if m != nil {
|
||||
@@ -81,7 +89,7 @@ const _ = grpc.SupportPackageIsVersion4
|
||||
// Client API for RestoreItemAction service
|
||||
|
||||
type RestoreItemActionClient interface {
|
||||
AppliesTo(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*AppliesToResponse, error)
|
||||
AppliesTo(ctx context.Context, in *AppliesToRequest, opts ...grpc.CallOption) (*AppliesToResponse, error)
|
||||
Execute(ctx context.Context, in *RestoreExecuteRequest, opts ...grpc.CallOption) (*RestoreExecuteResponse, error)
|
||||
}
|
||||
|
||||
@@ -93,7 +101,7 @@ func NewRestoreItemActionClient(cc *grpc.ClientConn) RestoreItemActionClient {
|
||||
return &restoreItemActionClient{cc}
|
||||
}
|
||||
|
||||
func (c *restoreItemActionClient) AppliesTo(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*AppliesToResponse, error) {
|
||||
func (c *restoreItemActionClient) AppliesTo(ctx context.Context, in *AppliesToRequest, opts ...grpc.CallOption) (*AppliesToResponse, error) {
|
||||
out := new(AppliesToResponse)
|
||||
err := grpc.Invoke(ctx, "/generated.RestoreItemAction/AppliesTo", in, out, c.cc, opts...)
|
||||
if err != nil {
|
||||
@@ -114,7 +122,7 @@ func (c *restoreItemActionClient) Execute(ctx context.Context, in *RestoreExecut
|
||||
// Server API for RestoreItemAction service
|
||||
|
||||
type RestoreItemActionServer interface {
|
||||
AppliesTo(context.Context, *Empty) (*AppliesToResponse, error)
|
||||
AppliesTo(context.Context, *AppliesToRequest) (*AppliesToResponse, error)
|
||||
Execute(context.Context, *RestoreExecuteRequest) (*RestoreExecuteResponse, error)
|
||||
}
|
||||
|
||||
@@ -123,7 +131,7 @@ func RegisterRestoreItemActionServer(s *grpc.Server, srv RestoreItemActionServer
|
||||
}
|
||||
|
||||
func _RestoreItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(Empty)
|
||||
in := new(AppliesToRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -135,7 +143,7 @@ func _RestoreItemAction_AppliesTo_Handler(srv interface{}, ctx context.Context,
|
||||
FullMethod: "/generated.RestoreItemAction/AppliesTo",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(RestoreItemActionServer).AppliesTo(ctx, req.(*Empty))
|
||||
return srv.(RestoreItemActionServer).AppliesTo(ctx, req.(*AppliesToRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
@@ -175,22 +183,23 @@ var _RestoreItemAction_serviceDesc = grpc.ServiceDesc{
|
||||
Metadata: "RestoreItemAction.proto",
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("RestoreItemAction.proto", fileDescriptor3) }
|
||||
func init() { proto.RegisterFile("RestoreItemAction.proto", fileDescriptor4) }
|
||||
|
||||
var fileDescriptor3 = []byte{
|
||||
// 210 bytes of a gzipped FileDescriptorProto
|
||||
var fileDescriptor4 = []byte{
|
||||
// 230 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x0f, 0x4a, 0x2d, 0x2e,
|
||||
0xc9, 0x2f, 0x4a, 0xf5, 0x2c, 0x49, 0xcd, 0x75, 0x4c, 0x2e, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28,
|
||||
0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4c, 0x4f, 0xcd, 0x4b, 0x2d, 0x4a, 0x2c, 0x49, 0x4d, 0x91, 0xe2,
|
||||
0x09, 0xce, 0x48, 0x2c, 0x4a, 0x4d, 0x81, 0x48, 0x28, 0xb9, 0x72, 0x89, 0x42, 0xf5, 0xb8, 0x56,
|
||||
0xa4, 0x26, 0x97, 0x96, 0xa4, 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x09, 0x71, 0xb1,
|
||||
0x64, 0x96, 0xa4, 0xe6, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0xf0, 0x04, 0x81, 0xd9, 0x42, 0x12, 0x5c,
|
||||
0xec, 0x45, 0x10, 0xc5, 0x12, 0x4c, 0x60, 0x61, 0x18, 0x57, 0xc9, 0x8d, 0x4b, 0x0c, 0xdd, 0x98,
|
||||
0xe2, 0x82, 0xfc, 0xbc, 0xe2, 0x54, 0x5c, 0xe6, 0x94, 0x27, 0x16, 0xe5, 0x65, 0xe6, 0xa5, 0x83,
|
||||
0xcd, 0xe1, 0x0c, 0x82, 0x71, 0x8d, 0x16, 0x30, 0x72, 0x09, 0x62, 0xf8, 0x41, 0xc8, 0x9a, 0x8b,
|
||||
0xd3, 0xb1, 0xa0, 0x20, 0x27, 0x33, 0xb5, 0x38, 0x24, 0x5f, 0x48, 0x40, 0x0f, 0xee, 0x17, 0x3d,
|
||||
0xd7, 0xdc, 0x82, 0x92, 0x4a, 0x29, 0x19, 0x24, 0x11, 0xb8, 0x3a, 0xb8, 0x03, 0xfc, 0xb8, 0xd8,
|
||||
0xa1, 0x6e, 0x12, 0x52, 0x40, 0x52, 0x88, 0xd5, 0xd7, 0x52, 0x8a, 0x78, 0x54, 0x40, 0xcc, 0x4b,
|
||||
0x62, 0x03, 0x07, 0x9c, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xb9, 0x08, 0x09, 0x74, 0x6c, 0x01,
|
||||
0x00, 0x00,
|
||||
0x09, 0xce, 0x48, 0x2c, 0x4a, 0x4d, 0x81, 0x48, 0x28, 0xc5, 0x72, 0x89, 0x42, 0xf5, 0xb8, 0x56,
|
||||
0xa4, 0x26, 0x97, 0x96, 0xa4, 0x06, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0x89, 0x71, 0xb1,
|
||||
0x15, 0xe4, 0x94, 0xa6, 0x67, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x41, 0x79, 0x42,
|
||||
0x42, 0x5c, 0x2c, 0x99, 0x25, 0xa9, 0xb9, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0x3c, 0x41, 0x60, 0xb6,
|
||||
0x90, 0x04, 0x17, 0x7b, 0x11, 0xc4, 0x10, 0x09, 0x66, 0xb0, 0x30, 0x8c, 0xab, 0xe4, 0xc6, 0x25,
|
||||
0x86, 0x6e, 0x7c, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0x2a, 0xdc, 0x1c, 0x46, 0x54, 0x73, 0xca, 0x13,
|
||||
0x8b, 0xf2, 0x32, 0xf3, 0xd2, 0xc1, 0xc6, 0x73, 0x06, 0xc1, 0xb8, 0x46, 0xab, 0x19, 0xb9, 0x04,
|
||||
0x31, 0xfc, 0x26, 0xe4, 0xc6, 0xc5, 0xe9, 0x58, 0x50, 0x90, 0x93, 0x99, 0x5a, 0x1c, 0x92, 0x2f,
|
||||
0x24, 0xad, 0x07, 0xf7, 0xa3, 0x1e, 0x5c, 0x14, 0xea, 0x1b, 0x29, 0x19, 0xec, 0x92, 0x50, 0xb7,
|
||||
0xf8, 0x71, 0xb1, 0x43, 0x9d, 0x27, 0xa4, 0x80, 0xa4, 0x10, 0x6b, 0xc0, 0x48, 0x29, 0xe2, 0x51,
|
||||
0x01, 0x31, 0x2f, 0x89, 0x0d, 0x1c, 0xb6, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaa, 0xe5,
|
||||
0x97, 0xa4, 0x8f, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -18,16 +18,24 @@ type Empty struct {
|
||||
func (m *Empty) Reset() { *m = Empty{} }
|
||||
func (m *Empty) String() string { return proto.CompactTextString(m) }
|
||||
func (*Empty) ProtoMessage() {}
|
||||
func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{0} }
|
||||
func (*Empty) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{0} }
|
||||
|
||||
type InitRequest struct {
|
||||
Config map[string]string `protobuf:"bytes,1,rep,name=config" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
Config map[string]string `protobuf:"bytes,2,rep,name=config" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
}
|
||||
|
||||
func (m *InitRequest) Reset() { *m = InitRequest{} }
|
||||
func (m *InitRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*InitRequest) ProtoMessage() {}
|
||||
func (*InitRequest) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{1} }
|
||||
func (*InitRequest) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{1} }
|
||||
|
||||
func (m *InitRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *InitRequest) GetConfig() map[string]string {
|
||||
if m != nil {
|
||||
@@ -36,6 +44,22 @@ func (m *InitRequest) GetConfig() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
type AppliesToRequest struct {
|
||||
Plugin string `protobuf:"bytes,1,opt,name=plugin" json:"plugin,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AppliesToRequest) Reset() { *m = AppliesToRequest{} }
|
||||
func (m *AppliesToRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*AppliesToRequest) ProtoMessage() {}
|
||||
func (*AppliesToRequest) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{2} }
|
||||
|
||||
func (m *AppliesToRequest) GetPlugin() string {
|
||||
if m != nil {
|
||||
return m.Plugin
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type AppliesToResponse struct {
|
||||
IncludedNamespaces []string `protobuf:"bytes,1,rep,name=includedNamespaces" json:"includedNamespaces,omitempty"`
|
||||
ExcludedNamespaces []string `protobuf:"bytes,2,rep,name=excludedNamespaces" json:"excludedNamespaces,omitempty"`
|
||||
@@ -47,7 +71,7 @@ type AppliesToResponse struct {
|
||||
func (m *AppliesToResponse) Reset() { *m = AppliesToResponse{} }
|
||||
func (m *AppliesToResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*AppliesToResponse) ProtoMessage() {}
|
||||
func (*AppliesToResponse) Descriptor() ([]byte, []int) { return fileDescriptor4, []int{2} }
|
||||
func (*AppliesToResponse) Descriptor() ([]byte, []int) { return fileDescriptor5, []int{3} }
|
||||
|
||||
func (m *AppliesToResponse) GetIncludedNamespaces() []string {
|
||||
if m != nil {
|
||||
@@ -87,28 +111,30 @@ func (m *AppliesToResponse) GetSelector() string {
|
||||
func init() {
|
||||
proto.RegisterType((*Empty)(nil), "generated.Empty")
|
||||
proto.RegisterType((*InitRequest)(nil), "generated.InitRequest")
|
||||
proto.RegisterType((*AppliesToRequest)(nil), "generated.AppliesToRequest")
|
||||
proto.RegisterType((*AppliesToResponse)(nil), "generated.AppliesToResponse")
|
||||
}
|
||||
|
||||
func init() { proto.RegisterFile("Shared.proto", fileDescriptor4) }
|
||||
func init() { proto.RegisterFile("Shared.proto", fileDescriptor5) }
|
||||
|
||||
var fileDescriptor4 = []byte{
|
||||
// 257 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0xd1, 0xb1, 0x4e, 0xc3, 0x30,
|
||||
0x10, 0x06, 0x60, 0xb9, 0x21, 0x85, 0x5c, 0x18, 0xa8, 0xc5, 0x10, 0x75, 0xaa, 0x32, 0x75, 0x40,
|
||||
0x19, 0x60, 0x81, 0x6e, 0x08, 0x75, 0x60, 0x61, 0x30, 0xbc, 0x80, 0x49, 0x7e, 0x4a, 0x44, 0x6a,
|
||||
0x1b, 0xdb, 0x41, 0x64, 0xe7, 0x6d, 0x79, 0x09, 0x14, 0x87, 0x96, 0x4a, 0x61, 0xf3, 0xdd, 0xff,
|
||||
0xdd, 0xc9, 0x96, 0xe9, 0xf4, 0xf1, 0x55, 0x5a, 0x54, 0x85, 0xb1, 0xda, 0x6b, 0x9e, 0x6c, 0xa0,
|
||||
0x60, 0xa5, 0x47, 0x95, 0x1f, 0x53, 0xbc, 0xde, 0x1a, 0xdf, 0xe5, 0x5f, 0x8c, 0xd2, 0x7b, 0x55,
|
||||
0x7b, 0x81, 0xf7, 0x16, 0xce, 0xf3, 0x15, 0x4d, 0x4b, 0xad, 0x5e, 0xea, 0x4d, 0xc6, 0x16, 0xd1,
|
||||
0x32, 0xbd, 0xcc, 0x8b, 0xfd, 0x50, 0x71, 0xe0, 0x8a, 0xbb, 0x80, 0xd6, 0xca, 0xdb, 0x4e, 0xfc,
|
||||
0x4e, 0xcc, 0x6f, 0x28, 0x3d, 0x68, 0xf3, 0x33, 0x8a, 0xde, 0xd0, 0x65, 0x6c, 0xc1, 0x96, 0x89,
|
||||
0xe8, 0x8f, 0xfc, 0x9c, 0xe2, 0x0f, 0xd9, 0xb4, 0xc8, 0x26, 0xa1, 0x37, 0x14, 0xab, 0xc9, 0x35,
|
||||
0xcb, 0xbf, 0x19, 0xcd, 0x6e, 0x8d, 0x69, 0x6a, 0xb8, 0x27, 0x2d, 0xe0, 0x8c, 0x56, 0x0e, 0xbc,
|
||||
0x20, 0x5e, 0xab, 0xb2, 0x69, 0x2b, 0x54, 0x0f, 0x72, 0x0b, 0x67, 0x64, 0x09, 0x17, 0x2e, 0x96,
|
||||
0x88, 0x7f, 0x92, 0xde, 0xe3, 0x73, 0xe4, 0x27, 0x83, 0x1f, 0x27, 0xfc, 0x82, 0x66, 0xbb, 0x2d,
|
||||
0x02, 0x4e, 0xb7, 0xb6, 0xe7, 0x51, 0xe0, 0xe3, 0xa0, 0xd7, 0xbb, 0x1d, 0x7f, 0xfa, 0x68, 0xd0,
|
||||
0xa3, 0x80, 0xcf, 0xe9, 0xc4, 0xa1, 0x41, 0xe9, 0xb5, 0xcd, 0xe2, 0xf0, 0xdc, 0x7d, 0xfd, 0x3c,
|
||||
0x0d, 0xff, 0x71, 0xf5, 0x13, 0x00, 0x00, 0xff, 0xff, 0x19, 0xd7, 0x88, 0x92, 0x9f, 0x01, 0x00,
|
||||
0x00,
|
||||
var fileDescriptor5 = []byte{
|
||||
// 278 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x91, 0xcd, 0x4e, 0x84, 0x30,
|
||||
0x14, 0x85, 0x53, 0x10, 0x94, 0x8b, 0x8b, 0x99, 0xc6, 0x18, 0x32, 0x2b, 0xc2, 0x8a, 0x18, 0xc3,
|
||||
0x42, 0x37, 0x3a, 0x3b, 0x63, 0x66, 0xe1, 0xc6, 0x05, 0xfa, 0x02, 0x08, 0x57, 0x24, 0x32, 0x6d,
|
||||
0xed, 0x8f, 0x91, 0x77, 0xf1, 0x0d, 0x7d, 0x09, 0x43, 0x61, 0x46, 0x12, 0x4c, 0x66, 0xd7, 0x73,
|
||||
0xcf, 0x77, 0xbf, 0xb4, 0x29, 0x9c, 0x3e, 0xbd, 0x15, 0x12, 0xab, 0x4c, 0x48, 0xae, 0x39, 0x0d,
|
||||
0x6a, 0x64, 0x28, 0x0b, 0x8d, 0x55, 0x72, 0x0c, 0xde, 0x66, 0x2b, 0x74, 0x97, 0x7c, 0x13, 0x08,
|
||||
0x1f, 0x58, 0xa3, 0x73, 0xfc, 0x30, 0xa8, 0x34, 0x3d, 0x07, 0x5f, 0xb4, 0xa6, 0x6e, 0x58, 0x44,
|
||||
0x62, 0x92, 0x06, 0xf9, 0x98, 0xe8, 0x1a, 0xfc, 0x92, 0xb3, 0xd7, 0xa6, 0x8e, 0x9c, 0xd8, 0x4d,
|
||||
0xc3, 0xab, 0x24, 0xdb, 0xcb, 0xb2, 0xc9, 0x7e, 0x76, 0x6f, 0xa1, 0x0d, 0xd3, 0xb2, 0xcb, 0xc7,
|
||||
0x8d, 0xd5, 0x2d, 0x84, 0x93, 0x31, 0x5d, 0x80, 0xfb, 0x8e, 0xdd, 0xe8, 0xef, 0x8f, 0xf4, 0x0c,
|
||||
0xbc, 0xcf, 0xa2, 0x35, 0x18, 0x39, 0x76, 0x36, 0x84, 0xb5, 0x73, 0x43, 0x92, 0x0b, 0x58, 0xdc,
|
||||
0x09, 0xd1, 0x36, 0xa8, 0x9e, 0xf9, 0x81, 0x2b, 0x26, 0x3f, 0x04, 0x96, 0x13, 0x58, 0x09, 0xce,
|
||||
0x14, 0xd2, 0x0c, 0x68, 0xc3, 0xca, 0xd6, 0x54, 0x58, 0x3d, 0x16, 0x5b, 0x54, 0xa2, 0x28, 0x51,
|
||||
0x45, 0x24, 0x76, 0xd3, 0x20, 0xff, 0xa7, 0xe9, 0x79, 0xfc, 0x9a, 0xf1, 0xce, 0xc0, 0xcf, 0x1b,
|
||||
0x7a, 0x09, 0xcb, 0x9d, 0x25, 0x47, 0xc5, 0x8d, 0xec, 0x71, 0xd7, 0xe2, 0xf3, 0xa2, 0xa7, 0x77,
|
||||
0x8e, 0x3f, 0xfa, 0x68, 0xa0, 0x67, 0x05, 0x5d, 0xc1, 0x89, 0xc2, 0x16, 0x4b, 0xcd, 0x65, 0xe4,
|
||||
0xd9, 0xb7, 0xee, 0xf3, 0x8b, 0x6f, 0xff, 0xf4, 0xfa, 0x37, 0x00, 0x00, 0xff, 0xff, 0x73, 0x1b,
|
||||
0xfe, 0x37, 0xe3, 0x01, 0x00, 0x00,
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import plugin "github.com/hashicorp/go-plugin"
|
||||
type Interface interface {
|
||||
plugin.Plugin
|
||||
|
||||
// Kind returns the PluginKind for the plugin.
|
||||
Kind() PluginKind
|
||||
// names returns a list of all the registered implementations for this plugin (such as "pod" and "pvc" for
|
||||
// BackupItemAction).
|
||||
names() []string
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewLogger(t *testing.T) {
|
||||
l := NewLogger().(*logrus.Logger)
|
||||
|
||||
expectedFormatter := &logrus.JSONFormatter{
|
||||
FieldMap: logrus.FieldMap{
|
||||
logrus.FieldKeyMsg: "@message",
|
||||
},
|
||||
DisableTimestamp: true,
|
||||
}
|
||||
assert.Equal(t, expectedFormatter, l.Formatter)
|
||||
|
||||
expectedHooks := []logrus.Hook{
|
||||
(&logging.LogLocationHook{}).WithLoggerName("plugin"),
|
||||
&logging.HcLogLevelHook{},
|
||||
}
|
||||
|
||||
for _, level := range logrus.AllLevels {
|
||||
assert.Equal(t, expectedHooks, l.Hooks[level])
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
|
||||
+163
-375
@@ -17,14 +17,8 @@ limitations under the License.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
@@ -32,391 +26,185 @@ import (
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
)
|
||||
|
||||
// PluginKind is a type alias for a string that describes
|
||||
// the kind of an Ark-supported plugin.
|
||||
type PluginKind string
|
||||
|
||||
func (k PluginKind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
func baseConfig() *plugin.ClientConfig {
|
||||
return &plugin.ClientConfig{
|
||||
HandshakeConfig: Handshake,
|
||||
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
|
||||
Managed: true,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// PluginKindObjectStore is the Kind string for
|
||||
// an Object Store plugin.
|
||||
PluginKindObjectStore PluginKind = "objectstore"
|
||||
|
||||
// PluginKindBlockStore is the Kind string for
|
||||
// a Block Store plugin.
|
||||
PluginKindBlockStore PluginKind = "blockstore"
|
||||
|
||||
// PluginKindCloudProvider is the Kind string for
|
||||
// a CloudProvider plugin (i.e. an Object & Block
|
||||
// store).
|
||||
//
|
||||
// NOTE that it is highly likely that in subsequent
|
||||
// versions of Ark this kind of plugin will be replaced
|
||||
// with a different mechanism for providing multiple
|
||||
// plugin impls within a single binary. This should
|
||||
// probably not be used.
|
||||
PluginKindCloudProvider PluginKind = "cloudprovider"
|
||||
|
||||
// PluginKindBackupItemAction is the Kind string for
|
||||
// a Backup ItemAction plugin.
|
||||
PluginKindBackupItemAction PluginKind = "backupitemaction"
|
||||
|
||||
// PluginKindRestoreItemAction is the Kind string for
|
||||
// a Restore ItemAction plugin.
|
||||
PluginKindRestoreItemAction PluginKind = "restoreitemaction"
|
||||
)
|
||||
|
||||
var AllPluginKinds = []PluginKind{
|
||||
PluginKindObjectStore,
|
||||
PluginKindBlockStore,
|
||||
PluginKindCloudProvider,
|
||||
PluginKindBackupItemAction,
|
||||
PluginKindRestoreItemAction,
|
||||
}
|
||||
|
||||
type pluginInfo struct {
|
||||
kinds []PluginKind
|
||||
name string
|
||||
commandName string
|
||||
commandArgs []string
|
||||
}
|
||||
|
||||
// Manager exposes functions for getting implementations of the pluggable
|
||||
// Ark interfaces.
|
||||
// Manager manages the lifecycles of plugins.
|
||||
type Manager interface {
|
||||
// GetObjectStore returns the plugin implementation of the
|
||||
// cloudprovider.ObjectStore interface with the specified name.
|
||||
// GetObjectStore returns the ObjectStore plugin for name.
|
||||
GetObjectStore(name string) (cloudprovider.ObjectStore, error)
|
||||
|
||||
// GetBlockStore returns the plugin implementation of the
|
||||
// cloudprovider.BlockStore interface with the specified name.
|
||||
// GetBlockStore returns the BlockStore plugin for name.
|
||||
GetBlockStore(name string) (cloudprovider.BlockStore, error)
|
||||
|
||||
// GetBackupItemActions returns all backup.ItemAction plugins.
|
||||
// These plugin instances should ONLY be used for a single backup
|
||||
// (mainly because each one outputs to a per-backup log),
|
||||
// and should be terminated upon completion of the backup with
|
||||
// CloseBackupItemActions().
|
||||
GetBackupItemActions(backupName string) ([]backup.ItemAction, error)
|
||||
// GetBackupItemActions returns all backup item action plugins.
|
||||
GetBackupItemActions() ([]backup.ItemAction, error)
|
||||
|
||||
// CloseBackupItemActions terminates the plugin sub-processes that
|
||||
// are hosting BackupItemAction plugins for the given backup name.
|
||||
CloseBackupItemActions(backupName string) error
|
||||
// GetBackupItemAction returns the backup item action plugin for name.
|
||||
GetBackupItemAction(name string) (backup.ItemAction, error)
|
||||
|
||||
// GetRestoreItemActions returns all restore.ItemAction plugins.
|
||||
// These plugin instances should ONLY be used for a single restore
|
||||
// (mainly because each one outputs to a per-restore log),
|
||||
// and should be terminated upon completion of the restore with
|
||||
// CloseRestoreItemActions().
|
||||
GetRestoreItemActions(restoreName string) ([]restore.ItemAction, error)
|
||||
// GetRestoreItemActions returns all restore item action plugins.
|
||||
GetRestoreItemActions() ([]restore.ItemAction, error)
|
||||
|
||||
// CloseRestoreItemActions terminates the plugin sub-processes that
|
||||
// are hosting RestoreItemAction plugins for the given restore name.
|
||||
CloseRestoreItemActions(restoreName string) error
|
||||
// GetRestoreItemAction returns the restore item action plugin for name.
|
||||
GetRestoreItemAction(name string) (restore.ItemAction, error)
|
||||
|
||||
// CleanupClients kills all plugin subprocesses.
|
||||
// CleanupClients terminates all of the Manager's running plugin processes.
|
||||
CleanupClients()
|
||||
}
|
||||
|
||||
// manager implements Manager.
|
||||
type manager struct {
|
||||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
pluginRegistry *registry
|
||||
clientStore *clientStore
|
||||
pluginDir string
|
||||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
registry Registry
|
||||
|
||||
restartableProcessFactory RestartableProcessFactory
|
||||
|
||||
// lock guards restartableProcesses
|
||||
lock sync.Mutex
|
||||
restartableProcesses map[string]RestartableProcess
|
||||
}
|
||||
|
||||
// NewManager constructs a manager for getting plugin implementations.
|
||||
func NewManager(logger logrus.FieldLogger, level logrus.Level, pluginDir string) (Manager, error) {
|
||||
m := &manager{
|
||||
logger: logger,
|
||||
logLevel: level,
|
||||
pluginRegistry: newRegistry(),
|
||||
clientStore: newClientStore(),
|
||||
pluginDir: pluginDir,
|
||||
// NewManager constructs a manager for getting plugins.
|
||||
func NewManager(logger logrus.FieldLogger, level logrus.Level, registry Registry) Manager {
|
||||
return &manager{
|
||||
logger: logger,
|
||||
logLevel: level,
|
||||
registry: registry,
|
||||
|
||||
restartableProcessFactory: newRestartableProcessFactory(),
|
||||
|
||||
restartableProcesses: make(map[string]RestartableProcess),
|
||||
}
|
||||
|
||||
if err := m.registerPlugins(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func pluginForKind(kind PluginKind) plugin.Plugin {
|
||||
switch kind {
|
||||
case PluginKindObjectStore:
|
||||
return &ObjectStorePlugin{}
|
||||
case PluginKindBlockStore:
|
||||
return &BlockStorePlugin{}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func getPluginInstance(client *plugin.Client, kind PluginKind) (interface{}, error) {
|
||||
protocolClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
plugin, err := protocolClient.Dispense(string(kind))
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func (m *manager) registerPlugins() error {
|
||||
arkCommand := os.Args[0]
|
||||
|
||||
// first, register internal plugins
|
||||
for _, provider := range []string{"aws", "gcp", "azure"} {
|
||||
m.pluginRegistry.register(provider, arkCommand, []string{"run-plugin", "cloudprovider", provider}, PluginKindObjectStore, PluginKindBlockStore)
|
||||
}
|
||||
m.pluginRegistry.register("pv", arkCommand, []string{"run-plugin", string(PluginKindBackupItemAction), "pv"}, PluginKindBackupItemAction)
|
||||
m.pluginRegistry.register("backup-pod", arkCommand, []string{"run-plugin", string(PluginKindBackupItemAction), "pod"}, PluginKindBackupItemAction)
|
||||
m.pluginRegistry.register("serviceaccount", arkCommand, []string{"run-plugin", string(PluginKindBackupItemAction), "serviceaccount"}, PluginKindBackupItemAction)
|
||||
|
||||
m.pluginRegistry.register("job", arkCommand, []string{"run-plugin", string(PluginKindRestoreItemAction), "job"}, PluginKindRestoreItemAction)
|
||||
m.pluginRegistry.register("restore-pod", arkCommand, []string{"run-plugin", string(PluginKindRestoreItemAction), "pod"}, PluginKindRestoreItemAction)
|
||||
m.pluginRegistry.register("svc", arkCommand, []string{"run-plugin", string(PluginKindRestoreItemAction), "svc"}, PluginKindRestoreItemAction)
|
||||
m.pluginRegistry.register("restic", arkCommand, []string{"run-plugin", string(PluginKindRestoreItemAction), "restic"}, PluginKindRestoreItemAction)
|
||||
|
||||
// second, register external plugins (these will override internal plugins, if applicable)
|
||||
if _, err := os.Stat(m.pluginDir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(m.pluginDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
name, kind, err := parse(file.Name())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if kind == PluginKindCloudProvider {
|
||||
m.pluginRegistry.register(name, filepath.Join(m.pluginDir, file.Name()), nil, PluginKindObjectStore, PluginKindBlockStore)
|
||||
} else {
|
||||
m.pluginRegistry.register(name, filepath.Join(m.pluginDir, file.Name()), nil, kind)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse(filename string) (string, PluginKind, error) {
|
||||
for _, kind := range AllPluginKinds {
|
||||
if prefix := fmt.Sprintf("ark-%s-", kind); strings.Index(filename, prefix) == 0 {
|
||||
return strings.Replace(filename, prefix, "", -1), kind, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", "", errors.New("invalid file name")
|
||||
}
|
||||
|
||||
// GetObjectStore returns the plugin implementation of the cloudprovider.ObjectStore
|
||||
// interface with the specified name.
|
||||
func (m *manager) GetObjectStore(name string) (cloudprovider.ObjectStore, error) {
|
||||
pluginObj, err := m.getCloudProviderPlugin(name, PluginKindObjectStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objStore, ok := pluginObj.(cloudprovider.ObjectStore)
|
||||
if !ok {
|
||||
return nil, errors.New("could not convert gRPC client to cloudprovider.ObjectStore")
|
||||
}
|
||||
|
||||
return objStore, nil
|
||||
}
|
||||
|
||||
// GetBlockStore returns the plugin implementation of the cloudprovider.BlockStore
|
||||
// interface with the specified name.
|
||||
func (m *manager) GetBlockStore(name string) (cloudprovider.BlockStore, error) {
|
||||
pluginObj, err := m.getCloudProviderPlugin(name, PluginKindBlockStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockStore, ok := pluginObj.(cloudprovider.BlockStore)
|
||||
if !ok {
|
||||
return nil, errors.New("could not convert gRPC client to cloudprovider.BlockStore")
|
||||
}
|
||||
|
||||
return blockStore, nil
|
||||
}
|
||||
|
||||
func (m *manager) getCloudProviderPlugin(name string, kind PluginKind) (interface{}, error) {
|
||||
client, err := m.clientStore.get(kind, name, "")
|
||||
if err != nil {
|
||||
pluginInfo, err := m.pluginRegistry.get(kind, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// build a plugin client that can dispense all of the PluginKinds it's registered for
|
||||
clientBuilder := newClientBuilder(baseConfig()).
|
||||
withCommand(pluginInfo.commandName, pluginInfo.commandArgs...).
|
||||
withLogger(&logrusAdapter{impl: m.logger, level: m.logLevel})
|
||||
|
||||
for _, kind := range pluginInfo.kinds {
|
||||
clientBuilder.withPlugin(kind, pluginForKind(kind))
|
||||
}
|
||||
|
||||
client = clientBuilder.client()
|
||||
|
||||
// register the plugin client for the appropriate kinds
|
||||
for _, kind := range pluginInfo.kinds {
|
||||
m.clientStore.add(client, kind, name, "")
|
||||
}
|
||||
}
|
||||
|
||||
pluginObj, err := getPluginInstance(client, kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pluginObj, nil
|
||||
}
|
||||
|
||||
// GetBackupActions returns all backup.BackupAction plugins.
|
||||
// These plugin instances should ONLY be used for a single backup
|
||||
// (mainly because each one outputs to a per-backup log),
|
||||
// and should be terminated upon completion of the backup with
|
||||
// CloseBackupActions().
|
||||
func (m *manager) GetBackupItemActions(backupName string) ([]backup.ItemAction, error) {
|
||||
clients, err := m.clientStore.list(PluginKindBackupItemAction, backupName)
|
||||
if err != nil {
|
||||
pluginInfo, err := m.pluginRegistry.list(PluginKindBackupItemAction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create clients for each
|
||||
for _, plugin := range pluginInfo {
|
||||
logger := &logrusAdapter{impl: m.logger, level: m.logLevel}
|
||||
client := newClientBuilder(baseConfig()).
|
||||
withCommand(plugin.commandName, plugin.commandArgs...).
|
||||
withPlugin(PluginKindBackupItemAction, &BackupItemActionPlugin{log: logger}).
|
||||
withLogger(logger).
|
||||
client()
|
||||
|
||||
m.clientStore.add(client, PluginKindBackupItemAction, plugin.name, backupName)
|
||||
|
||||
clients = append(clients, client)
|
||||
}
|
||||
}
|
||||
|
||||
var backupActions []backup.ItemAction
|
||||
for _, client := range clients {
|
||||
plugin, err := getPluginInstance(client, PluginKindBackupItemAction)
|
||||
if err != nil {
|
||||
m.CloseBackupItemActions(backupName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backupAction, ok := plugin.(backup.ItemAction)
|
||||
if !ok {
|
||||
m.CloseBackupItemActions(backupName)
|
||||
return nil, errors.New("could not convert gRPC client to backup.ItemAction")
|
||||
}
|
||||
|
||||
backupActions = append(backupActions, backupAction)
|
||||
}
|
||||
|
||||
return backupActions, nil
|
||||
}
|
||||
|
||||
// CloseBackupItemActions terminates the plugin sub-processes that
|
||||
// are hosting BackupItemAction plugins for the given backup name.
|
||||
func (m *manager) CloseBackupItemActions(backupName string) error {
|
||||
return closeAll(m.clientStore, PluginKindBackupItemAction, backupName)
|
||||
}
|
||||
|
||||
func (m *manager) GetRestoreItemActions(restoreName string) ([]restore.ItemAction, error) {
|
||||
clients, err := m.clientStore.list(PluginKindRestoreItemAction, restoreName)
|
||||
if err != nil {
|
||||
pluginInfo, err := m.pluginRegistry.list(PluginKindRestoreItemAction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create clients for each
|
||||
for _, plugin := range pluginInfo {
|
||||
logger := &logrusAdapter{impl: m.logger, level: m.logLevel}
|
||||
client := newClientBuilder(baseConfig()).
|
||||
withCommand(plugin.commandName, plugin.commandArgs...).
|
||||
withPlugin(PluginKindRestoreItemAction, &RestoreItemActionPlugin{log: logger}).
|
||||
withLogger(logger).
|
||||
client()
|
||||
|
||||
m.clientStore.add(client, PluginKindRestoreItemAction, plugin.name, restoreName)
|
||||
|
||||
clients = append(clients, client)
|
||||
}
|
||||
}
|
||||
|
||||
var itemActions []restore.ItemAction
|
||||
for _, client := range clients {
|
||||
plugin, err := getPluginInstance(client, PluginKindRestoreItemAction)
|
||||
if err != nil {
|
||||
m.CloseRestoreItemActions(restoreName)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
itemAction, ok := plugin.(restore.ItemAction)
|
||||
if !ok {
|
||||
m.CloseRestoreItemActions(restoreName)
|
||||
return nil, errors.New("could not convert gRPC client to restore.ItemAction")
|
||||
}
|
||||
|
||||
itemActions = append(itemActions, itemAction)
|
||||
}
|
||||
|
||||
return itemActions, nil
|
||||
}
|
||||
|
||||
// CloseRestoreItemActions terminates the plugin sub-processes that
|
||||
// are hosting RestoreItemAction plugins for the given restore name.
|
||||
func (m *manager) CloseRestoreItemActions(restoreName string) error {
|
||||
return closeAll(m.clientStore, PluginKindRestoreItemAction, restoreName)
|
||||
}
|
||||
|
||||
func closeAll(store *clientStore, kind PluginKind, scope string) error {
|
||||
clients, err := store.list(kind, scope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
client.Kill()
|
||||
}
|
||||
|
||||
store.deleteAll(kind, scope)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *manager) CleanupClients() {
|
||||
plugin.CleanupClients()
|
||||
m.lock.Lock()
|
||||
|
||||
for _, restartableProcess := range m.restartableProcesses {
|
||||
restartableProcess.stop()
|
||||
}
|
||||
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
// getRestartableProcess returns a restartableProcess for a plugin identified by kind and name, creating a
|
||||
// restartableProcess if it is the first time it has been requested.
|
||||
func (m *manager) getRestartableProcess(kind PluginKind, name string) (RestartableProcess, error) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
|
||||
logger := m.logger.WithFields(logrus.Fields{
|
||||
"kind": PluginKindObjectStore.String(),
|
||||
"name": name,
|
||||
})
|
||||
logger.Debug("looking for plugin in registry")
|
||||
|
||||
info, err := m.registry.Get(kind, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger = logger.WithField("command", info.Command)
|
||||
|
||||
restartableProcess, found := m.restartableProcesses[info.Command]
|
||||
if found {
|
||||
logger.Debug("found preexisting restartable plugin process")
|
||||
return restartableProcess, nil
|
||||
}
|
||||
|
||||
logger.Debug("creating new restartable plugin process")
|
||||
|
||||
restartableProcess, err = m.restartableProcessFactory.newRestartableProcess(info.Command, m.logger, m.logLevel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.restartableProcesses[info.Command] = restartableProcess
|
||||
|
||||
return restartableProcess, nil
|
||||
}
|
||||
|
||||
// GetObjectStore returns a restartableObjectStore for name.
|
||||
func (m *manager) GetObjectStore(name string) (cloudprovider.ObjectStore, error) {
|
||||
restartableProcess, err := m.getRestartableProcess(PluginKindObjectStore, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := newRestartableObjectStore(name, restartableProcess)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetBlockStore returns a restartableBlockStore for name.
|
||||
func (m *manager) GetBlockStore(name string) (cloudprovider.BlockStore, error) {
|
||||
restartableProcess, err := m.getRestartableProcess(PluginKindBlockStore, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := newRestartableBlockStore(name, restartableProcess)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetBackupItemActions returns all backup item actions as restartableBackupItemActions.
|
||||
func (m *manager) GetBackupItemActions() ([]backup.ItemAction, error) {
|
||||
list := m.registry.List(PluginKindBackupItemAction)
|
||||
|
||||
actions := make([]backup.ItemAction, 0, len(list))
|
||||
|
||||
for i := range list {
|
||||
id := list[i]
|
||||
|
||||
r, err := m.GetBackupItemAction(id.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actions = append(actions, r)
|
||||
}
|
||||
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
// GetBackupItemAction returns a restartableBackupItemAction for name.
|
||||
func (m *manager) GetBackupItemAction(name string) (backup.ItemAction, error) {
|
||||
restartableProcess, err := m.getRestartableProcess(PluginKindBackupItemAction, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := newRestartableBackupItemAction(name, restartableProcess)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetRestoreItemActions returns all restore item actions as restartableRestoreItemActions.
|
||||
func (m *manager) GetRestoreItemActions() ([]restore.ItemAction, error) {
|
||||
list := m.registry.List(PluginKindRestoreItemAction)
|
||||
|
||||
actions := make([]restore.ItemAction, 0, len(list))
|
||||
|
||||
for i := range list {
|
||||
id := list[i]
|
||||
|
||||
r, err := m.GetRestoreItemAction(id.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
actions = append(actions, r)
|
||||
}
|
||||
|
||||
return actions, nil
|
||||
}
|
||||
|
||||
// GetRestoreItemAction returns a restartableRestoreItemAction for name.
|
||||
func (m *manager) GetRestoreItemAction(name string) (restore.ItemAction, error) {
|
||||
restartableProcess, err := m.getRestartableProcess(PluginKindRestoreItemAction, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := newRestartableRestoreItemAction(name, restartableProcess)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockRegistry struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (r *mockRegistry) DiscoverPlugins() error {
|
||||
args := r.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (r *mockRegistry) List(kind PluginKind) []PluginIdentifier {
|
||||
args := r.Called(kind)
|
||||
return args.Get(0).([]PluginIdentifier)
|
||||
}
|
||||
|
||||
func (r *mockRegistry) Get(kind PluginKind, name string) (PluginIdentifier, error) {
|
||||
args := r.Called(kind, name)
|
||||
var id PluginIdentifier
|
||||
if args.Get(0) != nil {
|
||||
id = args.Get(0).(PluginIdentifier)
|
||||
}
|
||||
return id, args.Error(1)
|
||||
}
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
|
||||
registry := &mockRegistry{}
|
||||
defer registry.AssertExpectations(t)
|
||||
|
||||
m := NewManager(logger, logLevel, registry).(*manager)
|
||||
assert.Equal(t, logger, m.logger)
|
||||
assert.Equal(t, logLevel, m.logLevel)
|
||||
assert.Equal(t, registry, m.registry)
|
||||
assert.NotNil(t, m.restartableProcesses)
|
||||
assert.Empty(t, m.restartableProcesses)
|
||||
}
|
||||
|
||||
type mockRestartableProcessFactory struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (f *mockRestartableProcessFactory) newRestartableProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (RestartableProcess, error) {
|
||||
args := f.Called(command, logger, logLevel)
|
||||
var rp RestartableProcess
|
||||
if args.Get(0) != nil {
|
||||
rp = args.Get(0).(RestartableProcess)
|
||||
}
|
||||
return rp, args.Error(1)
|
||||
}
|
||||
|
||||
type mockRestartableProcess struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (rp *mockRestartableProcess) addReinitializer(key kindAndName, r reinitializer) {
|
||||
rp.Called(key, r)
|
||||
}
|
||||
|
||||
func (rp *mockRestartableProcess) reset() error {
|
||||
args := rp.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (rp *mockRestartableProcess) resetIfNeeded() error {
|
||||
args := rp.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (rp *mockRestartableProcess) getByKindAndName(key kindAndName) (interface{}, error) {
|
||||
args := rp.Called(key)
|
||||
return args.Get(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (rp *mockRestartableProcess) stop() {
|
||||
rp.Called()
|
||||
}
|
||||
|
||||
func TestGetRestartableProcess(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
|
||||
registry := &mockRegistry{}
|
||||
defer registry.AssertExpectations(t)
|
||||
|
||||
m := NewManager(logger, logLevel, registry).(*manager)
|
||||
factory := &mockRestartableProcessFactory{}
|
||||
defer factory.AssertExpectations(t)
|
||||
m.restartableProcessFactory = factory
|
||||
|
||||
// Test 1: registry error
|
||||
pluginKind := PluginKindBackupItemAction
|
||||
pluginName := "pod"
|
||||
registry.On("Get", pluginKind, pluginName).Return(nil, errors.Errorf("registry")).Once()
|
||||
rp, err := m.getRestartableProcess(pluginKind, pluginName)
|
||||
assert.Nil(t, rp)
|
||||
assert.EqualError(t, err, "registry")
|
||||
|
||||
// Test 2: registry ok, factory error
|
||||
podID := PluginIdentifier{
|
||||
Command: "/command",
|
||||
Kind: pluginKind,
|
||||
Name: pluginName,
|
||||
}
|
||||
registry.On("Get", pluginKind, pluginName).Return(podID, nil)
|
||||
factory.On("newRestartableProcess", podID.Command, logger, logLevel).Return(nil, errors.Errorf("factory")).Once()
|
||||
rp, err = m.getRestartableProcess(pluginKind, pluginName)
|
||||
assert.Nil(t, rp)
|
||||
assert.EqualError(t, err, "factory")
|
||||
|
||||
// Test 3: registry ok, factory ok
|
||||
restartableProcess := &mockRestartableProcess{}
|
||||
defer restartableProcess.AssertExpectations(t)
|
||||
factory.On("newRestartableProcess", podID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
||||
rp, err = m.getRestartableProcess(pluginKind, pluginName)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, restartableProcess, rp)
|
||||
|
||||
// Test 4: retrieve from cache
|
||||
rp, err = m.getRestartableProcess(pluginKind, pluginName)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, restartableProcess, rp)
|
||||
}
|
||||
|
||||
func TestCleanupClients(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
|
||||
registry := &mockRegistry{}
|
||||
defer registry.AssertExpectations(t)
|
||||
|
||||
m := NewManager(logger, logLevel, registry).(*manager)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
rp := &mockRestartableProcess{}
|
||||
defer rp.AssertExpectations(t)
|
||||
rp.On("stop")
|
||||
m.restartableProcesses[fmt.Sprintf("rp%d", i)] = rp
|
||||
}
|
||||
|
||||
m.CleanupClients()
|
||||
}
|
||||
|
||||
func TestGetObjectStore(t *testing.T) {
|
||||
getPluginTest(t,
|
||||
PluginKindObjectStore,
|
||||
"aws",
|
||||
func(m Manager, name string) (interface{}, error) {
|
||||
return m.GetObjectStore(name)
|
||||
},
|
||||
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
||||
return &restartableObjectStore{
|
||||
key: kindAndName{kind: PluginKindObjectStore, name: name},
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetBlockStore(t *testing.T) {
|
||||
getPluginTest(t,
|
||||
PluginKindBlockStore,
|
||||
"aws",
|
||||
func(m Manager, name string) (interface{}, error) {
|
||||
return m.GetBlockStore(name)
|
||||
},
|
||||
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
||||
return &restartableBlockStore{
|
||||
key: kindAndName{kind: PluginKindBlockStore, name: name},
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetBackupItemAction(t *testing.T) {
|
||||
getPluginTest(t,
|
||||
PluginKindBackupItemAction,
|
||||
"pod",
|
||||
func(m Manager, name string) (interface{}, error) {
|
||||
return m.GetBackupItemAction(name)
|
||||
},
|
||||
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
||||
return &restartableBackupItemAction{
|
||||
key: kindAndName{kind: PluginKindBackupItemAction, name: name},
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func TestGetRestoreItemAction(t *testing.T) {
|
||||
getPluginTest(t,
|
||||
PluginKindRestoreItemAction,
|
||||
"pod",
|
||||
func(m Manager, name string) (interface{}, error) {
|
||||
return m.GetRestoreItemAction(name)
|
||||
},
|
||||
func(name string, sharedPluginProcess RestartableProcess) interface{} {
|
||||
return &restartableRestoreItemAction{
|
||||
key: kindAndName{kind: PluginKindRestoreItemAction, name: name},
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
},
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
func getPluginTest(
|
||||
t *testing.T,
|
||||
kind PluginKind,
|
||||
name string,
|
||||
getPluginFunc func(m Manager, name string) (interface{}, error),
|
||||
expectedResultFunc func(name string, sharedPluginProcess RestartableProcess) interface{},
|
||||
reinitializable bool,
|
||||
) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
|
||||
registry := &mockRegistry{}
|
||||
defer registry.AssertExpectations(t)
|
||||
|
||||
m := NewManager(logger, logLevel, registry).(*manager)
|
||||
factory := &mockRestartableProcessFactory{}
|
||||
defer factory.AssertExpectations(t)
|
||||
m.restartableProcessFactory = factory
|
||||
|
||||
pluginKind := kind
|
||||
pluginName := name
|
||||
pluginID := PluginIdentifier{
|
||||
Command: "/command",
|
||||
Kind: pluginKind,
|
||||
Name: pluginName,
|
||||
}
|
||||
registry.On("Get", pluginKind, pluginName).Return(pluginID, nil)
|
||||
|
||||
restartableProcess := &mockRestartableProcess{}
|
||||
defer restartableProcess.AssertExpectations(t)
|
||||
|
||||
// Test 1: error getting restartable process
|
||||
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("newRestartableProcess")).Once()
|
||||
actual, err := getPluginFunc(m, pluginName)
|
||||
assert.Nil(t, actual)
|
||||
assert.EqualError(t, err, "newRestartableProcess")
|
||||
|
||||
// Test 2: happy path
|
||||
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
||||
|
||||
expected := expectedResultFunc(name, restartableProcess)
|
||||
if reinitializable {
|
||||
key := kindAndName{kind: pluginID.Kind, name: pluginID.Name}
|
||||
restartableProcess.On("addReinitializer", key, expected)
|
||||
}
|
||||
|
||||
actual, err = getPluginFunc(m, pluginName)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestGetBackupItemActions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
names []string
|
||||
newRestartableProcessError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "No items",
|
||||
names: []string{},
|
||||
},
|
||||
{
|
||||
name: "Error getting restartable process",
|
||||
names: []string{"a", "b", "c"},
|
||||
newRestartableProcessError: errors.Errorf("newRestartableProcess"),
|
||||
expectedError: "newRestartableProcess",
|
||||
},
|
||||
{
|
||||
name: "Happy path",
|
||||
names: []string{"a", "b", "c"},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
|
||||
registry := &mockRegistry{}
|
||||
defer registry.AssertExpectations(t)
|
||||
|
||||
m := NewManager(logger, logLevel, registry).(*manager)
|
||||
factory := &mockRestartableProcessFactory{}
|
||||
defer factory.AssertExpectations(t)
|
||||
m.restartableProcessFactory = factory
|
||||
|
||||
pluginKind := PluginKindBackupItemAction
|
||||
var pluginIDs []PluginIdentifier
|
||||
for i := range tc.names {
|
||||
pluginID := PluginIdentifier{
|
||||
Command: "/command",
|
||||
Kind: pluginKind,
|
||||
Name: tc.names[i],
|
||||
}
|
||||
pluginIDs = append(pluginIDs, pluginID)
|
||||
}
|
||||
registry.On("List", pluginKind).Return(pluginIDs)
|
||||
|
||||
var expectedActions []interface{}
|
||||
for i := range pluginIDs {
|
||||
pluginID := pluginIDs[i]
|
||||
pluginName := pluginID.Name
|
||||
|
||||
registry.On("Get", pluginKind, pluginName).Return(pluginID, nil)
|
||||
|
||||
restartableProcess := &mockRestartableProcess{}
|
||||
defer restartableProcess.AssertExpectations(t)
|
||||
|
||||
expected := &restartableBackupItemAction{
|
||||
key: kindAndName{kind: pluginKind, name: pluginName},
|
||||
sharedPluginProcess: restartableProcess,
|
||||
}
|
||||
|
||||
if tc.newRestartableProcessError != nil {
|
||||
// Test 1: error getting restartable process
|
||||
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("newRestartableProcess")).Once()
|
||||
break
|
||||
}
|
||||
|
||||
// Test 2: happy path
|
||||
if i == 0 {
|
||||
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
||||
}
|
||||
|
||||
expectedActions = append(expectedActions, expected)
|
||||
}
|
||||
|
||||
backupItemActions, err := m.GetBackupItemActions()
|
||||
if tc.newRestartableProcessError != nil {
|
||||
assert.Nil(t, backupItemActions)
|
||||
assert.EqualError(t, err, "newRestartableProcess")
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
var actual []interface{}
|
||||
for i := range backupItemActions {
|
||||
actual = append(actual, backupItemActions[i])
|
||||
}
|
||||
assert.Equal(t, expectedActions, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRestoreItemActions(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
names []string
|
||||
newRestartableProcessError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "No items",
|
||||
names: []string{},
|
||||
},
|
||||
{
|
||||
name: "Error getting restartable process",
|
||||
names: []string{"a", "b", "c"},
|
||||
newRestartableProcessError: errors.Errorf("newRestartableProcess"),
|
||||
expectedError: "newRestartableProcess",
|
||||
},
|
||||
{
|
||||
name: "Happy path",
|
||||
names: []string{"a", "b", "c"},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
|
||||
registry := &mockRegistry{}
|
||||
defer registry.AssertExpectations(t)
|
||||
|
||||
m := NewManager(logger, logLevel, registry).(*manager)
|
||||
factory := &mockRestartableProcessFactory{}
|
||||
defer factory.AssertExpectations(t)
|
||||
m.restartableProcessFactory = factory
|
||||
|
||||
pluginKind := PluginKindRestoreItemAction
|
||||
var pluginIDs []PluginIdentifier
|
||||
for i := range tc.names {
|
||||
pluginID := PluginIdentifier{
|
||||
Command: "/command",
|
||||
Kind: pluginKind,
|
||||
Name: tc.names[i],
|
||||
}
|
||||
pluginIDs = append(pluginIDs, pluginID)
|
||||
}
|
||||
registry.On("List", pluginKind).Return(pluginIDs)
|
||||
|
||||
var expectedActions []interface{}
|
||||
for i := range pluginIDs {
|
||||
pluginID := pluginIDs[i]
|
||||
pluginName := pluginID.Name
|
||||
|
||||
registry.On("Get", pluginKind, pluginName).Return(pluginID, nil)
|
||||
|
||||
restartableProcess := &mockRestartableProcess{}
|
||||
defer restartableProcess.AssertExpectations(t)
|
||||
|
||||
expected := &restartableRestoreItemAction{
|
||||
key: kindAndName{kind: pluginKind, name: pluginName},
|
||||
sharedPluginProcess: restartableProcess,
|
||||
}
|
||||
|
||||
if tc.newRestartableProcessError != nil {
|
||||
// Test 1: error getting restartable process
|
||||
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(nil, errors.Errorf("newRestartableProcess")).Once()
|
||||
break
|
||||
}
|
||||
|
||||
// Test 2: happy path
|
||||
if i == 0 {
|
||||
factory.On("newRestartableProcess", pluginID.Command, logger, logLevel).Return(restartableProcess, nil).Once()
|
||||
}
|
||||
|
||||
expectedActions = append(expectedActions, expected)
|
||||
}
|
||||
|
||||
restoreItemActions, err := m.GetRestoreItemActions()
|
||||
if tc.newRestartableProcessError != nil {
|
||||
assert.Nil(t, restoreItemActions)
|
||||
assert.EqualError(t, err, "newRestartableProcess")
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
var actual []interface{}
|
||||
for i := range restoreItemActions {
|
||||
actual = append(actual, restoreItemActions[i])
|
||||
}
|
||||
assert.Equal(t, expectedActions, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
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 mockery v1.0.0. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import backup "github.com/heptio/ark/pkg/backup"
|
||||
import cloudprovider "github.com/heptio/ark/pkg/cloudprovider"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
import restore "github.com/heptio/ark/pkg/restore"
|
||||
|
||||
// Manager is an autogenerated mock type for the Manager type
|
||||
type Manager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CleanupClients provides a mock function with given fields:
|
||||
func (_m *Manager) CleanupClients() {
|
||||
_m.Called()
|
||||
}
|
||||
|
||||
// GetBackupItemAction provides a mock function with given fields: name
|
||||
func (_m *Manager) GetBackupItemAction(name string) (backup.ItemAction, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 backup.ItemAction
|
||||
if rf, ok := ret.Get(0).(func(string) backup.ItemAction); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(backup.ItemAction)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBackupItemActions provides a mock function with given fields:
|
||||
func (_m *Manager) GetBackupItemActions() ([]backup.ItemAction, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 []backup.ItemAction
|
||||
if rf, ok := ret.Get(0).(func() []backup.ItemAction); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]backup.ItemAction)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBlockStore provides a mock function with given fields: name
|
||||
func (_m *Manager) GetBlockStore(name string) (cloudprovider.BlockStore, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 cloudprovider.BlockStore
|
||||
if rf, ok := ret.Get(0).(func(string) cloudprovider.BlockStore); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(cloudprovider.BlockStore)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetObjectStore provides a mock function with given fields: name
|
||||
func (_m *Manager) GetObjectStore(name string) (cloudprovider.ObjectStore, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 cloudprovider.ObjectStore
|
||||
if rf, ok := ret.Get(0).(func(string) cloudprovider.ObjectStore); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(cloudprovider.ObjectStore)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRestoreItemAction provides a mock function with given fields: name
|
||||
func (_m *Manager) GetRestoreItemAction(name string) (restore.ItemAction, error) {
|
||||
ret := _m.Called(name)
|
||||
|
||||
var r0 restore.ItemAction
|
||||
if rf, ok := ret.Get(0).(func(string) restore.ItemAction); ok {
|
||||
r0 = rf(name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(restore.ItemAction)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetRestoreItemActions provides a mock function with given fields:
|
||||
func (_m *Manager) GetRestoreItemActions() ([]restore.ItemAction, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 []restore.ItemAction
|
||||
if rf, ok := ret.Get(0).(func() []restore.ItemAction); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]restore.ItemAction)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 mockery v1.0.0. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import logrus "github.com/sirupsen/logrus"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import plugin "github.com/heptio/ark/pkg/plugin"
|
||||
|
||||
// ProcessFactory is an autogenerated mock type for the ProcessFactory type
|
||||
type ProcessFactory struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// newProcess provides a mock function with given fields: command, logger, logLevel
|
||||
func (_m *ProcessFactory) newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (plugin.Process, error) {
|
||||
ret := _m.Called(command, logger, logLevel)
|
||||
|
||||
var r0 plugin.Process
|
||||
if rf, ok := ret.Get(0).(func(string, logrus.FieldLogger, logrus.Level) plugin.Process); ok {
|
||||
r0 = rf(command, logger, logLevel)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(plugin.Process)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, logrus.FieldLogger, logrus.Level) error); ok {
|
||||
r1 = rf(command, logger, logLevel)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
+90
-28
@@ -21,6 +21,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -35,43 +36,45 @@ const byteChunkSize = 16384
|
||||
// interface.
|
||||
type ObjectStorePlugin struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
|
||||
impl cloudprovider.ObjectStore
|
||||
*pluginBase
|
||||
}
|
||||
|
||||
// NewObjectStorePlugin construct an ObjectStorePlugin.
|
||||
func NewObjectStorePlugin(objectStore cloudprovider.ObjectStore) *ObjectStorePlugin {
|
||||
func NewObjectStorePlugin(options ...pluginOption) *ObjectStorePlugin {
|
||||
return &ObjectStorePlugin{
|
||||
impl: objectStore,
|
||||
pluginBase: newPluginBase(options...),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ObjectStorePlugin) Kind() PluginKind {
|
||||
return PluginKindObjectStore
|
||||
}
|
||||
|
||||
// GRPCServer registers an ObjectStore gRPC server.
|
||||
func (p *ObjectStorePlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterObjectStoreServer(s, &ObjectStoreGRPCServer{impl: p.impl})
|
||||
return nil
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// client code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCClient returns an ObjectStore gRPC client.
|
||||
func (p *ObjectStorePlugin) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
|
||||
return &ObjectStoreGRPCClient{grpcClient: proto.NewObjectStoreClient(c)}, nil
|
||||
return newClientDispenser(p.clientLogger, c, newObjectStoreGRPCClient), nil
|
||||
|
||||
}
|
||||
|
||||
// ObjectStoreGRPCClient implements the cloudprovider.ObjectStore interface and uses a
|
||||
// gRPC client to make calls to the plugin server.
|
||||
type ObjectStoreGRPCClient struct {
|
||||
*clientBase
|
||||
grpcClient proto.ObjectStoreClient
|
||||
}
|
||||
|
||||
func newObjectStoreGRPCClient(base *clientBase, clientConn *grpc.ClientConn) interface{} {
|
||||
return &ObjectStoreGRPCClient{
|
||||
clientBase: base,
|
||||
grpcClient: proto.NewObjectStoreClient(clientConn),
|
||||
}
|
||||
}
|
||||
|
||||
// Init prepares the ObjectStore for usage using the provided map of
|
||||
// configuration key-value pairs. It returns an error if the ObjectStore
|
||||
// cannot be initialized from the provided config.
|
||||
func (c *ObjectStoreGRPCClient) Init(config map[string]string) error {
|
||||
_, err := c.grpcClient.Init(context.Background(), &proto.InitRequest{Config: config})
|
||||
_, err := c.grpcClient.Init(context.Background(), &proto.InitRequest{Plugin: c.plugin, Config: config})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -98,7 +101,7 @@ func (c *ObjectStoreGRPCClient) PutObject(bucket, key string, body io.Reader) er
|
||||
return err
|
||||
}
|
||||
|
||||
if err := stream.Send(&proto.PutObjectRequest{Bucket: bucket, Key: key, Body: chunk[0:n]}); err != nil {
|
||||
if err := stream.Send(&proto.PutObjectRequest{Plugin: c.plugin, Bucket: bucket, Key: key, Body: chunk[0:n]}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -107,7 +110,7 @@ func (c *ObjectStoreGRPCClient) PutObject(bucket, key string, body io.Reader) er
|
||||
// GetObject retrieves the object with the given key from the specified
|
||||
// bucket in object storage.
|
||||
func (c *ObjectStoreGRPCClient) GetObject(bucket, key string) (io.ReadCloser, error) {
|
||||
stream, err := c.grpcClient.GetObject(context.Background(), &proto.GetObjectRequest{Bucket: bucket, Key: key})
|
||||
stream, err := c.grpcClient.GetObject(context.Background(), &proto.GetObjectRequest{Plugin: c.plugin, Bucket: bucket, Key: key})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -132,7 +135,7 @@ func (c *ObjectStoreGRPCClient) GetObject(bucket, key string) (io.ReadCloser, er
|
||||
// before the provided delimiter (this is often used to simulate a directory
|
||||
// hierarchy in object storage).
|
||||
func (c *ObjectStoreGRPCClient) ListCommonPrefixes(bucket, delimiter string) ([]string, error) {
|
||||
res, err := c.grpcClient.ListCommonPrefixes(context.Background(), &proto.ListCommonPrefixesRequest{Bucket: bucket, Delimiter: delimiter})
|
||||
res, err := c.grpcClient.ListCommonPrefixes(context.Background(), &proto.ListCommonPrefixesRequest{Plugin: c.plugin, Bucket: bucket, Delimiter: delimiter})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -142,7 +145,7 @@ func (c *ObjectStoreGRPCClient) ListCommonPrefixes(bucket, delimiter string) ([]
|
||||
|
||||
// ListObjects gets a list of all objects in bucket that have the same prefix.
|
||||
func (c *ObjectStoreGRPCClient) ListObjects(bucket, prefix string) ([]string, error) {
|
||||
res, err := c.grpcClient.ListObjects(context.Background(), &proto.ListObjectsRequest{Bucket: bucket, Prefix: prefix})
|
||||
res, err := c.grpcClient.ListObjects(context.Background(), &proto.ListObjectsRequest{Plugin: c.plugin, Bucket: bucket, Prefix: prefix})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,7 +156,7 @@ func (c *ObjectStoreGRPCClient) ListObjects(bucket, prefix string) ([]string, er
|
||||
// DeleteObject removes object with the specified key from the given
|
||||
// bucket.
|
||||
func (c *ObjectStoreGRPCClient) DeleteObject(bucket, key string) error {
|
||||
_, err := c.grpcClient.DeleteObject(context.Background(), &proto.DeleteObjectRequest{Bucket: bucket, Key: key})
|
||||
_, err := c.grpcClient.DeleteObject(context.Background(), &proto.DeleteObjectRequest{Plugin: c.plugin, Bucket: bucket, Key: key})
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -161,6 +164,7 @@ func (c *ObjectStoreGRPCClient) DeleteObject(bucket, key string) error {
|
||||
// CreateSignedURL creates a pre-signed URL for the given bucket and key that expires after ttl.
|
||||
func (c *ObjectStoreGRPCClient) CreateSignedURL(bucket, key string, ttl time.Duration) (string, error) {
|
||||
res, err := c.grpcClient.CreateSignedURL(context.Background(), &proto.CreateSignedURLRequest{
|
||||
Plugin: c.plugin,
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
Ttl: int64(ttl),
|
||||
@@ -172,17 +176,46 @@ func (c *ObjectStoreGRPCClient) CreateSignedURL(bucket, key string, ttl time.Dur
|
||||
return res.Url, nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// server code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCServer registers an ObjectStore gRPC server.
|
||||
func (p *ObjectStorePlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterObjectStoreServer(s, &ObjectStoreGRPCServer{mux: p.serverMux})
|
||||
return nil
|
||||
}
|
||||
|
||||
// ObjectStoreGRPCServer implements the proto-generated ObjectStoreServer interface, and accepts
|
||||
// gRPC calls and forwards them to an implementation of the pluggable interface.
|
||||
type ObjectStoreGRPCServer struct {
|
||||
impl cloudprovider.ObjectStore
|
||||
mux *serverMux
|
||||
}
|
||||
|
||||
func (s *ObjectStoreGRPCServer) getImpl(name string) (cloudprovider.ObjectStore, error) {
|
||||
impl, err := s.mux.getHandler(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
itemAction, ok := impl.(cloudprovider.ObjectStore)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not an object store", impl)
|
||||
}
|
||||
|
||||
return itemAction, nil
|
||||
}
|
||||
|
||||
// Init prepares the ObjectStore for usage using the provided map of
|
||||
// configuration key-value pairs. It returns an error if the ObjectStore
|
||||
// cannot be initialized from the provided config.
|
||||
func (s *ObjectStoreGRPCServer) Init(ctx context.Context, req *proto.InitRequest) (*proto.Empty, error) {
|
||||
if err := s.impl.Init(req.Config); err != nil {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := impl.Init(req.Config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -199,6 +232,11 @@ func (s *ObjectStoreGRPCServer) PutObject(stream proto.ObjectStore_PutObjectServ
|
||||
return err
|
||||
}
|
||||
|
||||
impl, err := s.getImpl(firstChunk.Plugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bucket := firstChunk.Bucket
|
||||
key := firstChunk.Key
|
||||
|
||||
@@ -220,7 +258,7 @@ func (s *ObjectStoreGRPCServer) PutObject(stream proto.ObjectStore_PutObjectServ
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := s.impl.PutObject(bucket, key, &StreamReadCloser{receive: receive, close: close}); err != nil {
|
||||
if err := impl.PutObject(bucket, key, &StreamReadCloser{receive: receive, close: close}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -230,7 +268,12 @@ func (s *ObjectStoreGRPCServer) PutObject(stream proto.ObjectStore_PutObjectServ
|
||||
// GetObject retrieves the object with the given key from the specified
|
||||
// bucket in object storage.
|
||||
func (s *ObjectStoreGRPCServer) GetObject(req *proto.GetObjectRequest, stream proto.ObjectStore_GetObjectServer) error {
|
||||
rdr, err := s.impl.GetObject(req.Bucket, req.Key)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rdr, err := impl.GetObject(req.Bucket, req.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -255,7 +298,12 @@ func (s *ObjectStoreGRPCServer) GetObject(req *proto.GetObjectRequest, stream pr
|
||||
// before the provided delimiter (this is often used to simulate a directory
|
||||
// hierarchy in object storage).
|
||||
func (s *ObjectStoreGRPCServer) ListCommonPrefixes(ctx context.Context, req *proto.ListCommonPrefixesRequest) (*proto.ListCommonPrefixesResponse, error) {
|
||||
prefixes, err := s.impl.ListCommonPrefixes(req.Bucket, req.Delimiter)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefixes, err := impl.ListCommonPrefixes(req.Bucket, req.Delimiter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -265,7 +313,12 @@ func (s *ObjectStoreGRPCServer) ListCommonPrefixes(ctx context.Context, req *pro
|
||||
|
||||
// ListObjects gets a list of all objects in bucket that have the same prefix.
|
||||
func (s *ObjectStoreGRPCServer) ListObjects(ctx context.Context, req *proto.ListObjectsRequest) (*proto.ListObjectsResponse, error) {
|
||||
keys, err := s.impl.ListObjects(req.Bucket, req.Prefix)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys, err := impl.ListObjects(req.Bucket, req.Prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -276,17 +329,26 @@ func (s *ObjectStoreGRPCServer) ListObjects(ctx context.Context, req *proto.List
|
||||
// DeleteObject removes object with the specified key from the given
|
||||
// bucket.
|
||||
func (s *ObjectStoreGRPCServer) DeleteObject(ctx context.Context, req *proto.DeleteObjectRequest) (*proto.Empty, error) {
|
||||
err := s.impl.DeleteObject(req.Bucket, req.Key)
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := impl.DeleteObject(req.Bucket, req.Key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &proto.Empty{}, nil
|
||||
}
|
||||
|
||||
// CreateSignedURL creates a pre-signed URL for the given bucket and key that expires after ttl.
|
||||
func (s *ObjectStoreGRPCServer) CreateSignedURL(ctx context.Context, req *proto.CreateSignedURLRequest) (*proto.CreateSignedURLResponse, error) {
|
||||
url, err := s.impl.CreateSignedURL(req.Bucket, req.Key, time.Duration(req.Ttl))
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
url, err := impl.CreateSignedURL(req.Bucket, req.Key, time.Duration(req.Ttl))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
type pluginBase struct {
|
||||
clientLogger logrus.FieldLogger
|
||||
*serverMux
|
||||
}
|
||||
|
||||
func newPluginBase(options ...pluginOption) *pluginBase {
|
||||
base := new(pluginBase)
|
||||
for _, option := range options {
|
||||
option(base)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
type pluginOption func(base *pluginBase)
|
||||
|
||||
func clientLogger(logger logrus.FieldLogger) pluginOption {
|
||||
return func(base *pluginBase) {
|
||||
base.clientLogger = logger
|
||||
}
|
||||
}
|
||||
|
||||
func serverLogger(logger logrus.FieldLogger) pluginOption {
|
||||
return func(base *pluginBase) {
|
||||
base.serverMux = newServerMux(logger)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClientLogger(t *testing.T) {
|
||||
base := &pluginBase{}
|
||||
logger := test.NewLogger()
|
||||
f := clientLogger(logger)
|
||||
f(base)
|
||||
assert.Equal(t, logger, base.clientLogger)
|
||||
}
|
||||
|
||||
func TestServerLogger(t *testing.T) {
|
||||
base := &pluginBase{}
|
||||
logger := test.NewLogger()
|
||||
f := serverLogger(logger)
|
||||
f(base)
|
||||
assert.Equal(t, newServerMux(logger), base.serverMux)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// PluginKind is a type alias for a string that describes
|
||||
// the kind of an Ark-supported plugin.
|
||||
type PluginKind string
|
||||
|
||||
// String returns the string for k.
|
||||
func (k PluginKind) String() string {
|
||||
return string(k)
|
||||
}
|
||||
|
||||
const (
|
||||
// PluginKindObjectStore represents an object store plugin.
|
||||
PluginKindObjectStore PluginKind = "ObjectStore"
|
||||
|
||||
// PluginKindBlockStore represents a block store plugin.
|
||||
PluginKindBlockStore PluginKind = "BlockStore"
|
||||
|
||||
// PluginKindBackupItemAction represents a backup item action plugin.
|
||||
PluginKindBackupItemAction PluginKind = "BackupItemAction"
|
||||
|
||||
// PluginKindRestoreItemAction represents a restore item action plugin.
|
||||
PluginKindRestoreItemAction PluginKind = "RestoreItemAction"
|
||||
|
||||
// PluginKindPluginLister represents a plugin lister plugin.
|
||||
PluginKindPluginLister PluginKind = "PluginLister"
|
||||
)
|
||||
|
||||
// allPluginKinds contains all the valid plugin kinds that Ark supports, excluding PluginLister because that is not a
|
||||
// kind that a developer would ever need to implement (it's handled by Ark and the Ark plugin library code).
|
||||
var allPluginKinds = sets.NewString(
|
||||
PluginKindObjectStore.String(),
|
||||
PluginKindBlockStore.String(),
|
||||
PluginKindBackupItemAction.String(),
|
||||
PluginKindRestoreItemAction.String(),
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func TestAllPluginKinds(t *testing.T) {
|
||||
expected := sets.NewString(
|
||||
PluginKindObjectStore.String(),
|
||||
PluginKindBlockStore.String(),
|
||||
PluginKindBackupItemAction.String(),
|
||||
PluginKindRestoreItemAction.String(),
|
||||
)
|
||||
|
||||
assert.True(t, expected.Equal(allPluginKinds))
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
proto "github.com/heptio/ark/pkg/plugin/generated"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// PluginIdenitifer uniquely identifies a plugin by command, kind, and name.
|
||||
type PluginIdentifier struct {
|
||||
Command string
|
||||
Kind PluginKind
|
||||
Name string
|
||||
}
|
||||
|
||||
// PluginLister lists plugins.
|
||||
type PluginLister interface {
|
||||
ListPlugins() ([]PluginIdentifier, error)
|
||||
}
|
||||
|
||||
// pluginLister implements PluginLister.
|
||||
type pluginLister struct {
|
||||
plugins []PluginIdentifier
|
||||
}
|
||||
|
||||
// NewPluginLister returns a new PluginLister for plugins.
|
||||
func NewPluginLister(plugins ...PluginIdentifier) PluginLister {
|
||||
return &pluginLister{plugins: plugins}
|
||||
}
|
||||
|
||||
// ListPlugins returns the pluginLister's plugins.
|
||||
func (pl *pluginLister) ListPlugins() ([]PluginIdentifier, error) {
|
||||
return pl.plugins, nil
|
||||
}
|
||||
|
||||
// PluginListerPlugin is a go-plugin Plugin for a PluginLister.
|
||||
type PluginListerPlugin struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
impl PluginLister
|
||||
}
|
||||
|
||||
// NewPluginListerPlugin creates a new PluginListerPlugin with impl as the server-side implementation.
|
||||
func NewPluginListerPlugin(impl PluginLister) *PluginListerPlugin {
|
||||
return &PluginListerPlugin{impl: impl}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// client code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCClient returns a PluginLister gRPC client.
|
||||
func (p *PluginListerPlugin) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
|
||||
return &PluginListerGRPCClient{grpcClient: proto.NewPluginListerClient(c)}, nil
|
||||
}
|
||||
|
||||
// PluginListerGRPCClient implements PluginLister and uses a gRPC client to make calls to the plugin server.
|
||||
type PluginListerGRPCClient struct {
|
||||
grpcClient proto.PluginListerClient
|
||||
}
|
||||
|
||||
// ListPlugins uses the gRPC client to request the list of plugins from the server. It translates the protobuf response
|
||||
// to []PluginIdentifier.
|
||||
func (c *PluginListerGRPCClient) ListPlugins() ([]PluginIdentifier, error) {
|
||||
resp, err := c.grpcClient.ListPlugins(context.Background(), &proto.Empty{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]PluginIdentifier, len(resp.Plugins))
|
||||
for i, id := range resp.Plugins {
|
||||
if !allPluginKinds.Has(id.Kind) {
|
||||
return nil, errors.Errorf("invalid plugin kind: %s", id.Kind)
|
||||
}
|
||||
|
||||
ret[i] = PluginIdentifier{
|
||||
Command: id.Command,
|
||||
Kind: PluginKind(id.Kind),
|
||||
Name: id.Name,
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// server code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCServer registers a PluginLister gRPC server.
|
||||
func (p *PluginListerPlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterPluginListerServer(s, &PluginListerGRPCServer{impl: p.impl})
|
||||
return nil
|
||||
}
|
||||
|
||||
// PluginListerGRPCServer implements the proto-generated PluginLister gRPC service interface. It accepts gRPC calls,
|
||||
// forwards them to impl, and translates the responses to protobuf.
|
||||
type PluginListerGRPCServer struct {
|
||||
impl PluginLister
|
||||
}
|
||||
|
||||
// ListPlugins returns a list of registered plugins, delegating to s.impl to perform the listing.
|
||||
func (s *PluginListerGRPCServer) ListPlugins(ctx context.Context, req *proto.Empty) (*proto.ListPluginsResponse, error) {
|
||||
list, err := s.impl.ListPlugins()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plugins := make([]*proto.PluginIdentifier, len(list))
|
||||
for i, id := range list {
|
||||
if !allPluginKinds.Has(id.Kind.String()) {
|
||||
return nil, errors.Errorf("invalid plugin kind: %s", id.Kind)
|
||||
}
|
||||
|
||||
plugins[i] = &proto.PluginIdentifier{
|
||||
Command: id.Command,
|
||||
Kind: id.Kind.String(),
|
||||
Name: id.Name,
|
||||
}
|
||||
}
|
||||
ret := &proto.ListPluginsResponse{
|
||||
Plugins: plugins,
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ProcessFactory interface {
|
||||
newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (Process, error)
|
||||
}
|
||||
|
||||
type processFactory struct {
|
||||
}
|
||||
|
||||
func newProcessFactory() ProcessFactory {
|
||||
return &processFactory{}
|
||||
}
|
||||
|
||||
func (pf *processFactory) newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (Process, error) {
|
||||
return newProcess(command, logger, logLevel)
|
||||
}
|
||||
|
||||
type Process interface {
|
||||
dispense(key kindAndName) (interface{}, error)
|
||||
exited() bool
|
||||
kill()
|
||||
}
|
||||
|
||||
type process struct {
|
||||
client *plugin.Client
|
||||
protocolClient plugin.ClientProtocol
|
||||
}
|
||||
|
||||
func newProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (Process, error) {
|
||||
builder := newClientBuilder(command, logger.WithField("cmd", command), logLevel)
|
||||
|
||||
// This creates a new go-plugin Client that has its own unique exec.Cmd for launching the plugin process.
|
||||
client := builder.client()
|
||||
|
||||
// This launches the plugin process.
|
||||
protocolClient, err := client.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &process{
|
||||
client: client,
|
||||
protocolClient: protocolClient,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (r *process) dispense(key kindAndName) (interface{}, error) {
|
||||
// This calls GRPCClient(clientConn) on the plugin instance registered for key.name.
|
||||
dispensed, err := r.protocolClient.Dispense(key.kind.String())
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Currently all plugins except for PluginLister dispense clientDispenser instances.
|
||||
if clientDispenser, ok := dispensed.(ClientDispenser); ok {
|
||||
if key.name == "" {
|
||||
return nil, errors.Errorf("%s plugin requested but name is missing", key.kind.String())
|
||||
}
|
||||
// Get the instance that implements our plugin interface (e.g. cloudprovider.ObjectStore) that is a gRPC-based
|
||||
// client
|
||||
dispensed = clientDispenser.clientFor(key.name)
|
||||
}
|
||||
|
||||
return dispensed, nil
|
||||
}
|
||||
|
||||
func (r *process) exited() bool {
|
||||
return r.client.Exited()
|
||||
}
|
||||
|
||||
func (r *process) kill() {
|
||||
r.client.Kill()
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type mockClientProtocol struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (cp *mockClientProtocol) Close() error {
|
||||
args := cp.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (cp *mockClientProtocol) Dispense(name string) (interface{}, error) {
|
||||
args := cp.Called(name)
|
||||
return args.Get(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (cp *mockClientProtocol) Ping() error {
|
||||
args := cp.Called()
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
type mockClientDispenser struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (cd *mockClientDispenser) clientFor(name string) interface{} {
|
||||
args := cd.Called(name)
|
||||
return args.Get(0)
|
||||
}
|
||||
|
||||
func TestDispense(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
missingKeyName bool
|
||||
dispenseError error
|
||||
clientDispenser bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "protocol client dispense error",
|
||||
dispenseError: errors.Errorf("protocol client dispense"),
|
||||
expectedError: "protocol client dispense",
|
||||
},
|
||||
{
|
||||
name: "plugin lister, no error",
|
||||
},
|
||||
{
|
||||
name: "client dispenser, missing key name",
|
||||
clientDispenser: true,
|
||||
missingKeyName: true,
|
||||
expectedError: "ObjectStore plugin requested but name is missing",
|
||||
},
|
||||
{
|
||||
name: "client dispenser, have key name",
|
||||
clientDispenser: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := new(process)
|
||||
protocolClient := new(mockClientProtocol)
|
||||
defer protocolClient.AssertExpectations(t)
|
||||
p.protocolClient = protocolClient
|
||||
|
||||
clientDispenser := new(mockClientDispenser)
|
||||
defer clientDispenser.AssertExpectations(t)
|
||||
|
||||
var client interface{}
|
||||
|
||||
key := kindAndName{}
|
||||
if tc.clientDispenser {
|
||||
key.kind = PluginKindObjectStore
|
||||
protocolClient.On("Dispense", key.kind.String()).Return(clientDispenser, tc.dispenseError)
|
||||
|
||||
if !tc.missingKeyName {
|
||||
key.name = "aws"
|
||||
client = &BackupItemActionGRPCClient{}
|
||||
clientDispenser.On("clientFor", key.name).Return(client)
|
||||
}
|
||||
} else {
|
||||
key.kind = PluginKindPluginLister
|
||||
client = &PluginListerGRPCClient{}
|
||||
protocolClient.On("Dispense", key.kind.String()).Return(client, tc.dispenseError)
|
||||
}
|
||||
|
||||
dispensed, err := p.dispense(key)
|
||||
|
||||
if tc.expectedError != "" {
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, client, dispensed)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,9 @@ package generated;
|
||||
import "Shared.proto";
|
||||
|
||||
message ExecuteRequest {
|
||||
bytes item = 1;
|
||||
bytes backup = 2;
|
||||
string plugin = 1;
|
||||
bytes item = 2;
|
||||
bytes backup = 3;
|
||||
}
|
||||
|
||||
message ExecuteResponse {
|
||||
@@ -21,6 +22,6 @@ message ResourceIdentifier {
|
||||
}
|
||||
|
||||
service BackupItemAction {
|
||||
rpc AppliesTo(Empty) returns (AppliesToResponse);
|
||||
rpc AppliesTo(AppliesToRequest) returns (AppliesToResponse);
|
||||
rpc Execute(ExecuteRequest) returns (ExecuteResponse);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,11 @@ package generated;
|
||||
import "Shared.proto";
|
||||
|
||||
message CreateVolumeRequest {
|
||||
string snapshotID = 1;
|
||||
string volumeType = 2;
|
||||
string volumeAZ = 3;
|
||||
int64 iops = 4;
|
||||
string plugin = 1;
|
||||
string snapshotID = 2;
|
||||
string volumeType = 3;
|
||||
string volumeAZ = 4;
|
||||
int64 iops = 5;
|
||||
}
|
||||
|
||||
message CreateVolumeResponse {
|
||||
@@ -15,8 +16,9 @@ message CreateVolumeResponse {
|
||||
}
|
||||
|
||||
message GetVolumeInfoRequest {
|
||||
string volumeID = 1;
|
||||
string volumeAZ = 2;
|
||||
string plugin = 1;
|
||||
string volumeID = 2;
|
||||
string volumeAZ = 3;
|
||||
}
|
||||
|
||||
message GetVolumeInfoResponse {
|
||||
@@ -25,8 +27,9 @@ message GetVolumeInfoResponse {
|
||||
}
|
||||
|
||||
message IsVolumeReadyRequest {
|
||||
string volumeID = 1;
|
||||
string volumeAZ = 2;
|
||||
string plugin = 1;
|
||||
string volumeID = 2;
|
||||
string volumeAZ = 3;
|
||||
}
|
||||
|
||||
message IsVolumeReadyResponse {
|
||||
@@ -34,9 +37,10 @@ message IsVolumeReadyResponse {
|
||||
}
|
||||
|
||||
message CreateSnapshotRequest {
|
||||
string volumeID = 1;
|
||||
string volumeAZ = 2;
|
||||
map<string, string> tags = 3;
|
||||
string plugin = 1;
|
||||
string volumeID = 2;
|
||||
string volumeAZ = 3;
|
||||
map<string, string> tags = 4;
|
||||
}
|
||||
|
||||
message CreateSnapshotResponse {
|
||||
@@ -44,11 +48,13 @@ message CreateSnapshotResponse {
|
||||
}
|
||||
|
||||
message DeleteSnapshotRequest {
|
||||
string snapshotID = 1;
|
||||
string plugin = 1;
|
||||
string snapshotID = 2;
|
||||
}
|
||||
|
||||
message GetVolumeIDRequest {
|
||||
bytes persistentVolume = 1;
|
||||
string plugin = 1;
|
||||
bytes persistentVolume = 2;
|
||||
}
|
||||
|
||||
message GetVolumeIDResponse {
|
||||
@@ -56,8 +62,9 @@ message GetVolumeIDResponse {
|
||||
}
|
||||
|
||||
message SetVolumeIDRequest {
|
||||
bytes persistentVolume = 1;
|
||||
string volumeID = 2;
|
||||
string plugin = 1;
|
||||
bytes persistentVolume = 2;
|
||||
string volumeID = 3;
|
||||
}
|
||||
|
||||
message SetVolumeIDResponse {
|
||||
|
||||
@@ -4,14 +4,16 @@ package generated;
|
||||
import "Shared.proto";
|
||||
|
||||
message PutObjectRequest {
|
||||
string bucket = 1;
|
||||
string key = 2;
|
||||
bytes body = 3;
|
||||
string plugin = 1;
|
||||
string bucket = 2;
|
||||
string key = 3;
|
||||
bytes body = 4;
|
||||
}
|
||||
|
||||
message GetObjectRequest {
|
||||
string bucket = 1;
|
||||
string key = 2;
|
||||
string plugin = 1;
|
||||
string bucket = 2;
|
||||
string key = 3;
|
||||
}
|
||||
|
||||
message Bytes {
|
||||
@@ -19,8 +21,9 @@ message Bytes {
|
||||
}
|
||||
|
||||
message ListCommonPrefixesRequest {
|
||||
string bucket = 1;
|
||||
string delimiter = 2;
|
||||
string plugin = 1;
|
||||
string bucket = 2;
|
||||
string delimiter = 3;
|
||||
}
|
||||
|
||||
message ListCommonPrefixesResponse {
|
||||
@@ -28,8 +31,9 @@ message ListCommonPrefixesResponse {
|
||||
}
|
||||
|
||||
message ListObjectsRequest {
|
||||
string bucket = 1;
|
||||
string prefix = 2;
|
||||
string plugin = 1;
|
||||
string bucket = 2;
|
||||
string prefix = 3;
|
||||
}
|
||||
|
||||
message ListObjectsResponse {
|
||||
@@ -37,15 +41,17 @@ message ListObjectsResponse {
|
||||
}
|
||||
|
||||
message DeleteObjectRequest {
|
||||
string bucket = 1;
|
||||
string key = 2;
|
||||
string plugin = 1;
|
||||
string bucket = 2;
|
||||
string key = 3;
|
||||
}
|
||||
|
||||
|
||||
message CreateSignedURLRequest {
|
||||
string bucket = 1;
|
||||
string key = 2;
|
||||
int64 ttl = 3;
|
||||
string plugin = 1;
|
||||
string bucket = 2;
|
||||
string key = 3;
|
||||
int64 ttl = 4;
|
||||
}
|
||||
|
||||
message CreateSignedURLResponse {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
syntax = "proto3";
|
||||
package generated;
|
||||
|
||||
import "Shared.proto";
|
||||
|
||||
message PluginIdentifier {
|
||||
string command = 1;
|
||||
string kind = 2;
|
||||
string name = 3;
|
||||
}
|
||||
|
||||
message ListPluginsResponse {
|
||||
repeated PluginIdentifier plugins = 1;
|
||||
}
|
||||
|
||||
service PluginLister {
|
||||
rpc ListPlugins(Empty) returns (ListPluginsResponse);
|
||||
}
|
||||
@@ -4,8 +4,9 @@ package generated;
|
||||
import "Shared.proto";
|
||||
|
||||
message RestoreExecuteRequest {
|
||||
bytes item = 1;
|
||||
bytes restore = 2;
|
||||
string plugin = 1;
|
||||
bytes item = 2;
|
||||
bytes restore = 3;
|
||||
}
|
||||
|
||||
message RestoreExecuteResponse {
|
||||
@@ -14,6 +15,6 @@ message RestoreExecuteResponse {
|
||||
}
|
||||
|
||||
service RestoreItemAction {
|
||||
rpc AppliesTo(Empty) returns (AppliesToResponse);
|
||||
rpc AppliesTo(AppliesToRequest) returns (AppliesToResponse);
|
||||
rpc Execute(RestoreExecuteRequest) returns (RestoreExecuteResponse);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,12 @@ package generated;
|
||||
message Empty {}
|
||||
|
||||
message InitRequest {
|
||||
map<string, string> config = 1;
|
||||
string plugin = 1;
|
||||
map<string, string> config = 2;
|
||||
}
|
||||
|
||||
message AppliesToRequest {
|
||||
string plugin = 1;
|
||||
}
|
||||
|
||||
message AppliesToResponse {
|
||||
|
||||
+222
-44
@@ -1,68 +1,246 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/filesystem"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// registry is a simple store of plugin binary information. If a binary
|
||||
// is registered as supporting multiple PluginKinds, it will be
|
||||
// gettable/listable for all of those kinds.
|
||||
// Registry manages information about available plugins.
|
||||
type Registry interface {
|
||||
// DiscoverPlugins discovers all available plugins.
|
||||
DiscoverPlugins() error
|
||||
// List returns all PluginIdentifiers for kind.
|
||||
List(kind PluginKind) []PluginIdentifier
|
||||
// Get returns the PluginIdentifier for kind and name.
|
||||
Get(kind PluginKind, name string) (PluginIdentifier, error)
|
||||
}
|
||||
|
||||
// kindAndName is a convenience struct that combines a PluginKind and a name.
|
||||
type kindAndName struct {
|
||||
kind PluginKind
|
||||
name string
|
||||
}
|
||||
|
||||
// registry implements Registry.
|
||||
type registry struct {
|
||||
// plugins is a nested map, keyed first by PluginKind,
|
||||
// and second by name. this is to allow easy listing
|
||||
// of plugins for a kind, as well as efficient lookup
|
||||
// of a plugin by kind+name.
|
||||
plugins map[PluginKind]map[string]pluginInfo
|
||||
// dir is the directory to search for plugins.
|
||||
dir string
|
||||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
|
||||
processFactory ProcessFactory
|
||||
fs filesystem.Interface
|
||||
pluginsByID map[kindAndName]PluginIdentifier
|
||||
pluginsByKind map[PluginKind][]PluginIdentifier
|
||||
}
|
||||
|
||||
func newRegistry() *registry {
|
||||
// NewRegistry returns a new registry.
|
||||
func NewRegistry(dir string, logger logrus.FieldLogger, logLevel logrus.Level) Registry {
|
||||
return ®istry{
|
||||
plugins: make(map[PluginKind]map[string]pluginInfo),
|
||||
dir: dir,
|
||||
logger: logger,
|
||||
logLevel: logLevel,
|
||||
|
||||
processFactory: newProcessFactory(),
|
||||
fs: filesystem.NewFileSystem(),
|
||||
pluginsByID: make(map[kindAndName]PluginIdentifier),
|
||||
pluginsByKind: make(map[PluginKind][]PluginIdentifier),
|
||||
}
|
||||
}
|
||||
|
||||
// register adds a binary to the registry. If the binary supports multiple
|
||||
// PluginKinds, it will be stored for each of those kinds so subsequent gets/lists
|
||||
// for any supported kind will return it.
|
||||
func (r *registry) register(name, commandName string, commandArgs []string, kinds ...PluginKind) {
|
||||
for _, kind := range kinds {
|
||||
if r.plugins[kind] == nil {
|
||||
r.plugins[kind] = make(map[string]pluginInfo)
|
||||
}
|
||||
|
||||
r.plugins[kind][name] = pluginInfo{
|
||||
kinds: kinds,
|
||||
name: name,
|
||||
commandName: commandName,
|
||||
commandArgs: commandArgs,
|
||||
}
|
||||
func (r *registry) DiscoverPlugins() error {
|
||||
plugins, err := r.readPluginsDir(r.dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start by adding ark's internal plugins
|
||||
commands := []string{os.Args[0]}
|
||||
// Then add the discovered plugin executables
|
||||
commands = append(commands, plugins...)
|
||||
|
||||
return r.discoverPlugins(commands)
|
||||
}
|
||||
|
||||
// list returns info about all plugin binaries that implement the given
|
||||
func (r *registry) discoverPlugins(commands []string) error {
|
||||
for _, command := range commands {
|
||||
plugins, err := r.listPlugins(command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, plugin := range plugins {
|
||||
r.logger.WithFields(logrus.Fields{
|
||||
"kind": plugin.Kind,
|
||||
"name": plugin.Name,
|
||||
"command": command,
|
||||
}).Info("registering plugin")
|
||||
|
||||
if err := r.register(plugin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns info about all plugin binaries that implement the given
|
||||
// PluginKind.
|
||||
func (r *registry) list(kind PluginKind) ([]pluginInfo, error) {
|
||||
var res []pluginInfo
|
||||
|
||||
if plugins, found := r.plugins[kind]; found {
|
||||
for _, itm := range plugins {
|
||||
res = append(res, itm)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("plugins not found")
|
||||
func (r *registry) List(kind PluginKind) []PluginIdentifier {
|
||||
return r.pluginsByKind[kind]
|
||||
}
|
||||
|
||||
// get returns info about a plugin with the given name and kind, or an
|
||||
// Get returns info about a plugin with the given name and kind, or an
|
||||
// error if one cannot be found.
|
||||
func (r *registry) get(kind PluginKind, name string) (pluginInfo, error) {
|
||||
if forKind := r.plugins[kind]; forKind != nil {
|
||||
if plugin, found := r.plugins[kind][name]; found {
|
||||
return plugin, nil
|
||||
func (r *registry) Get(kind PluginKind, name string) (PluginIdentifier, error) {
|
||||
p, found := r.pluginsByID[kindAndName{kind: kind, name: name}]
|
||||
if !found {
|
||||
return PluginIdentifier{}, newPluginNotFoundError(kind, name)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// readPluginsDir recursively reads dir looking for plugins.
|
||||
func (r *registry) readPluginsDir(dir string) ([]string, error) {
|
||||
if _, err := r.fs.Stat(dir); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return []string{}, nil
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return pluginInfo{}, errors.New("plugin not found")
|
||||
files, err := r.fs.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
fullPaths := make([]string, 0, len(files))
|
||||
for _, file := range files {
|
||||
fullPath := filepath.Join(dir, file.Name())
|
||||
|
||||
if file.IsDir() {
|
||||
subDirPaths, err := r.readPluginsDir(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fullPaths = append(fullPaths, subDirPaths...)
|
||||
continue
|
||||
}
|
||||
|
||||
if !executable(file) {
|
||||
continue
|
||||
}
|
||||
|
||||
fullPaths = append(fullPaths, fullPath)
|
||||
}
|
||||
return fullPaths, nil
|
||||
}
|
||||
|
||||
// executable determines if a file is executable.
|
||||
func executable(info os.FileInfo) bool {
|
||||
/*
|
||||
When we AND the mode with 0111:
|
||||
|
||||
- 0100 (user executable)
|
||||
- 0010 (group executable)
|
||||
- 0001 (other executable)
|
||||
|
||||
the result will be 0 if and only if none of the executable bits is set.
|
||||
*/
|
||||
return (info.Mode() & 0111) != 0
|
||||
}
|
||||
|
||||
// listPlugins executes command, queries it for registered plugins, and returns the list of PluginIdentifiers.
|
||||
func (r *registry) listPlugins(command string) ([]PluginIdentifier, error) {
|
||||
process, err := r.processFactory.newProcess(command, r.logger, r.logLevel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer process.kill()
|
||||
|
||||
plugin, err := process.dispense(kindAndName{kind: PluginKindPluginLister})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lister, ok := plugin.(PluginLister)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a PluginLister", plugin)
|
||||
}
|
||||
|
||||
return lister.ListPlugins()
|
||||
}
|
||||
|
||||
// register registers a PluginIdentifier with the registry.
|
||||
func (r *registry) register(id PluginIdentifier) error {
|
||||
key := kindAndName{kind: id.Kind, name: id.Name}
|
||||
if existing, found := r.pluginsByID[key]; found {
|
||||
return newDuplicatePluginRegistrationError(existing, id)
|
||||
}
|
||||
|
||||
r.pluginsByID[key] = id
|
||||
r.pluginsByKind[id.Kind] = append(r.pluginsByKind[id.Kind], id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// pluginNotFoundError indicates a plugin could not be located for kind and name.
|
||||
type pluginNotFoundError struct {
|
||||
kind PluginKind
|
||||
name string
|
||||
}
|
||||
|
||||
// newPluginNotFoundError returns a new pluginNotFoundError for kind and name.
|
||||
func newPluginNotFoundError(kind PluginKind, name string) *pluginNotFoundError {
|
||||
return &pluginNotFoundError{
|
||||
kind: kind,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *pluginNotFoundError) Error() string {
|
||||
return fmt.Sprintf("unable to locate %v plugin named %s", e.kind, e.name)
|
||||
}
|
||||
|
||||
type duplicatePluginRegistrationError struct {
|
||||
existing PluginIdentifier
|
||||
duplicate PluginIdentifier
|
||||
}
|
||||
|
||||
func newDuplicatePluginRegistrationError(existing, duplicate PluginIdentifier) *duplicatePluginRegistrationError {
|
||||
return &duplicatePluginRegistrationError{
|
||||
existing: existing,
|
||||
duplicate: duplicate,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *duplicatePluginRegistrationError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"unable to register plugin (kind=%s, name=%s, command=%s) because another plugin is already registered for this kind and name (command=%s)",
|
||||
string(e.duplicate.Kind),
|
||||
e.duplicate.Name,
|
||||
e.duplicate.Command,
|
||||
e.existing.Command,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewRegistry(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
dir := "/plugins"
|
||||
|
||||
r := NewRegistry(dir, logger, logLevel).(*registry)
|
||||
assert.Equal(t, dir, r.dir)
|
||||
assert.Equal(t, logger, r.logger)
|
||||
assert.Equal(t, logLevel, r.logLevel)
|
||||
assert.NotNil(t, r.pluginsByID)
|
||||
assert.Empty(t, r.pluginsByID)
|
||||
assert.NotNil(t, r.pluginsByKind)
|
||||
assert.Empty(t, r.pluginsByKind)
|
||||
}
|
||||
|
||||
type fakeFileInfo struct {
|
||||
os.FileInfo
|
||||
mode os.FileMode
|
||||
}
|
||||
|
||||
func (f *fakeFileInfo) Mode() os.FileMode {
|
||||
return f.mode
|
||||
}
|
||||
|
||||
func TestExecutable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mode uint32
|
||||
expectExecutable bool
|
||||
}{
|
||||
{
|
||||
name: "no perms",
|
||||
mode: 0000,
|
||||
},
|
||||
{
|
||||
name: "r--r--r--",
|
||||
mode: 0444,
|
||||
},
|
||||
{
|
||||
name: "rw-rw-rw-",
|
||||
mode: 0666,
|
||||
},
|
||||
{
|
||||
name: "--x------",
|
||||
mode: 0100,
|
||||
expectExecutable: true,
|
||||
},
|
||||
{
|
||||
name: "-----x---",
|
||||
mode: 0010,
|
||||
expectExecutable: true,
|
||||
},
|
||||
{
|
||||
name: "--------x",
|
||||
mode: 0001,
|
||||
expectExecutable: true,
|
||||
},
|
||||
{
|
||||
name: "rwxrwxrwx",
|
||||
mode: 0777,
|
||||
expectExecutable: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
info := &fakeFileInfo{
|
||||
mode: os.FileMode(test.mode),
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expectExecutable, executable(info))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPluginsDir(t *testing.T) {
|
||||
logger := test.NewLogger()
|
||||
logLevel := logrus.InfoLevel
|
||||
dir := "/plugins"
|
||||
|
||||
r := NewRegistry(dir, logger, logLevel).(*registry)
|
||||
r.fs = test.NewFakeFileSystem().
|
||||
WithFileAndMode("/plugins/executable1", []byte("plugin1"), 0755).
|
||||
WithFileAndMode("/plugins/nonexecutable2", []byte("plugin2"), 0644).
|
||||
WithFileAndMode("/plugins/executable3", []byte("plugin3"), 0755).
|
||||
WithFileAndMode("/plugins/nested/executable4", []byte("plugin4"), 0755).
|
||||
WithFileAndMode("/plugins/nested/nonexecutable5", []byte("plugin4"), 0644)
|
||||
|
||||
plugins, err := r.readPluginsDir(dir)
|
||||
require.NoError(t, err)
|
||||
|
||||
expected := []string{
|
||||
"/plugins/executable1",
|
||||
"/plugins/executable3",
|
||||
"/plugins/nested/executable4",
|
||||
}
|
||||
|
||||
sort.Strings(plugins)
|
||||
sort.Strings(expected)
|
||||
assert.Equal(t, expected, plugins)
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// restartableBackupItemAction is a backup item action for a given implementation (such as "pod"). It is associated with
|
||||
// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method
|
||||
// call, the restartableBackupItemAction asks its restartableProcess to restart itself if needed (e.g. if the
|
||||
// process terminated for any reason), then it proceeds with the actual call.
|
||||
type restartableBackupItemAction struct {
|
||||
key kindAndName
|
||||
sharedPluginProcess RestartableProcess
|
||||
}
|
||||
|
||||
// newRestartableBackupItemAction returns a new restartableBackupItemAction.
|
||||
func newRestartableBackupItemAction(name string, sharedPluginProcess RestartableProcess) *restartableBackupItemAction {
|
||||
r := &restartableBackupItemAction{
|
||||
key: kindAndName{kind: PluginKindBackupItemAction, name: name},
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// getBackupItemAction returns the backup item action for this restartableBackupItemAction. It does *not* restart the
|
||||
// plugin process.
|
||||
func (r *restartableBackupItemAction) getBackupItemAction() (backup.ItemAction, error) {
|
||||
plugin, err := r.sharedPluginProcess.getByKindAndName(r.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backupItemAction, ok := plugin.(backup.ItemAction)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a backup.ItemAction!", plugin)
|
||||
}
|
||||
|
||||
return backupItemAction, nil
|
||||
}
|
||||
|
||||
// getDelegate restarts the plugin process (if needed) and returns the backup item action for this restartableBackupItemAction.
|
||||
func (r *restartableBackupItemAction) getDelegate() (backup.ItemAction, error) {
|
||||
if err := r.sharedPluginProcess.resetIfNeeded(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.getBackupItemAction()
|
||||
}
|
||||
|
||||
// AppliesTo restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBackupItemAction) AppliesTo() (backup.ResourceSelector, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return backup.ResourceSelector{}, err
|
||||
}
|
||||
|
||||
return delegate.AppliesTo()
|
||||
}
|
||||
|
||||
// Execute restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBackupItemAction) Execute(item runtime.Unstructured, backup *api.Backup) (runtime.Unstructured, []backup.ResourceIdentifier, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return delegate.Execute(item, backup)
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/backup"
|
||||
"github.com/heptio/ark/pkg/backup/mocks"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func TestRestartableGetBackupItemAction(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin interface{}
|
||||
getError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "error getting by kind and name",
|
||||
getError: errors.Errorf("get error"),
|
||||
expectedError: "get error",
|
||||
},
|
||||
{
|
||||
name: "wrong type",
|
||||
plugin: 3,
|
||||
expectedError: "int is not a backup.ItemAction!",
|
||||
},
|
||||
{
|
||||
name: "happy path",
|
||||
plugin: new(mocks.ItemAction),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
name := "pod"
|
||||
key := kindAndName{kind: PluginKindBackupItemAction, name: name}
|
||||
p.On("getByKindAndName", key).Return(tc.plugin, tc.getError)
|
||||
|
||||
r := newRestartableBackupItemAction(name, p)
|
||||
a, err := r.getBackupItemAction()
|
||||
if tc.expectedError != "" {
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.plugin, a)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartableBackupItemActionGetDelegate(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// Reset error
|
||||
p.On("resetIfNeeded").Return(errors.Errorf("reset error")).Once()
|
||||
name := "pod"
|
||||
r := newRestartableBackupItemAction(name, p)
|
||||
a, err := r.getDelegate()
|
||||
assert.Nil(t, a)
|
||||
assert.EqualError(t, err, "reset error")
|
||||
|
||||
// Happy path
|
||||
p.On("resetIfNeeded").Return(nil)
|
||||
expected := new(mocks.ItemAction)
|
||||
key := kindAndName{kind: PluginKindBackupItemAction, name: name}
|
||||
p.On("getByKindAndName", key).Return(expected, nil)
|
||||
|
||||
a, err = r.getDelegate()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, a)
|
||||
}
|
||||
|
||||
func TestRestartableBackupItemActionDelegatedFunctions(t *testing.T) {
|
||||
b := new(v1.Backup)
|
||||
|
||||
pv := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
pvToReturn := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"color": "green",
|
||||
},
|
||||
}
|
||||
|
||||
additionalItems := []backup.ResourceIdentifier{
|
||||
{
|
||||
GroupResource: schema.GroupResource{Group: "ark.heptio.com", Resource: "backups"},
|
||||
},
|
||||
}
|
||||
|
||||
runRestartableDelegateTests(
|
||||
t,
|
||||
PluginKindBackupItemAction,
|
||||
func(key kindAndName, p RestartableProcess) interface{} {
|
||||
return &restartableBackupItemAction{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
},
|
||||
func() mockable {
|
||||
return new(mocks.ItemAction)
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "AppliesTo",
|
||||
inputs: []interface{}{},
|
||||
expectedErrorOutputs: []interface{}{backup.ResourceSelector{}, errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{backup.ResourceSelector{IncludedNamespaces: []string{"a"}}, errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "Execute",
|
||||
inputs: []interface{}{pv, b},
|
||||
expectedErrorOutputs: []interface{}{nil, ([]backup.ResourceIdentifier)(nil), errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{pvToReturn, additionalItems, errors.Errorf("delegate error")},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// restartableBlockStore is an object store for a given implementation (such as "aws"). It is associated with
|
||||
// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method
|
||||
// call, the restartableBlockStore asks its restartableProcess to restart itself if needed (e.g. if the
|
||||
// process terminated for any reason), then it proceeds with the actual call.
|
||||
type restartableBlockStore struct {
|
||||
key kindAndName
|
||||
sharedPluginProcess RestartableProcess
|
||||
config map[string]string
|
||||
}
|
||||
|
||||
// newRestartableBlockStore returns a new restartableBlockStore.
|
||||
func newRestartableBlockStore(name string, sharedPluginProcess RestartableProcess) *restartableBlockStore {
|
||||
key := kindAndName{kind: PluginKindBlockStore, name: name}
|
||||
r := &restartableBlockStore{
|
||||
key: key,
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
|
||||
// Register our reinitializer so we can reinitialize after a restart with r.config.
|
||||
sharedPluginProcess.addReinitializer(key, r)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// reinitialize reinitializes a re-dispensed plugin using the initial data passed to Init().
|
||||
func (r *restartableBlockStore) reinitialize(dispensed interface{}) error {
|
||||
blockStore, ok := dispensed.(cloudprovider.BlockStore)
|
||||
if !ok {
|
||||
return errors.Errorf("%T is not a cloudprovider.BlockStore!", dispensed)
|
||||
}
|
||||
return r.init(blockStore, r.config)
|
||||
}
|
||||
|
||||
// getBlockStore returns the block store for this restartableBlockStore. It does *not* restart the
|
||||
// plugin process.
|
||||
func (r *restartableBlockStore) getBlockStore() (cloudprovider.BlockStore, error) {
|
||||
plugin, err := r.sharedPluginProcess.getByKindAndName(r.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockStore, ok := plugin.(cloudprovider.BlockStore)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a cloudprovider.BlockStore!", plugin)
|
||||
}
|
||||
|
||||
return blockStore, nil
|
||||
}
|
||||
|
||||
// getDelegate restarts the plugin process (if needed) and returns the block store for this restartableBlockStore.
|
||||
func (r *restartableBlockStore) getDelegate() (cloudprovider.BlockStore, error) {
|
||||
if err := r.sharedPluginProcess.resetIfNeeded(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.getBlockStore()
|
||||
}
|
||||
|
||||
// Init initializes the block store instance using config. If this is the first invocation, r stores config for future
|
||||
// reinitialization needs. Init does NOT restart the shared plugin process. Init may only be called once.
|
||||
func (r *restartableBlockStore) Init(config map[string]string) error {
|
||||
if r.config != nil {
|
||||
return errors.Errorf("already initialized")
|
||||
}
|
||||
|
||||
// Not using getDelegate() to avoid possible infinite recursion
|
||||
delegate, err := r.getBlockStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.config = config
|
||||
|
||||
return r.init(delegate, config)
|
||||
}
|
||||
|
||||
// init calls Init on blockStore with config. This is split out from Init() so that both Init() and reinitialize() may
|
||||
// call it using a specific BlockStore.
|
||||
func (r *restartableBlockStore) init(blockStore cloudprovider.BlockStore, config map[string]string) error {
|
||||
return blockStore.Init(config)
|
||||
}
|
||||
|
||||
// CreateVolumeFromSnapshot restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) CreateVolumeFromSnapshot(snapshotID string, volumeType string, volumeAZ string, iops *int64) (volumeID string, err error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return delegate.CreateVolumeFromSnapshot(snapshotID, volumeType, volumeAZ, iops)
|
||||
}
|
||||
|
||||
// GetVolumeID restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) GetVolumeID(pv runtime.Unstructured) (string, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return delegate.GetVolumeID(pv)
|
||||
}
|
||||
|
||||
// SetVolumeID restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) SetVolumeID(pv runtime.Unstructured, volumeID string) (runtime.Unstructured, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return delegate.SetVolumeID(pv, volumeID)
|
||||
}
|
||||
|
||||
// GetVolumeInfo restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) GetVolumeInfo(volumeID string, volumeAZ string) (string, *int64, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return delegate.GetVolumeInfo(volumeID, volumeAZ)
|
||||
}
|
||||
|
||||
// IsVolumeReady restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) IsVolumeReady(volumeID string, volumeAZ string) (ready bool, err error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return delegate.IsVolumeReady(volumeID, volumeAZ)
|
||||
}
|
||||
|
||||
// CreateSnapshot restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) CreateSnapshot(volumeID string, volumeAZ string, tags map[string]string) (snapshotID string, err error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return delegate.CreateSnapshot(volumeID, volumeAZ, tags)
|
||||
}
|
||||
|
||||
// DeleteSnapshot restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableBlockStore) DeleteSnapshot(snapshotID string) error {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return delegate.DeleteSnapshot(snapshotID)
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Azure/go-autorest/autorest/to"
|
||||
"github.com/heptio/ark/pkg/cloudprovider/mocks"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestRestartableGetBlockStore(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin interface{}
|
||||
getError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "error getting by kind and name",
|
||||
getError: errors.Errorf("get error"),
|
||||
expectedError: "get error",
|
||||
},
|
||||
{
|
||||
name: "wrong type",
|
||||
plugin: 3,
|
||||
expectedError: "int is not a cloudprovider.BlockStore!",
|
||||
},
|
||||
{
|
||||
name: "happy path",
|
||||
plugin: new(mocks.BlockStore),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindBlockStore, name: name}
|
||||
p.On("getByKindAndName", key).Return(tc.plugin, tc.getError)
|
||||
|
||||
r := &restartableBlockStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
a, err := r.getBlockStore()
|
||||
if tc.expectedError != "" {
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.plugin, a)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartableBlockStoreReinitialize(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindBlockStore, name: name}
|
||||
r := &restartableBlockStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
config: map[string]string{
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
err := r.reinitialize(3)
|
||||
assert.EqualError(t, err, "int is not a cloudprovider.BlockStore!")
|
||||
|
||||
blockStore := new(mocks.BlockStore)
|
||||
blockStore.Test(t)
|
||||
defer blockStore.AssertExpectations(t)
|
||||
|
||||
blockStore.On("Init", r.config).Return(errors.Errorf("init error")).Once()
|
||||
err = r.reinitialize(blockStore)
|
||||
assert.EqualError(t, err, "init error")
|
||||
|
||||
blockStore.On("Init", r.config).Return(nil)
|
||||
err = r.reinitialize(blockStore)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRestartableBlockStoreGetDelegate(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// Reset error
|
||||
p.On("resetIfNeeded").Return(errors.Errorf("reset error")).Once()
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindBlockStore, name: name}
|
||||
r := &restartableBlockStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
a, err := r.getDelegate()
|
||||
assert.Nil(t, a)
|
||||
assert.EqualError(t, err, "reset error")
|
||||
|
||||
// Happy path
|
||||
p.On("resetIfNeeded").Return(nil)
|
||||
blockStore := new(mocks.BlockStore)
|
||||
blockStore.Test(t)
|
||||
defer blockStore.AssertExpectations(t)
|
||||
p.On("getByKindAndName", key).Return(blockStore, nil)
|
||||
|
||||
a, err = r.getDelegate()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, blockStore, a)
|
||||
}
|
||||
|
||||
func TestRestartableBlockStoreInit(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// getBlockStore error
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindBlockStore, name: name}
|
||||
r := &restartableBlockStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
p.On("getByKindAndName", key).Return(nil, errors.Errorf("getByKindAndName error")).Once()
|
||||
|
||||
config := map[string]string{
|
||||
"color": "blue",
|
||||
}
|
||||
err := r.Init(config)
|
||||
assert.EqualError(t, err, "getByKindAndName error")
|
||||
|
||||
// Delegate returns error
|
||||
blockStore := new(mocks.BlockStore)
|
||||
blockStore.Test(t)
|
||||
defer blockStore.AssertExpectations(t)
|
||||
p.On("getByKindAndName", key).Return(blockStore, nil)
|
||||
blockStore.On("Init", config).Return(errors.Errorf("Init error")).Once()
|
||||
|
||||
err = r.Init(config)
|
||||
assert.EqualError(t, err, "Init error")
|
||||
|
||||
// wipe this out because the previous failed Init call set it
|
||||
r.config = nil
|
||||
|
||||
// Happy path
|
||||
blockStore.On("Init", config).Return(nil)
|
||||
err = r.Init(config)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config, r.config)
|
||||
|
||||
// Calling Init twice is forbidden
|
||||
err = r.Init(config)
|
||||
assert.EqualError(t, err, "already initialized")
|
||||
}
|
||||
|
||||
func TestRestartableBlockStoreDelegatedFunctions(t *testing.T) {
|
||||
pv := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
pvToReturn := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"color": "green",
|
||||
},
|
||||
}
|
||||
|
||||
runRestartableDelegateTests(
|
||||
t,
|
||||
PluginKindBlockStore,
|
||||
func(key kindAndName, p RestartableProcess) interface{} {
|
||||
return &restartableBlockStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
},
|
||||
func() mockable {
|
||||
return new(mocks.BlockStore)
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "CreateVolumeFromSnapshot",
|
||||
inputs: []interface{}{"snapshotID", "volumeID", "volumeAZ", to.Int64Ptr(10000)},
|
||||
expectedErrorOutputs: []interface{}{"", errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{"volumeID", errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "GetVolumeID",
|
||||
inputs: []interface{}{pv},
|
||||
expectedErrorOutputs: []interface{}{"", errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{"volumeID", errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "SetVolumeID",
|
||||
inputs: []interface{}{pv, "volumeID"},
|
||||
expectedErrorOutputs: []interface{}{nil, errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{pvToReturn, errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "GetVolumeInfo",
|
||||
inputs: []interface{}{"volumeID", "volumeAZ"},
|
||||
expectedErrorOutputs: []interface{}{"", (*int64)(nil), errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{"volumeType", to.Int64Ptr(10000), errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "IsVolumeReady",
|
||||
inputs: []interface{}{"volumeID", "volumeAZ"},
|
||||
expectedErrorOutputs: []interface{}{false, errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{true, errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "CreateSnapshot",
|
||||
inputs: []interface{}{"volumeID", "volumeAZ", map[string]string{"a": "b"}},
|
||||
expectedErrorOutputs: []interface{}{"", errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{"snapshotID", errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "DeleteSnapshot",
|
||||
inputs: []interface{}{"snapshotID"},
|
||||
expectedErrorOutputs: []interface{}{errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type restartableDelegateTest struct {
|
||||
function string
|
||||
inputs []interface{}
|
||||
expectedErrorOutputs []interface{}
|
||||
expectedDelegateOutputs []interface{}
|
||||
}
|
||||
|
||||
type mockable interface {
|
||||
Test(t mock.TestingT)
|
||||
On(method string, args ...interface{}) *mock.Call
|
||||
AssertExpectations(t mock.TestingT) bool
|
||||
}
|
||||
|
||||
func runRestartableDelegateTests(
|
||||
t *testing.T,
|
||||
kind PluginKind,
|
||||
newRestartable func(key kindAndName, p RestartableProcess) interface{},
|
||||
newMock func() mockable,
|
||||
tests ...restartableDelegateTest,
|
||||
) {
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.function, func(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// getDelegate error
|
||||
p.On("resetIfNeeded").Return(errors.Errorf("reset error")).Once()
|
||||
name := "delegateName"
|
||||
key := kindAndName{kind: kind, name: name}
|
||||
r := newRestartable(key, p)
|
||||
|
||||
// Get the method we're going to call using reflection
|
||||
method := reflect.ValueOf(r).MethodByName(tc.function)
|
||||
require.NotEmpty(t, method)
|
||||
|
||||
// Convert the test case inputs ([]interface{}) to []reflect.Value
|
||||
var inputValues []reflect.Value
|
||||
for i := range tc.inputs {
|
||||
inputValues = append(inputValues, reflect.ValueOf(tc.inputs[i]))
|
||||
}
|
||||
|
||||
// Invoke the method being tested
|
||||
actual := method.Call(inputValues)
|
||||
|
||||
// This function asserts that the actual outputs match the expected outputs
|
||||
checkOutputs := func(expected []interface{}, actual []reflect.Value) {
|
||||
require.Equal(t, len(expected), len(actual))
|
||||
|
||||
for i := range actual {
|
||||
// Get the underlying value from the reflect.Value
|
||||
a := actual[i].Interface()
|
||||
|
||||
// Check if it's an error
|
||||
actualErr, actualErrOk := a.(error)
|
||||
// Check if the expected output element is an error
|
||||
expectedErr, expectedErrOk := expected[i].(error)
|
||||
// If both are errors, use EqualError
|
||||
if actualErrOk && expectedErrOk {
|
||||
assert.EqualError(t, actualErr, expectedErr.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, use plain Equal
|
||||
assert.Equal(t, expected[i], a)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we get what we expected when getDelegate returned an error
|
||||
checkOutputs(tc.expectedErrorOutputs, actual)
|
||||
|
||||
// Invoke delegate, make sure all returned values are passed through
|
||||
p.On("resetIfNeeded").Return(nil)
|
||||
|
||||
delegate := newMock()
|
||||
delegate.Test(t)
|
||||
defer delegate.AssertExpectations(t)
|
||||
|
||||
p.On("getByKindAndName", key).Return(delegate, nil)
|
||||
|
||||
// Set up the mocked method in the delegate
|
||||
delegate.On(tc.function, tc.inputs...).Return(tc.expectedDelegateOutputs...)
|
||||
|
||||
// Invoke the method being tested
|
||||
actual = method.Call(inputValues)
|
||||
|
||||
// Make sure we get what we expected when invoking the delegate
|
||||
checkOutputs(tc.expectedDelegateOutputs, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/heptio/ark/pkg/cloudprovider"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// restartableObjectStore is an object store for a given implementation (such as "aws"). It is associated with
|
||||
// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method
|
||||
// call, the restartableObjectStore asks its restartableProcess to restart itself if needed (e.g. if the
|
||||
// process terminated for any reason), then it proceeds with the actual call.
|
||||
type restartableObjectStore struct {
|
||||
key kindAndName
|
||||
sharedPluginProcess RestartableProcess
|
||||
// config contains the data used to initialize the plugin. It is used to reinitialize the plugin in the event its
|
||||
// sharedPluginProcess gets restarted.
|
||||
config map[string]string
|
||||
}
|
||||
|
||||
// newRestartableObjectStore returns a new restartableObjectStore.
|
||||
func newRestartableObjectStore(name string, sharedPluginProcess RestartableProcess) *restartableObjectStore {
|
||||
key := kindAndName{kind: PluginKindObjectStore, name: name}
|
||||
r := &restartableObjectStore{
|
||||
key: key,
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
|
||||
// Register our reinitializer so we can reinitialize after a restart with r.config.
|
||||
sharedPluginProcess.addReinitializer(key, r)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// reinitialize reinitializes a re-dispensed plugin using the initial data passed to Init().
|
||||
func (r *restartableObjectStore) reinitialize(dispensed interface{}) error {
|
||||
objectStore, ok := dispensed.(cloudprovider.ObjectStore)
|
||||
if !ok {
|
||||
return errors.Errorf("%T is not a cloudprovider.ObjectStore!", dispensed)
|
||||
}
|
||||
|
||||
return r.init(objectStore, r.config)
|
||||
}
|
||||
|
||||
// getObjectStore returns the object store for this restartableObjectStore. It does *not* restart the
|
||||
// plugin process.
|
||||
func (r *restartableObjectStore) getObjectStore() (cloudprovider.ObjectStore, error) {
|
||||
plugin, err := r.sharedPluginProcess.getByKindAndName(r.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objectStore, ok := plugin.(cloudprovider.ObjectStore)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a cloudprovider.ObjectStore!", plugin)
|
||||
}
|
||||
|
||||
return objectStore, nil
|
||||
}
|
||||
|
||||
// getDelegate restarts the plugin process (if needed) and returns the object store for this restartableObjectStore.
|
||||
func (r *restartableObjectStore) getDelegate() (cloudprovider.ObjectStore, error) {
|
||||
if err := r.sharedPluginProcess.resetIfNeeded(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.getObjectStore()
|
||||
}
|
||||
|
||||
// Init initializes the object store instance using config. If this is the first invocation, r stores config for future
|
||||
// reinitialization needs. Init does NOT restart the shared plugin process. Init may only be called once.
|
||||
func (r *restartableObjectStore) Init(config map[string]string) error {
|
||||
if r.config != nil {
|
||||
return errors.Errorf("already initialized")
|
||||
}
|
||||
|
||||
// Not using getDelegate() to avoid possible infinite recursion
|
||||
delegate, err := r.getObjectStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.config = config
|
||||
|
||||
return r.init(delegate, config)
|
||||
}
|
||||
|
||||
// init calls Init on objectStore with config. This is split out from Init() so that both Init() and reinitialize() may
|
||||
// call it using a specific ObjectStore.
|
||||
func (r *restartableObjectStore) init(objectStore cloudprovider.ObjectStore, config map[string]string) error {
|
||||
return objectStore.Init(config)
|
||||
}
|
||||
|
||||
// PutObject restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableObjectStore) PutObject(bucket string, key string, body io.Reader) error {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return delegate.PutObject(bucket, key, body)
|
||||
}
|
||||
|
||||
// GetObject restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableObjectStore) GetObject(bucket string, key string) (io.ReadCloser, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return delegate.GetObject(bucket, key)
|
||||
}
|
||||
|
||||
// ListCommonPrefixes restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableObjectStore) ListCommonPrefixes(bucket string, delimiter string) ([]string, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return delegate.ListCommonPrefixes(bucket, delimiter)
|
||||
}
|
||||
|
||||
// ListObjects restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableObjectStore) ListObjects(bucket string, prefix string) ([]string, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return delegate.ListObjects(bucket, prefix)
|
||||
}
|
||||
|
||||
// DeleteObject restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableObjectStore) DeleteObject(bucket string, key string) error {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return delegate.DeleteObject(bucket, key)
|
||||
}
|
||||
|
||||
// CreateSignedURL restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableObjectStore) CreateSignedURL(bucket string, key string, ttl time.Duration) (string, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return delegate.CreateSignedURL(bucket, key, ttl)
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/heptio/ark/pkg/util/test"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRestartableGetObjectStore(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin interface{}
|
||||
getError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "error getting by kind and name",
|
||||
getError: errors.Errorf("get error"),
|
||||
expectedError: "get error",
|
||||
},
|
||||
{
|
||||
name: "wrong type",
|
||||
plugin: 3,
|
||||
expectedError: "int is not a cloudprovider.ObjectStore!",
|
||||
},
|
||||
{
|
||||
name: "happy path",
|
||||
plugin: new(test.ObjectStore),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindObjectStore, name: name}
|
||||
p.On("getByKindAndName", key).Return(tc.plugin, tc.getError)
|
||||
|
||||
r := &restartableObjectStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
a, err := r.getObjectStore()
|
||||
if tc.expectedError != "" {
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.plugin, a)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartableObjectStoreReinitialize(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindObjectStore, name: name}
|
||||
r := &restartableObjectStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
config: map[string]string{
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
err := r.reinitialize(3)
|
||||
assert.EqualError(t, err, "int is not a cloudprovider.ObjectStore!")
|
||||
|
||||
objectStore := new(test.ObjectStore)
|
||||
objectStore.Test(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
|
||||
objectStore.On("Init", r.config).Return(errors.Errorf("init error")).Once()
|
||||
err = r.reinitialize(objectStore)
|
||||
assert.EqualError(t, err, "init error")
|
||||
|
||||
objectStore.On("Init", r.config).Return(nil)
|
||||
err = r.reinitialize(objectStore)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestRestartableObjectStoreGetDelegate(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// Reset error
|
||||
p.On("resetIfNeeded").Return(errors.Errorf("reset error")).Once()
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindObjectStore, name: name}
|
||||
r := &restartableObjectStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
a, err := r.getDelegate()
|
||||
assert.Nil(t, a)
|
||||
assert.EqualError(t, err, "reset error")
|
||||
|
||||
// Happy path
|
||||
p.On("resetIfNeeded").Return(nil)
|
||||
objectStore := new(test.ObjectStore)
|
||||
objectStore.Test(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
p.On("getByKindAndName", key).Return(objectStore, nil)
|
||||
|
||||
a, err = r.getDelegate()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, objectStore, a)
|
||||
}
|
||||
|
||||
func TestRestartableObjectStoreInit(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
p.Test(t)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// getObjectStore error
|
||||
name := "aws"
|
||||
key := kindAndName{kind: PluginKindObjectStore, name: name}
|
||||
r := &restartableObjectStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
p.On("getByKindAndName", key).Return(nil, errors.Errorf("getByKindAndName error")).Once()
|
||||
|
||||
config := map[string]string{
|
||||
"color": "blue",
|
||||
}
|
||||
err := r.Init(config)
|
||||
assert.EqualError(t, err, "getByKindAndName error")
|
||||
|
||||
// Delegate returns error
|
||||
objectStore := new(test.ObjectStore)
|
||||
objectStore.Test(t)
|
||||
defer objectStore.AssertExpectations(t)
|
||||
p.On("getByKindAndName", key).Return(objectStore, nil)
|
||||
objectStore.On("Init", config).Return(errors.Errorf("Init error")).Once()
|
||||
|
||||
err = r.Init(config)
|
||||
assert.EqualError(t, err, "Init error")
|
||||
|
||||
// wipe this out because the previous failed Init call set it
|
||||
r.config = nil
|
||||
|
||||
// Happy path
|
||||
objectStore.On("Init", config).Return(nil)
|
||||
err = r.Init(config)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config, r.config)
|
||||
|
||||
// Calling Init twice is forbidden
|
||||
err = r.Init(config)
|
||||
assert.EqualError(t, err, "already initialized")
|
||||
}
|
||||
|
||||
func TestRestartableObjectStoreDelegatedFunctions(t *testing.T) {
|
||||
runRestartableDelegateTests(
|
||||
t,
|
||||
PluginKindObjectStore,
|
||||
func(key kindAndName, p RestartableProcess) interface{} {
|
||||
return &restartableObjectStore{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
},
|
||||
func() mockable {
|
||||
return new(test.ObjectStore)
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "PutObject",
|
||||
inputs: []interface{}{"bucket", "key", strings.NewReader("body")},
|
||||
expectedErrorOutputs: []interface{}{errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "GetObject",
|
||||
inputs: []interface{}{"bucket", "key"},
|
||||
expectedErrorOutputs: []interface{}{nil, errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{ioutil.NopCloser(strings.NewReader("object")), errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "ListCommonPrefixes",
|
||||
inputs: []interface{}{"bucket", "delimeter"},
|
||||
expectedErrorOutputs: []interface{}{([]string)(nil), errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{[]string{"a", "b"}, errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "ListObjects",
|
||||
inputs: []interface{}{"bucket", "prefix"},
|
||||
expectedErrorOutputs: []interface{}{([]string)(nil), errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{[]string{"a", "b"}, errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "DeleteObject",
|
||||
inputs: []interface{}{"bucket", "key"},
|
||||
expectedErrorOutputs: []interface{}{errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "CreateSignedURL",
|
||||
inputs: []interface{}{"bucket", "key", 30 * time.Minute},
|
||||
expectedErrorOutputs: []interface{}{"", errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{"signedURL", errors.Errorf("delegate error")},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type RestartableProcessFactory interface {
|
||||
newRestartableProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (RestartableProcess, error)
|
||||
}
|
||||
|
||||
type restartableProcessFactory struct {
|
||||
}
|
||||
|
||||
func newRestartableProcessFactory() RestartableProcessFactory {
|
||||
return &restartableProcessFactory{}
|
||||
}
|
||||
|
||||
func (rpf *restartableProcessFactory) newRestartableProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (RestartableProcess, error) {
|
||||
return newRestartableProcess(command, logger, logLevel)
|
||||
}
|
||||
|
||||
type RestartableProcess interface {
|
||||
addReinitializer(key kindAndName, r reinitializer)
|
||||
reset() error
|
||||
resetIfNeeded() error
|
||||
getByKindAndName(key kindAndName) (interface{}, error)
|
||||
stop()
|
||||
}
|
||||
|
||||
// restartableProcess encapsulates the lifecycle for all plugins contained in a single executable file. It is able
|
||||
// to restart a plugin process if it is terminated for any reason. If this happens, all plugins are reinitialized using
|
||||
// the original configuration data.
|
||||
type restartableProcess struct {
|
||||
command string
|
||||
logger logrus.FieldLogger
|
||||
logLevel logrus.Level
|
||||
|
||||
// lock guards all of the fields below
|
||||
lock sync.RWMutex
|
||||
process Process
|
||||
plugins map[kindAndName]interface{}
|
||||
reinitializers map[kindAndName]reinitializer
|
||||
resetFailures int
|
||||
}
|
||||
|
||||
// reinitializer is capable of reinitializing a restartable plugin instance using the newly dispensed plugin.
|
||||
type reinitializer interface {
|
||||
// reinitialize reinitializes a restartable plugin instance using the newly dispensed plugin.
|
||||
reinitialize(dispensed interface{}) error
|
||||
}
|
||||
|
||||
// newRestartableProcess creates a new restartableProcess for the given command and options.
|
||||
func newRestartableProcess(command string, logger logrus.FieldLogger, logLevel logrus.Level) (RestartableProcess, error) {
|
||||
p := &restartableProcess{
|
||||
command: command,
|
||||
logger: logger,
|
||||
logLevel: logLevel,
|
||||
plugins: make(map[kindAndName]interface{}),
|
||||
reinitializers: make(map[kindAndName]reinitializer),
|
||||
}
|
||||
|
||||
// This launches the process
|
||||
err := p.reset()
|
||||
|
||||
return p, err
|
||||
}
|
||||
|
||||
// addReinitializer registers the reinitializer r for key.
|
||||
func (p *restartableProcess) addReinitializer(key kindAndName, r reinitializer) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.reinitializers[key] = r
|
||||
}
|
||||
|
||||
// reset acquires the lock and calls resetLH.
|
||||
func (p *restartableProcess) reset() error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return p.resetLH()
|
||||
}
|
||||
|
||||
// resetLH (re)launches the plugin process. It redispenses all previously dispensed plugins and reinitializes all the
|
||||
// registered reinitializers using the newly dispensed plugins.
|
||||
//
|
||||
// Callers of resetLH *must* acquire the lock before calling it.
|
||||
func (p *restartableProcess) resetLH() error {
|
||||
if p.resetFailures > 10 {
|
||||
return errors.Errorf("unable to restart plugin process: execeeded maximum number of reset failures")
|
||||
}
|
||||
|
||||
process, err := newProcess(p.command, p.logger, p.logLevel)
|
||||
if err != nil {
|
||||
p.resetFailures++
|
||||
return err
|
||||
}
|
||||
p.process = process
|
||||
|
||||
// Redispense any previously dispensed plugins, reinitializing if necessary.
|
||||
// Start by creating a new map to hold the newly dispensed plugins.
|
||||
newPlugins := make(map[kindAndName]interface{})
|
||||
for key := range p.plugins {
|
||||
// Re-dispense
|
||||
dispensed, err := p.process.dispense(key)
|
||||
if err != nil {
|
||||
p.resetFailures++
|
||||
return err
|
||||
}
|
||||
// Store in the new map
|
||||
newPlugins[key] = dispensed
|
||||
|
||||
// Reinitialize
|
||||
if r, found := p.reinitializers[key]; found {
|
||||
if err := r.reinitialize(dispensed); err != nil {
|
||||
p.resetFailures++
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we update p's plugins!
|
||||
p.plugins = newPlugins
|
||||
|
||||
p.resetFailures = 0
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// resetIfNeeded checks if the plugin process has exited and resets p if it has.
|
||||
func (p *restartableProcess) resetIfNeeded() error {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
if p.process.exited() {
|
||||
p.logger.Info("Plugin process exited - restarting.")
|
||||
return p.resetLH()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getByKindAndName acquires the lock and calls getByKindAndNameLH.
|
||||
func (p *restartableProcess) getByKindAndName(key kindAndName) (interface{}, error) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
return p.getByKindAndNameLH(key)
|
||||
}
|
||||
|
||||
// getByKindAndNameLH returns the dispensed plugin for key. If the plugin hasn't been dispensed before, it dispenses a
|
||||
// new one.
|
||||
func (p *restartableProcess) getByKindAndNameLH(key kindAndName) (interface{}, error) {
|
||||
dispensed, found := p.plugins[key]
|
||||
if found {
|
||||
return dispensed, nil
|
||||
}
|
||||
|
||||
dispensed, err := p.process.dispense(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.plugins[key] = dispensed
|
||||
return p.plugins[key], nil
|
||||
}
|
||||
|
||||
// stop terminates the plugin process.
|
||||
func (p *restartableProcess) stop() {
|
||||
p.lock.Lock()
|
||||
p.process.kill()
|
||||
p.lock.Unlock()
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
api "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// restartableRestoreItemAction is a restore item action for a given implementation (such as "pod"). It is associated with
|
||||
// a restartableProcess, which may be shared and used to run multiple plugins. At the beginning of each method
|
||||
// call, the restartableRestoreItemAction asks its restartableProcess to restart itself if needed (e.g. if the
|
||||
// process terminated for any reason), then it proceeds with the actual call.
|
||||
type restartableRestoreItemAction struct {
|
||||
key kindAndName
|
||||
sharedPluginProcess RestartableProcess
|
||||
config map[string]string
|
||||
}
|
||||
|
||||
// newRestartableRestoreItemAction returns a new restartableRestoreItemAction.
|
||||
func newRestartableRestoreItemAction(name string, sharedPluginProcess RestartableProcess) *restartableRestoreItemAction {
|
||||
r := &restartableRestoreItemAction{
|
||||
key: kindAndName{kind: PluginKindRestoreItemAction, name: name},
|
||||
sharedPluginProcess: sharedPluginProcess,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// getRestoreItemAction returns the restore item action for this restartableRestoreItemAction. It does *not* restart the
|
||||
// plugin process.
|
||||
func (r *restartableRestoreItemAction) getRestoreItemAction() (restore.ItemAction, error) {
|
||||
plugin, err := r.sharedPluginProcess.getByKindAndName(r.key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
restoreItemAction, ok := plugin.(restore.ItemAction)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a restore.ItemAction!", plugin)
|
||||
}
|
||||
|
||||
return restoreItemAction, nil
|
||||
}
|
||||
|
||||
// getDelegate restarts the plugin process (if needed) and returns the restore item action for this restartableRestoreItemAction.
|
||||
func (r *restartableRestoreItemAction) getDelegate() (restore.ItemAction, error) {
|
||||
if err := r.sharedPluginProcess.resetIfNeeded(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.getRestoreItemAction()
|
||||
}
|
||||
|
||||
// AppliesTo restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableRestoreItemAction) AppliesTo() (restore.ResourceSelector, error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return restore.ResourceSelector{}, err
|
||||
}
|
||||
|
||||
return delegate.AppliesTo()
|
||||
}
|
||||
|
||||
// Execute restarts the plugin's process if needed, then delegates the call.
|
||||
func (r *restartableRestoreItemAction) Execute(obj runtime.Unstructured, restore *api.Restore) (res runtime.Unstructured, warning error, err error) {
|
||||
delegate, err := r.getDelegate()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return delegate.Execute(obj, restore)
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
"github.com/heptio/ark/pkg/restore"
|
||||
"github.com/heptio/ark/pkg/restore/mocks"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestRestartableGetRestoreItemAction(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
plugin interface{}
|
||||
getError error
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "error getting by kind and name",
|
||||
getError: errors.Errorf("get error"),
|
||||
expectedError: "get error",
|
||||
},
|
||||
{
|
||||
name: "wrong type",
|
||||
plugin: 3,
|
||||
expectedError: "int is not a restore.ItemAction!",
|
||||
},
|
||||
{
|
||||
name: "happy path",
|
||||
plugin: new(mocks.ItemAction),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
name := "pod"
|
||||
key := kindAndName{kind: PluginKindRestoreItemAction, name: name}
|
||||
p.On("getByKindAndName", key).Return(tc.plugin, tc.getError)
|
||||
|
||||
r := newRestartableRestoreItemAction(name, p)
|
||||
a, err := r.getRestoreItemAction()
|
||||
if tc.expectedError != "" {
|
||||
assert.EqualError(t, err, tc.expectedError)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tc.plugin, a)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartableRestoreItemActionGetDelegate(t *testing.T) {
|
||||
p := new(mockRestartableProcess)
|
||||
defer p.AssertExpectations(t)
|
||||
|
||||
// Reset error
|
||||
p.On("resetIfNeeded").Return(errors.Errorf("reset error")).Once()
|
||||
name := "pod"
|
||||
r := newRestartableRestoreItemAction(name, p)
|
||||
a, err := r.getDelegate()
|
||||
assert.Nil(t, a)
|
||||
assert.EqualError(t, err, "reset error")
|
||||
|
||||
// Happy path
|
||||
p.On("resetIfNeeded").Return(nil)
|
||||
expected := new(mocks.ItemAction)
|
||||
key := kindAndName{kind: PluginKindRestoreItemAction, name: name}
|
||||
p.On("getByKindAndName", key).Return(expected, nil)
|
||||
|
||||
a, err = r.getDelegate()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, a)
|
||||
}
|
||||
|
||||
func TestRestartableRestoreItemActionDelegatedFunctions(t *testing.T) {
|
||||
r := new(v1.Restore)
|
||||
|
||||
pv := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"color": "blue",
|
||||
},
|
||||
}
|
||||
|
||||
pvToReturn := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"color": "green",
|
||||
},
|
||||
}
|
||||
|
||||
runRestartableDelegateTests(
|
||||
t,
|
||||
PluginKindRestoreItemAction,
|
||||
func(key kindAndName, p RestartableProcess) interface{} {
|
||||
return &restartableRestoreItemAction{
|
||||
key: key,
|
||||
sharedPluginProcess: p,
|
||||
}
|
||||
},
|
||||
func() mockable {
|
||||
return new(mocks.ItemAction)
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "AppliesTo",
|
||||
inputs: []interface{}{},
|
||||
expectedErrorOutputs: []interface{}{restore.ResourceSelector{}, errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{restore.ResourceSelector{IncludedNamespaces: []string{"a"}}, errors.Errorf("delegate error")},
|
||||
},
|
||||
restartableDelegateTest{
|
||||
function: "Execute",
|
||||
inputs: []interface{}{pv, r},
|
||||
expectedErrorOutputs: []interface{}{nil, nil, errors.Errorf("reset error")},
|
||||
expectedDelegateOutputs: []interface{}{pvToReturn, errors.Errorf("delegate warning"), errors.Errorf("delegate error")},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"github.com/hashicorp/go-plugin"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -38,41 +37,41 @@ import (
|
||||
// interface.
|
||||
type RestoreItemActionPlugin struct {
|
||||
plugin.NetRPCUnsupportedPlugin
|
||||
impl restore.ItemAction
|
||||
log *logrusAdapter
|
||||
*pluginBase
|
||||
}
|
||||
|
||||
// NewRestoreItemActionPlugin constructs a RestoreItemActionPlugin.
|
||||
func NewRestoreItemActionPlugin(itemAction restore.ItemAction) *RestoreItemActionPlugin {
|
||||
func NewRestoreItemActionPlugin(options ...pluginOption) *RestoreItemActionPlugin {
|
||||
return &RestoreItemActionPlugin{
|
||||
impl: itemAction,
|
||||
pluginBase: newPluginBase(options...),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *RestoreItemActionPlugin) Kind() PluginKind {
|
||||
return PluginKindRestoreItemAction
|
||||
}
|
||||
|
||||
// GRPCServer registers a RestoreItemAction gRPC server.
|
||||
func (p *RestoreItemActionPlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterRestoreItemActionServer(s, &RestoreItemActionGRPCServer{impl: p.impl})
|
||||
return nil
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// client code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCClient returns a RestoreItemAction gRPC client.
|
||||
func (p *RestoreItemActionPlugin) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
|
||||
return &RestoreItemActionGRPCClient{grpcClient: proto.NewRestoreItemActionClient(c), log: p.log}, nil
|
||||
return newClientDispenser(p.clientLogger, c, newRestoreItemActionGRPCClient), nil
|
||||
}
|
||||
|
||||
// RestoreItemActionGRPCClient implements the backup/ItemAction interface and uses a
|
||||
// gRPC client to make calls to the plugin server.
|
||||
type RestoreItemActionGRPCClient struct {
|
||||
*clientBase
|
||||
grpcClient proto.RestoreItemActionClient
|
||||
log *logrusAdapter
|
||||
}
|
||||
|
||||
func newRestoreItemActionGRPCClient(base *clientBase, clientConn *grpc.ClientConn) interface{} {
|
||||
return &RestoreItemActionGRPCClient{
|
||||
clientBase: base,
|
||||
grpcClient: proto.NewRestoreItemActionClient(clientConn),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RestoreItemActionGRPCClient) AppliesTo() (restore.ResourceSelector, error) {
|
||||
res, err := c.grpcClient.AppliesTo(context.Background(), &proto.Empty{})
|
||||
res, err := c.grpcClient.AppliesTo(context.Background(), &proto.AppliesToRequest{Plugin: c.plugin})
|
||||
if err != nil {
|
||||
return restore.ResourceSelector{}, err
|
||||
}
|
||||
@@ -98,6 +97,7 @@ func (c *RestoreItemActionGRPCClient) Execute(item runtime.Unstructured, restore
|
||||
}
|
||||
|
||||
req := &proto.RestoreExecuteRequest{
|
||||
Plugin: c.plugin,
|
||||
Item: itemJSON,
|
||||
Restore: restoreJSON,
|
||||
}
|
||||
@@ -120,18 +120,43 @@ func (c *RestoreItemActionGRPCClient) Execute(item runtime.Unstructured, restore
|
||||
return &updatedItem, warning, nil
|
||||
}
|
||||
|
||||
func (c *RestoreItemActionGRPCClient) SetLog(log logrus.FieldLogger) {
|
||||
c.log.impl = log
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// server code
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// GRPCServer registers a RestoreItemAction gRPC server.
|
||||
func (p *RestoreItemActionPlugin) GRPCServer(s *grpc.Server) error {
|
||||
proto.RegisterRestoreItemActionServer(s, &RestoreItemActionGRPCServer{mux: p.serverMux})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreItemActionGRPCServer implements the proto-generated RestoreItemActionServer interface, and accepts
|
||||
// gRPC calls and forwards them to an implementation of the pluggable interface.
|
||||
type RestoreItemActionGRPCServer struct {
|
||||
impl restore.ItemAction
|
||||
mux *serverMux
|
||||
}
|
||||
|
||||
func (s *RestoreItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.Empty) (*proto.AppliesToResponse, error) {
|
||||
appliesTo, err := s.impl.AppliesTo()
|
||||
func (s *RestoreItemActionGRPCServer) getImpl(name string) (restore.ItemAction, error) {
|
||||
impl, err := s.mux.getHandler(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
itemAction, ok := impl.(restore.ItemAction)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("%T is not a restore item action", impl)
|
||||
}
|
||||
|
||||
return itemAction, nil
|
||||
}
|
||||
|
||||
func (s *RestoreItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.AppliesToRequest) (*proto.AppliesToResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
appliesTo, err := impl.AppliesTo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -146,6 +171,11 @@ func (s *RestoreItemActionGRPCServer) AppliesTo(ctx context.Context, req *proto.
|
||||
}
|
||||
|
||||
func (s *RestoreItemActionGRPCServer) Execute(ctx context.Context, req *proto.RestoreExecuteRequest) (*proto.RestoreExecuteResponse, error) {
|
||||
impl, err := s.getImpl(req.Plugin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
item unstructured.Unstructured
|
||||
restore api.Restore
|
||||
@@ -159,7 +189,7 @@ func (s *RestoreItemActionGRPCServer) Execute(ctx context.Context, req *proto.Re
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, warning, err := s.impl.Execute(&item, &restore)
|
||||
res, warning, err := impl.Execute(&item, &restore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright 2017 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 plugin
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
plugin "github.com/hashicorp/go-plugin"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Handshake is configuration information that allows go-plugin clients and servers to perform a handshake.
|
||||
//
|
||||
// TODO(ncdc): this should probably be a function so it can't be mutated, and we should probably move it to
|
||||
// handshake.go.
|
||||
var Handshake = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "ARK_PLUGIN",
|
||||
MagicCookieValue: "hello",
|
||||
}
|
||||
|
||||
// Server serves registered plugin implementations.
|
||||
type Server interface {
|
||||
// RegisterBackupItemAction registers a backup item action.
|
||||
RegisterBackupItemAction(name string, initializer HandlerInitializer) Server
|
||||
|
||||
// RegisterBackupItemActions registers multiple backup item actions.
|
||||
RegisterBackupItemActions(map[string]HandlerInitializer) Server
|
||||
|
||||
// RegisterBlockStore registers a block store.
|
||||
RegisterBlockStore(name string, initializer HandlerInitializer) Server
|
||||
|
||||
// RegisterBlockStores registers multiple block stores.
|
||||
RegisterBlockStores(map[string]HandlerInitializer) Server
|
||||
|
||||
// RegisterObjectStore registers an object store.
|
||||
RegisterObjectStore(name string, initializer HandlerInitializer) Server
|
||||
|
||||
// RegisterObjectStores registers multiple object stores.
|
||||
RegisterObjectStores(map[string]HandlerInitializer) Server
|
||||
|
||||
// RegisterRestoreItemAction registers a restore item action.
|
||||
RegisterRestoreItemAction(name string, initializer HandlerInitializer) Server
|
||||
|
||||
// RegisterRestoreItemActions registers multiple restore item actions.
|
||||
RegisterRestoreItemActions(map[string]HandlerInitializer) Server
|
||||
|
||||
// Server runs the plugin server.
|
||||
Serve()
|
||||
}
|
||||
|
||||
// server implements Server.
|
||||
type server struct {
|
||||
backupItemAction *BackupItemActionPlugin
|
||||
blockStore *BlockStorePlugin
|
||||
objectStore *ObjectStorePlugin
|
||||
restoreItemAction *RestoreItemActionPlugin
|
||||
}
|
||||
|
||||
// NewServer returns a new Server
|
||||
func NewServer(log logrus.FieldLogger) Server {
|
||||
return &server{
|
||||
backupItemAction: NewBackupItemActionPlugin(serverLogger(log)),
|
||||
blockStore: NewBlockStorePlugin(serverLogger(log)),
|
||||
objectStore: NewObjectStorePlugin(serverLogger(log)),
|
||||
restoreItemAction: NewRestoreItemActionPlugin(serverLogger(log)),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) RegisterBackupItemAction(name string, initializer HandlerInitializer) Server {
|
||||
s.backupItemAction.register(name, initializer)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterBackupItemActions(m map[string]HandlerInitializer) Server {
|
||||
for name := range m {
|
||||
s.RegisterBackupItemAction(name, m[name])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterBlockStore(name string, initializer HandlerInitializer) Server {
|
||||
s.blockStore.register(name, initializer)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterBlockStores(m map[string]HandlerInitializer) Server {
|
||||
for name := range m {
|
||||
s.RegisterBlockStore(name, m[name])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterObjectStore(name string, initializer HandlerInitializer) Server {
|
||||
s.objectStore.register(name, initializer)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterObjectStores(m map[string]HandlerInitializer) Server {
|
||||
for name := range m {
|
||||
s.RegisterObjectStore(name, m[name])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterRestoreItemAction(name string, initializer HandlerInitializer) Server {
|
||||
s.restoreItemAction.register(name, initializer)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *server) RegisterRestoreItemActions(m map[string]HandlerInitializer) Server {
|
||||
for name := range m {
|
||||
s.RegisterRestoreItemAction(name, m[name])
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// getNames returns a list of PluginIdentifiers registered with plugin.
|
||||
func getNames(command string, kind PluginKind, plugin Interface) []PluginIdentifier {
|
||||
var pluginIdentifiers []PluginIdentifier
|
||||
|
||||
for _, name := range plugin.names() {
|
||||
id := PluginIdentifier{Command: command, Kind: kind, Name: name}
|
||||
pluginIdentifiers = append(pluginIdentifiers, id)
|
||||
}
|
||||
|
||||
return pluginIdentifiers
|
||||
}
|
||||
|
||||
func (s *server) Serve() {
|
||||
command := os.Args[0]
|
||||
|
||||
var pluginIdentifiers []PluginIdentifier
|
||||
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindBackupItemAction, s.backupItemAction)...)
|
||||
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindBlockStore, s.blockStore)...)
|
||||
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindObjectStore, s.objectStore)...)
|
||||
pluginIdentifiers = append(pluginIdentifiers, getNames(command, PluginKindRestoreItemAction, s.restoreItemAction)...)
|
||||
|
||||
pluginLister := NewPluginLister(pluginIdentifiers...)
|
||||
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: Handshake,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
string(PluginKindBackupItemAction): s.backupItemAction,
|
||||
string(PluginKindBlockStore): s.blockStore,
|
||||
string(PluginKindObjectStore): s.objectStore,
|
||||
string(PluginKindPluginLister): NewPluginListerPlugin(pluginLister),
|
||||
string(PluginKindRestoreItemAction): s.restoreItemAction,
|
||||
},
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 plugin
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// HandlerInitializer is a function that initializes and returns a new instance of one of Ark's plugin interfaces
|
||||
// (ObjectStore, BlockStore, BackupItemAction, RestoreItemAction).
|
||||
type HandlerInitializer func(logger logrus.FieldLogger) (interface{}, error)
|
||||
|
||||
// serverMux manages multiple implementations of a single plugin kind, such as pod and pvc BackupItemActions.
|
||||
type serverMux struct {
|
||||
kind PluginKind
|
||||
initializers map[string]HandlerInitializer
|
||||
handlers map[string]interface{}
|
||||
serverLog logrus.FieldLogger
|
||||
}
|
||||
|
||||
// newServerMux returns a new serverMux.
|
||||
func newServerMux(logger logrus.FieldLogger) *serverMux {
|
||||
return &serverMux{
|
||||
initializers: make(map[string]HandlerInitializer),
|
||||
handlers: make(map[string]interface{}),
|
||||
serverLog: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// register registers the initializer for name.
|
||||
func (m *serverMux) register(name string, f HandlerInitializer) {
|
||||
// TODO(ncdc): return an error on duplicate registrations for the same name.
|
||||
m.initializers[name] = f
|
||||
}
|
||||
|
||||
// names returns a list of all registered implementations.
|
||||
func (m *serverMux) names() []string {
|
||||
return sets.StringKeySet(m.initializers).List()
|
||||
}
|
||||
|
||||
// getHandler returns the instance for a plugin with the given name. If an instance has already been initialized,
|
||||
// that is returned. Otherwise, the instance is initialized by calling its initialization function.
|
||||
func (m *serverMux) getHandler(name string) (interface{}, error) {
|
||||
if instance, found := m.handlers[name]; found {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
initializer, found := m.initializers[name]
|
||||
if !found {
|
||||
return nil, errors.Errorf("unknown %v plugin: %s", m.kind, name)
|
||||
}
|
||||
|
||||
instance, err := initializer(m.serverLog)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.handlers[name] = instance
|
||||
|
||||
return m.handlers[name], nil
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 plugin
|
||||
|
||||
import plugin "github.com/hashicorp/go-plugin"
|
||||
|
||||
// Handshake is configuration information that allows go-plugin
|
||||
// clients and servers to perform a handshake.
|
||||
var Handshake = plugin.HandshakeConfig{
|
||||
ProtocolVersion: 1,
|
||||
MagicCookieKey: "ARK_PLUGIN",
|
||||
MagicCookieValue: "hello",
|
||||
}
|
||||
|
||||
// Serve serves the plugin p.
|
||||
func Serve(p Interface) {
|
||||
plugin.Serve(&plugin.ServeConfig{
|
||||
HandshakeConfig: Handshake,
|
||||
Plugins: map[string]plugin.Plugin{
|
||||
string(p.Kind()): p,
|
||||
},
|
||||
GRPCServer: plugin.DefaultGRPCServer,
|
||||
})
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 restic
|
||||
|
||||
import "sync"
|
||||
|
||||
@@ -30,9 +30,7 @@ type jobAction struct {
|
||||
}
|
||||
|
||||
func NewJobAction(logger logrus.FieldLogger) ItemAction {
|
||||
return &jobAction{
|
||||
logger: logger,
|
||||
}
|
||||
return &jobAction{logger: logger}
|
||||
}
|
||||
|
||||
func (a *jobAction) AppliesTo() (ResourceSelector, error) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
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 mockery v1.0.0. DO NOT EDIT.
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import restore "github.com/heptio/ark/pkg/restore"
|
||||
import runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
import v1 "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
|
||||
// ItemAction is an autogenerated mock type for the ItemAction type
|
||||
type ItemAction struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// AppliesTo provides a mock function with given fields:
|
||||
func (_m *ItemAction) AppliesTo() (restore.ResourceSelector, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 restore.ResourceSelector
|
||||
if rf, ok := ret.Get(0).(func() restore.ResourceSelector); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(restore.ResourceSelector)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Execute provides a mock function with given fields: obj, _a1
|
||||
func (_m *ItemAction) Execute(obj runtime.Unstructured, _a1 *v1.Restore) (runtime.Unstructured, error, error) {
|
||||
ret := _m.Called(obj, _a1)
|
||||
|
||||
var r0 runtime.Unstructured
|
||||
if rf, ok := ret.Get(0).(func(runtime.Unstructured, *v1.Restore) runtime.Unstructured); ok {
|
||||
r0 = rf(obj, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(runtime.Unstructured)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(runtime.Unstructured, *v1.Restore) error); ok {
|
||||
r1 = rf(obj, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
var r2 error
|
||||
if rf, ok := ret.Get(2).(func(runtime.Unstructured, *v1.Restore) error); ok {
|
||||
r2 = rf(obj, _a1)
|
||||
} else {
|
||||
r2 = ret.Error(2)
|
||||
}
|
||||
|
||||
return r0, r1, r2
|
||||
}
|
||||
@@ -32,9 +32,7 @@ type podAction struct {
|
||||
}
|
||||
|
||||
func NewPodAction(logger logrus.FieldLogger) ItemAction {
|
||||
return &podAction{
|
||||
logger: logger,
|
||||
}
|
||||
return &podAction{logger: logger}
|
||||
}
|
||||
|
||||
func (a *podAction) AppliesTo() (ResourceSelector, error) {
|
||||
|
||||
+2
-18
@@ -57,14 +57,13 @@ import (
|
||||
"github.com/heptio/ark/pkg/util/collections"
|
||||
"github.com/heptio/ark/pkg/util/filesystem"
|
||||
"github.com/heptio/ark/pkg/util/kube"
|
||||
"github.com/heptio/ark/pkg/util/logging"
|
||||
arksync "github.com/heptio/ark/pkg/util/sync"
|
||||
)
|
||||
|
||||
// Restorer knows how to restore a backup.
|
||||
type Restorer interface {
|
||||
// Restore restores the backup data from backupReader, returning warnings and errors.
|
||||
Restore(restore *api.Restore, backup *api.Backup, backupReader io.Reader, logFile io.Writer, actions []ItemAction) (api.RestoreResult, api.RestoreResult)
|
||||
Restore(log logrus.FieldLogger, restore *api.Restore, backup *api.Backup, backupReader io.Reader, actions []ItemAction) (api.RestoreResult, api.RestoreResult)
|
||||
}
|
||||
|
||||
type gvString string
|
||||
@@ -74,7 +73,6 @@ type kindString string
|
||||
type kubernetesRestorer struct {
|
||||
discoveryHelper discovery.Helper
|
||||
dynamicFactory client.DynamicFactory
|
||||
backupService cloudprovider.BackupService
|
||||
snapshotService cloudprovider.SnapshotService
|
||||
backupClient arkv1client.BackupsGetter
|
||||
namespaceClient corev1.NamespaceInterface
|
||||
@@ -147,7 +145,6 @@ func prioritizeResources(helper discovery.Helper, priorities []string, includedR
|
||||
func NewKubernetesRestorer(
|
||||
discoveryHelper discovery.Helper,
|
||||
dynamicFactory client.DynamicFactory,
|
||||
backupService cloudprovider.BackupService,
|
||||
snapshotService cloudprovider.SnapshotService,
|
||||
resourcePriorities []string,
|
||||
backupClient arkv1client.BackupsGetter,
|
||||
@@ -159,7 +156,6 @@ func NewKubernetesRestorer(
|
||||
return &kubernetesRestorer{
|
||||
discoveryHelper: discoveryHelper,
|
||||
dynamicFactory: dynamicFactory,
|
||||
backupService: backupService,
|
||||
snapshotService: snapshotService,
|
||||
backupClient: backupClient,
|
||||
namespaceClient: namespaceClient,
|
||||
@@ -175,7 +171,7 @@ func NewKubernetesRestorer(
|
||||
// Restore executes a restore into the target Kubernetes cluster according to the restore spec
|
||||
// and using data from the provided backup/backup reader. Returns a warnings and errors RestoreResult,
|
||||
// respectively, summarizing info about the restore.
|
||||
func (kr *kubernetesRestorer) Restore(restore *api.Restore, backup *api.Backup, backupReader io.Reader, logFile io.Writer, actions []ItemAction) (api.RestoreResult, api.RestoreResult) {
|
||||
func (kr *kubernetesRestorer) Restore(log logrus.FieldLogger, restore *api.Restore, backup *api.Backup, backupReader io.Reader, actions []ItemAction) (api.RestoreResult, api.RestoreResult) {
|
||||
// metav1.LabelSelectorAsSelector converts a nil LabelSelector to a
|
||||
// Nothing Selector, i.e. a selector that matches nothing. We want
|
||||
// a selector that matches everything. This can be accomplished by
|
||||
@@ -190,14 +186,6 @@ func (kr *kubernetesRestorer) Restore(restore *api.Restore, backup *api.Backup,
|
||||
return api.RestoreResult{}, api.RestoreResult{Ark: []string{err.Error()}}
|
||||
}
|
||||
|
||||
gzippedLog := gzip.NewWriter(logFile)
|
||||
defer gzippedLog.Close()
|
||||
|
||||
log := logrus.New()
|
||||
log.Out = gzippedLog
|
||||
log.Hooks.Add(&logging.ErrorLocationHook{})
|
||||
log.Hooks.Add(&logging.LogLocationHook{})
|
||||
|
||||
// get resource includes-excludes
|
||||
resourceIncludesExcludes := getResourceIncludesExcludes(kr.discoveryHelper, restore.Spec.IncludedResources, restore.Spec.ExcludedResources)
|
||||
prioritizedResources, err := prioritizeResources(kr.discoveryHelper, kr.resourcePriorities, resourceIncludesExcludes, log)
|
||||
@@ -742,10 +730,6 @@ func (ctx *context) restoreResource(resource, namespace, resourcePath string) (a
|
||||
|
||||
ctx.infof("Executing item action for %v", &groupResource)
|
||||
|
||||
if logSetter, ok := action.ItemAction.(logging.LogSetter); ok {
|
||||
logSetter.SetLog(ctx.logger)
|
||||
}
|
||||
|
||||
updatedObj, warning, err := action.Execute(obj, ctx.restore)
|
||||
if warning != nil {
|
||||
addToResult(&warnings, namespace, fmt.Errorf("warning preparing %s: %v", fullPath, warning))
|
||||
|
||||
@@ -29,10 +29,8 @@ type serviceAction struct {
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
func NewServiceAction(log logrus.FieldLogger) ItemAction {
|
||||
return &serviceAction{
|
||||
log: log,
|
||||
}
|
||||
func NewServiceAction(logger logrus.FieldLogger) ItemAction {
|
||||
return &serviceAction{log: logger}
|
||||
}
|
||||
|
||||
func (a *serviceAction) AppliesTo() (ResourceSelector, error) {
|
||||
|
||||
@@ -33,6 +33,7 @@ type Interface interface {
|
||||
ReadFile(filename string) ([]byte, error)
|
||||
DirExists(path string) (bool, error)
|
||||
TempFile(dir, prefix string) (NameWriteCloser, error)
|
||||
Stat(path string) (os.FileInfo, error)
|
||||
}
|
||||
|
||||
type NameWriteCloser interface {
|
||||
@@ -85,3 +86,7 @@ func (fs *osFileSystem) DirExists(path string) (bool, error) {
|
||||
func (fs *osFileSystem) TempFile(dir, prefix string) (NameWriteCloser, error) {
|
||||
return ioutil.TempFile(dir, prefix)
|
||||
}
|
||||
|
||||
func (fs *osFileSystem) Stat(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 logging
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package logging
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
// LogSetter is an interface for a type that allows a FieldLogger
|
||||
// to be set on it.
|
||||
type LogSetter interface {
|
||||
SetLog(logrus.FieldLogger)
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 mockery v1.0.0
|
||||
package test
|
||||
|
||||
import io "io"
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
import time "time"
|
||||
import v1 "github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
|
||||
// BackupService is an autogenerated mock type for the BackupService type
|
||||
type BackupService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CreateSignedURL provides a mock function with given fields: target, bucket, ttl
|
||||
func (_m *BackupService) CreateSignedURL(target v1.DownloadTarget, bucket, directory string, ttl time.Duration) (string, error) {
|
||||
ret := _m.Called(target, bucket, directory, ttl)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(v1.DownloadTarget, string, string, time.Duration) string); ok {
|
||||
r0 = rf(target, bucket, directory, ttl)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(v1.DownloadTarget, string, string, time.Duration) error); ok {
|
||||
r1 = rf(target, bucket, directory, ttl)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteBackupDir provides a mock function with given fields: bucket, backupName
|
||||
func (_m *BackupService) DeleteBackupDir(bucket string, backupName string) error {
|
||||
ret := _m.Called(bucket, backupName)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string) error); ok {
|
||||
r0 = rf(bucket, backupName)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DownloadBackup provides a mock function with given fields: bucket, name
|
||||
func (_m *BackupService) DownloadBackup(bucket string, name string) (io.ReadCloser, error) {
|
||||
ret := _m.Called(bucket, name)
|
||||
|
||||
var r0 io.ReadCloser
|
||||
if rf, ok := ret.Get(0).(func(string, string) io.ReadCloser); ok {
|
||||
r0 = rf(bucket, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(io.ReadCloser)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(bucket, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetAllBackups provides a mock function with given fields: bucket
|
||||
func (_m *BackupService) GetAllBackups(bucket string) ([]*v1.Backup, error) {
|
||||
ret := _m.Called(bucket)
|
||||
|
||||
var r0 []*v1.Backup
|
||||
if rf, ok := ret.Get(0).(func(string) []*v1.Backup); ok {
|
||||
r0 = rf(bucket)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*v1.Backup)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(bucket)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetBackup provides a mock function with given fields: bucket, name
|
||||
func (_m *BackupService) GetBackup(bucket string, name string) (*v1.Backup, error) {
|
||||
ret := _m.Called(bucket, name)
|
||||
|
||||
var r0 *v1.Backup
|
||||
if rf, ok := ret.Get(0).(func(string, string) *v1.Backup); ok {
|
||||
r0 = rf(bucket, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*v1.Backup)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, string) error); ok {
|
||||
r1 = rf(bucket, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UploadBackup provides a mock function with given fields: bucket, name, metadata, backup, log
|
||||
func (_m *BackupService) UploadBackup(bucket string, name string, metadata io.Reader, backup io.Reader, log io.Reader) error {
|
||||
ret := _m.Called(bucket, name, metadata, backup, log)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, io.Reader, io.Reader, io.Reader) error); ok {
|
||||
r0 = rf(bucket, name, metadata, backup, log)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UploadRestoreLog provides a mock function with given fields: bucket, backup, restore, log
|
||||
func (_m *BackupService) UploadRestoreLog(bucket string, backup string, restore string, log io.Reader) error {
|
||||
ret := _m.Called(bucket, backup, restore, log)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, io.Reader) error); ok {
|
||||
r0 = rf(bucket, backup, restore, log)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UploadRestoreResults provides a mock function with given fields: bucket, backup, restore, results
|
||||
func (_m *BackupService) UploadRestoreResults(bucket string, backup string, restore string, results io.Reader) error {
|
||||
ret := _m.Called(bucket, backup, restore, results)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string, io.Reader) error); ok {
|
||||
r0 = rf(bucket, backup, restore, results)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 test
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/heptio/ark/pkg/apis/ark/v1"
|
||||
)
|
||||
|
||||
type FakeBackupService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (f *FakeBackupService) GetAllBackups(bucket string) ([]*v1.Backup, error) {
|
||||
args := f.Called(bucket)
|
||||
|
||||
var backups []*v1.Backup
|
||||
|
||||
b := args.Get(0)
|
||||
if b != nil {
|
||||
backups = b.([]*v1.Backup)
|
||||
}
|
||||
|
||||
return backups, args.Error(1)
|
||||
}
|
||||
|
||||
func (f *FakeBackupService) UploadBackup(bucket, name string, metadata, backup io.Reader) error {
|
||||
args := f.Called(bucket, name, metadata, backup)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (f *FakeBackupService) DownloadBackup(bucket, name string) (io.ReadCloser, error) {
|
||||
args := f.Called(bucket, name)
|
||||
return args.Get(0).(io.ReadCloser), args.Error(1)
|
||||
}
|
||||
|
||||
func (f *FakeBackupService) DeleteBackup(bucket, backupName string) error {
|
||||
args := f.Called(bucket, backupName)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (f *FakeBackupService) GetBackup(bucket, name string) (*v1.Backup, error) {
|
||||
var (
|
||||
args = f.Called(bucket, name)
|
||||
b = args.Get(0)
|
||||
backup *v1.Backup
|
||||
)
|
||||
|
||||
if b != nil {
|
||||
backup = b.(*v1.Backup)
|
||||
}
|
||||
|
||||
return backup, args.Error(1)
|
||||
}
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 test
|
||||
|
||||
import (
|
||||
@@ -50,6 +65,10 @@ func (fs *FakeFileSystem) DirExists(path string) (bool, error) {
|
||||
return afero.DirExists(fs.fs, path)
|
||||
}
|
||||
|
||||
func (fs *FakeFileSystem) Stat(path string) (os.FileInfo, error) {
|
||||
return fs.fs.Stat(path)
|
||||
}
|
||||
|
||||
func (fs *FakeFileSystem) WithFile(path string, data []byte) *FakeFileSystem {
|
||||
file, _ := fs.fs.Create(path)
|
||||
file.Write(data)
|
||||
@@ -58,6 +77,14 @@ func (fs *FakeFileSystem) WithFile(path string, data []byte) *FakeFileSystem {
|
||||
return fs
|
||||
}
|
||||
|
||||
func (fs *FakeFileSystem) WithFileAndMode(path string, data []byte, mode os.FileMode) *FakeFileSystem {
|
||||
file, _ := fs.fs.OpenFile(path, os.O_CREATE|os.O_RDWR, mode)
|
||||
file.Write(data)
|
||||
file.Close()
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
func (fs *FakeFileSystem) WithDirectory(path string) *FakeFileSystem {
|
||||
fs.fs.MkdirAll(path, 0755)
|
||||
return fs
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
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 test
|
||||
|
||||
import (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user