Add storage bucket service

The storage bucket service wraps another bucket service, and invokes
actions on a storage engine based upon the actions taken upon buckets.

Currently, the storage bucket service will delete bucket data from the
storage engine when the bucket is deleted via the bucket service.
pull/10616/head
Edd Robinson 2019-01-09 13:35:25 +00:00
parent 5a12a3a72e
commit e3ae256782
3 changed files with 153 additions and 9 deletions

90
storage/bucket_service.go Normal file
View File

@ -0,0 +1,90 @@
package storage
import (
"context"
"errors"
"github.com/influxdata/platform"
)
// BucketDeleter defines the behaviour of deleting a bucket.
type BucketDeleter interface {
DeleteBucket(platform.ID, platform.ID) error
}
// BucketService wraps an existing platform.BucketService implementation.
//
// BucketService ensures that when a bucket is deleted, all stored data
// associated with the bucket is either removed, or marked to be removed via a
// future compaction.
type BucketService struct {
inner platform.BucketService
engine BucketDeleter
}
// NewBucketService returns a new BucketService for the provided BucketDeleter,
// which typically will be an Engine.
func NewBucketService(s platform.BucketService, engine BucketDeleter) *BucketService {
return &BucketService{
inner: s,
engine: engine,
}
}
// FindBucketByID returns a single bucket by ID.
func (s *BucketService) FindBucketByID(ctx context.Context, id platform.ID) (*platform.Bucket, error) {
if s.inner == nil || s.engine == nil {
return nil, errors.New("nil inner BucketService or Engine")
}
return s.inner.FindBucketByID(ctx, id)
}
// FindBucket returns the first bucket that matches filter.
func (s *BucketService) FindBucket(ctx context.Context, filter platform.BucketFilter) (*platform.Bucket, error) {
if s.inner == nil || s.engine == nil {
return nil, errors.New("nil inner BucketService or Engine")
}
return s.inner.FindBucket(ctx, filter)
}
// FindBuckets returns a list of buckets that match filter and the total count of matching buckets.
// Additional options provide pagination & sorting.
func (s *BucketService) FindBuckets(ctx context.Context, filter platform.BucketFilter, opt ...platform.FindOptions) ([]*platform.Bucket, int, error) {
if s.inner == nil || s.engine == nil {
return nil, 0, errors.New("nil inner BucketService or Engine")
}
return s.inner.FindBuckets(ctx, filter, opt...)
}
// CreateBucket creates a new bucket and sets b.ID with the new identifier.
func (s *BucketService) CreateBucket(ctx context.Context, b *platform.Bucket) error {
if s.inner == nil || s.engine == nil {
return errors.New("nil inner BucketService or Engine")
}
return s.inner.CreateBucket(ctx, b)
}
// UpdateBucket updates a single bucket with changeset.
// Returns the new bucket state after update.
func (s *BucketService) UpdateBucket(ctx context.Context, id platform.ID, upd platform.BucketUpdate) (*platform.Bucket, error) {
if s.inner == nil || s.engine == nil {
return nil, errors.New("nil inner BucketService or Engine")
}
return s.inner.UpdateBucket(ctx, id, upd)
}
// DeleteBucket removes a bucket by ID.
func (s *BucketService) DeleteBucket(ctx context.Context, bucketID platform.ID) error {
bucket, err := s.FindBucketByID(ctx, bucketID)
if err != nil {
return err
}
// The data is dropped first from the storage engine. If this fails for any
// reason, then the bucket will still be available in the future to retrieve
// the orgID, which is needed for the engine.
if err := s.engine.DeleteBucket(bucket.OrganizationID, bucketID); err != nil {
return err
}
return s.inner.DeleteBucket(ctx, bucketID)
}

View File

@ -0,0 +1,63 @@
package storage_test
import (
"context"
"testing"
"github.com/influxdata/platform"
"github.com/influxdata/platform/inmem"
"github.com/influxdata/platform/storage"
)
func TestBucketService(t *testing.T) {
service := storage.NewBucketService(nil, nil)
i, err := platform.IDFromString("2222222222222222")
if err != nil {
panic(err)
}
if err := service.DeleteBucket(context.TODO(), *i); err == nil {
t.Fatal("expected error, got nil")
}
inmemService := inmem.NewService()
service = storage.NewBucketService(inmemService, nil)
if err := service.DeleteBucket(context.TODO(), *i); err == nil {
t.Fatal("expected error, got nil")
}
org := &platform.Organization{}
if err := inmemService.CreateOrganization(context.TODO(), org); err != nil {
panic(err)
}
bucket := &platform.Bucket{OrganizationID: org.ID}
if err := inmemService.CreateBucket(context.TODO(), bucket); err != nil {
panic(err)
}
// Test deleting a bucket calls into the deleter.
deleter := &MockDeleter{}
service = storage.NewBucketService(inmemService, deleter)
if err := service.DeleteBucket(context.TODO(), bucket.ID); err != nil {
t.Fatal(err)
}
if deleter.orgID != org.ID {
t.Errorf("got org ID: %s, expected %s", deleter.orgID, org.ID)
} else if deleter.bucketID != bucket.ID {
t.Errorf("got bucket ID: %s, expected %s", deleter.bucketID, bucket.ID)
}
}
type MockDeleter struct {
orgID, bucketID platform.ID
}
func (m *MockDeleter) DeleteBucket(orgID, bucketID platform.ID) error {
m.orgID, m.bucketID = orgID, bucketID
return nil
}

View File

@ -192,15 +192,6 @@ func (s *retentionEnforcer) PrometheusCollectors() []prometheus.Collector {
return s.metrics.PrometheusCollectors()
}
// A BucketService is an platform.BucketService that the retentionEnforcer can open,
// close and log.
type BucketService interface {
platform.BucketService
Open() error
Close() error
WithLogger(l *zap.Logger)
}
type seriesIteratorAdapter struct {
itr SeriesCursor
ea seriesElemAdapter