2019-04-03 09:46:21 +01:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package uplink
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
2019-06-26 17:22:01 +01:00
|
|
|
"github.com/skyrings/skyring-common/tools/uuid"
|
2019-04-03 09:46:21 +01:00
|
|
|
"github.com/vivint/infectious"
|
|
|
|
|
|
|
|
"storj.io/storj/internal/memory"
|
|
|
|
"storj.io/storj/pkg/eestream"
|
|
|
|
"storj.io/storj/pkg/encryption"
|
|
|
|
"storj.io/storj/pkg/metainfo/kvmetainfo"
|
|
|
|
ecclient "storj.io/storj/pkg/storage/ec"
|
|
|
|
"storj.io/storj/pkg/storage/segments"
|
|
|
|
"storj.io/storj/pkg/storage/streams"
|
|
|
|
"storj.io/storj/pkg/storj"
|
|
|
|
"storj.io/storj/pkg/transport"
|
|
|
|
"storj.io/storj/uplink/metainfo"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Project represents a specific project access session.
|
|
|
|
type Project struct {
|
2019-04-10 23:27:04 +01:00
|
|
|
uplinkCfg *Config
|
2019-04-03 09:46:21 +01:00
|
|
|
tc transport.Client
|
2019-06-25 16:36:23 +01:00
|
|
|
metainfo *metainfo.Client
|
2019-04-03 09:46:21 +01:00
|
|
|
project *kvmetainfo.Project
|
|
|
|
maxInlineSize memory.Size
|
|
|
|
}
|
|
|
|
|
|
|
|
// BucketConfig holds information about a bucket's configuration. This is
|
|
|
|
// filled in by the caller for use with CreateBucket(), or filled in by the
|
|
|
|
// library as Bucket.Config when a bucket is returned from OpenBucket().
|
|
|
|
type BucketConfig struct {
|
|
|
|
// PathCipher indicates which cipher suite is to be used for path
|
|
|
|
// encryption within the new Bucket. If not set, AES-GCM encryption
|
|
|
|
// will be used.
|
|
|
|
PathCipher storj.CipherSuite
|
|
|
|
|
|
|
|
// EncryptionParameters specifies the default encryption parameters to
|
|
|
|
// be used for data encryption of new Objects in this bucket.
|
|
|
|
EncryptionParameters storj.EncryptionParameters
|
|
|
|
|
|
|
|
// Volatile groups config values that are likely to change semantics
|
|
|
|
// or go away entirely between releases. Be careful when using them!
|
|
|
|
Volatile struct {
|
|
|
|
// RedundancyScheme defines the default Reed-Solomon and/or
|
|
|
|
// Forward Error Correction encoding parameters to be used by
|
|
|
|
// objects in this Bucket.
|
|
|
|
RedundancyScheme storj.RedundancyScheme
|
2019-04-10 23:27:04 +01:00
|
|
|
// SegmentsSize is the default segment size to use for new
|
2019-04-03 09:46:21 +01:00
|
|
|
// objects in this Bucket.
|
2019-04-10 23:27:04 +01:00
|
|
|
SegmentsSize memory.Size
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-26 18:15:41 +01:00
|
|
|
func (cfg *BucketConfig) clone() *BucketConfig {
|
|
|
|
clone := *cfg
|
|
|
|
return &clone
|
|
|
|
}
|
|
|
|
|
2019-06-13 16:09:05 +01:00
|
|
|
// TODO: is this the best way to do this?
|
2019-04-26 18:15:41 +01:00
|
|
|
func (cfg *BucketConfig) setDefaults() {
|
|
|
|
if cfg.PathCipher == storj.EncUnspecified {
|
|
|
|
cfg.PathCipher = defaultCipher
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.EncryptionParameters.CipherSuite == storj.EncUnspecified {
|
|
|
|
cfg.EncryptionParameters.CipherSuite = defaultCipher
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.Volatile.RedundancyScheme.RequiredShares == 0 {
|
|
|
|
cfg.Volatile.RedundancyScheme.RequiredShares = 29
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.Volatile.RedundancyScheme.RepairShares == 0 {
|
|
|
|
cfg.Volatile.RedundancyScheme.RepairShares = 35
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.Volatile.RedundancyScheme.OptimalShares == 0 {
|
|
|
|
cfg.Volatile.RedundancyScheme.OptimalShares = 80
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.Volatile.RedundancyScheme.TotalShares == 0 {
|
|
|
|
cfg.Volatile.RedundancyScheme.TotalShares = 95
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.Volatile.RedundancyScheme.ShareSize == 0 {
|
|
|
|
cfg.Volatile.RedundancyScheme.ShareSize = (1 * memory.KiB).Int32()
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
2019-06-20 22:50:13 +01:00
|
|
|
if cfg.EncryptionParameters.BlockSize == 0 {
|
|
|
|
cfg.EncryptionParameters.BlockSize = cfg.Volatile.RedundancyScheme.ShareSize * int32(cfg.Volatile.RedundancyScheme.RequiredShares)
|
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
if cfg.Volatile.SegmentsSize.Int() == 0 {
|
|
|
|
cfg.Volatile.SegmentsSize = 64 * memory.MiB
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateBucket creates a new bucket if authorized.
|
2019-06-06 19:55:10 +01:00
|
|
|
func (p *Project) CreateBucket(ctx context.Context, name string, cfg *BucketConfig) (bucket storj.Bucket, err error) {
|
2019-04-03 09:46:21 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
if cfg == nil {
|
|
|
|
cfg = &BucketConfig{}
|
|
|
|
}
|
2019-04-26 18:15:41 +01:00
|
|
|
cfg = cfg.clone()
|
2019-04-03 09:46:21 +01:00
|
|
|
cfg.setDefaults()
|
2019-06-06 19:55:10 +01:00
|
|
|
|
|
|
|
bucket = storj.Bucket{
|
2019-04-10 23:27:04 +01:00
|
|
|
PathCipher: cfg.PathCipher.ToCipher(),
|
|
|
|
EncryptionParameters: cfg.EncryptionParameters,
|
|
|
|
RedundancyScheme: cfg.Volatile.RedundancyScheme,
|
|
|
|
SegmentsSize: cfg.Volatile.SegmentsSize.Int64(),
|
|
|
|
}
|
2019-06-06 19:55:10 +01:00
|
|
|
return p.project.CreateBucket(ctx, name, &bucket)
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// DeleteBucket deletes a bucket if authorized. If the bucket contains any
|
|
|
|
// Objects at the time of deletion, they may be lost permanently.
|
|
|
|
func (p *Project) DeleteBucket(ctx context.Context, bucket string) (err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
return p.project.DeleteBucket(ctx, bucket)
|
|
|
|
}
|
|
|
|
|
|
|
|
// BucketListOptions controls options to the ListBuckets() call.
|
|
|
|
type BucketListOptions = storj.BucketListOptions
|
|
|
|
|
|
|
|
// ListBuckets will list authorized buckets.
|
|
|
|
func (p *Project) ListBuckets(ctx context.Context, opts *BucketListOptions) (bl storj.BucketList, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
if opts == nil {
|
2019-06-13 16:09:05 +01:00
|
|
|
opts = &BucketListOptions{Direction: storj.Forward}
|
2019-04-03 09:46:21 +01:00
|
|
|
}
|
|
|
|
return p.project.ListBuckets(ctx, *opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetBucketInfo returns info about the requested bucket if authorized.
|
|
|
|
func (p *Project) GetBucketInfo(ctx context.Context, bucket string) (b storj.Bucket, bi *BucketConfig, err error) {
|
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
b, err = p.project.GetBucket(ctx, bucket)
|
|
|
|
if err != nil {
|
|
|
|
return b, nil, err
|
|
|
|
}
|
2019-04-10 23:27:04 +01:00
|
|
|
cfg := &BucketConfig{
|
|
|
|
PathCipher: b.PathCipher.ToCipherSuite(),
|
|
|
|
EncryptionParameters: b.EncryptionParameters,
|
|
|
|
}
|
|
|
|
cfg.Volatile.RedundancyScheme = b.RedundancyScheme
|
|
|
|
cfg.Volatile.SegmentsSize = memory.Size(b.SegmentsSize)
|
2019-04-03 09:46:21 +01:00
|
|
|
return b, cfg, nil
|
|
|
|
}
|
|
|
|
|
2019-06-25 16:36:23 +01:00
|
|
|
// TODO: move the bucket related OpenBucket to bucket.go
|
|
|
|
|
2019-04-03 09:46:21 +01:00
|
|
|
// OpenBucket returns a Bucket handle with the given EncryptionAccess
|
|
|
|
// information.
|
2019-04-10 23:27:04 +01:00
|
|
|
func (p *Project) OpenBucket(ctx context.Context, bucketName string, access *EncryptionAccess) (b *Bucket, err error) {
|
2019-04-03 09:46:21 +01:00
|
|
|
defer mon.Task()(&ctx)(&err)
|
|
|
|
|
2019-06-26 17:22:01 +01:00
|
|
|
err = p.CheckBucketAttribution(ctx, bucketName)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-10 23:27:04 +01:00
|
|
|
bucketInfo, cfg, err := p.GetBucketInfo(ctx, bucketName)
|
2019-04-03 09:46:21 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if access == nil || access.Key == (storj.Key{}) {
|
|
|
|
return nil, Error.New("No encryption key chosen")
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
encryptionScheme := cfg.EncryptionParameters.ToEncryptionScheme()
|
|
|
|
|
2019-04-10 23:27:04 +01:00
|
|
|
ec := ecclient.NewClient(p.tc, p.uplinkCfg.Volatile.MaxMemory.Int())
|
2019-04-03 09:46:21 +01:00
|
|
|
fc, err := infectious.NewFEC(int(cfg.Volatile.RedundancyScheme.RequiredShares), int(cfg.Volatile.RedundancyScheme.TotalShares))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
rs, err := eestream.NewRedundancyStrategy(
|
|
|
|
eestream.NewRSScheme(fc, int(cfg.Volatile.RedundancyScheme.ShareSize)),
|
|
|
|
int(cfg.Volatile.RedundancyScheme.RepairShares),
|
|
|
|
int(cfg.Volatile.RedundancyScheme.OptimalShares))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-04-10 23:27:04 +01:00
|
|
|
maxEncryptedSegmentSize, err := encryption.CalcEncryptedSize(cfg.Volatile.SegmentsSize.Int64(),
|
2019-04-03 09:46:21 +01:00
|
|
|
cfg.EncryptionParameters.ToEncryptionScheme())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-04-10 23:27:04 +01:00
|
|
|
segmentStore := segments.NewSegmentStore(p.metainfo, ec, rs, p.maxInlineSize.Int(), maxEncryptedSegmentSize)
|
2019-04-03 09:46:21 +01:00
|
|
|
|
2019-06-24 20:23:07 +01:00
|
|
|
// TODO(jeff): this is where we would load scope information in.
|
|
|
|
encStore := encryption.NewStore()
|
|
|
|
encStore.SetDefaultKey(&access.Key)
|
|
|
|
|
|
|
|
streamStore, err := streams.NewStreamStore(segmentStore, cfg.Volatile.SegmentsSize.Int64(), encStore, int(encryptionScheme.BlockSize), encryptionScheme.Cipher, p.maxInlineSize.Int())
|
2019-04-03 09:46:21 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Bucket{
|
2019-04-10 23:27:04 +01:00
|
|
|
BucketConfig: *cfg,
|
|
|
|
Name: bucketInfo.Name,
|
|
|
|
Created: bucketInfo.Created,
|
|
|
|
bucket: bucketInfo,
|
2019-06-24 20:23:07 +01:00
|
|
|
metainfo: kvmetainfo.New(p.project, p.metainfo, streamStore, segmentStore, encStore),
|
2019-04-10 23:27:04 +01:00
|
|
|
streams: streamStore,
|
2019-04-03 09:46:21 +01:00
|
|
|
}, nil
|
|
|
|
}
|
2019-06-26 17:22:01 +01:00
|
|
|
|
|
|
|
// CheckBucketAttribution Checks the bucket attribution
|
|
|
|
func (p *Project) CheckBucketAttribution(ctx context.Context, bucketName string) error {
|
|
|
|
if p.uplinkCfg.Volatile.PartnerID == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
partnerID, err := uuid.Parse(p.uplinkCfg.Volatile.PartnerID)
|
|
|
|
if err != nil {
|
|
|
|
return Error.Wrap(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return p.metainfo.SetAttribution(ctx, bucketName, *partnerID)
|
|
|
|
}
|