@ -17,55 +17,28 @@
package donut
package donut
import (
import (
"bytes"
"encoding/base64"
"encoding/base64"
"encoding/hex"
"encoding/hex"
"io"
"io"
"os"
"os"
"path/filepath"
"path/filepath"
"runtime/debug"
"sort"
"sort"
"strconv"
"strconv"
"strings"
"strings"
"sync"
"sync"
"time"
"io/ioutil"
"io/ioutil"
"github.com/minio/minio/pkg/iodine"
"github.com/minio/minio/pkg/iodine"
"github.com/minio/minio/pkg/storage/donut"
"github.com/minio/minio/pkg/storage/donut"
"github.com/minio/minio/pkg/storage/drivers"
"github.com/minio/minio/pkg/storage/drivers"
"github.com/minio/minio/pkg/storage/trove"
"github.com/minio/minio/pkg/utils/log"
)
type storedBucket struct {
bucketMetadata drivers . BucketMetadata
objectMetadata map [ string ] drivers . ObjectMetadata
partMetadata map [ string ] drivers . PartMetadata
multiPartSession map [ string ] multiPartSession
}
type multiPartSession struct {
totalParts int
uploadID string
initiated time . Time
}
const (
totalBuckets = 100
)
)
// donutDriver - creates a new single disk drivers driver using donut
// donutDriver - creates a new single disk drivers driver using donut
type donutDriver struct {
type donutDriver struct {
donut donut . Donut
donut donut . Donut
paths [ ] string
paths [ ] string
lock * sync . RWMutex
lock * sync . RWMutex
storedBuckets map [ string ] storedBucket
objects * trove . Cache
multiPartObjects * trove . Cache
maxSize uint64
expiration time . Duration
}
}
// This is a dummy nodeDiskMap which is going to be deprecated soon
// This is a dummy nodeDiskMap which is going to be deprecated soon
@ -101,83 +74,18 @@ func createNodeDiskMap(paths []string) map[string][]string {
return nodes
return nodes
}
}
func initialize ( d * donutDriver ) error {
// Soon to be user configurable, when Management API is available
// we should remove "default" to something which is passed down
// from configuration paramters
var err error
d . donut , err = donut . NewDonut ( "default" , createNodeDiskMap ( d . paths ) )
if err != nil {
return iodine . New ( err , nil )
}
buckets , err := d . donut . ListBuckets ( )
if err != nil {
return iodine . New ( err , nil )
}
for bucketName , metadata := range buckets {
d . lock . RLock ( )
storedBucket := d . storedBuckets [ bucketName ]
d . lock . RUnlock ( )
if len ( storedBucket . multiPartSession ) == 0 {
storedBucket . multiPartSession = make ( map [ string ] multiPartSession )
}
if len ( storedBucket . objectMetadata ) == 0 {
storedBucket . objectMetadata = make ( map [ string ] drivers . ObjectMetadata )
}
if len ( storedBucket . partMetadata ) == 0 {
storedBucket . partMetadata = make ( map [ string ] drivers . PartMetadata )
}
storedBucket . bucketMetadata = drivers . BucketMetadata {
Name : metadata . Name ,
Created : metadata . Created ,
ACL : drivers . BucketACL ( metadata . ACL ) ,
}
d . lock . Lock ( )
d . storedBuckets [ bucketName ] = storedBucket
d . lock . Unlock ( )
}
return nil
}
// NewDriver instantiate a donut driver
// NewDriver instantiate a donut driver
func NewDriver ( paths [ ] string , maxSize uint64 , expiration time . Duration ) ( drivers . Driver , error ) {
func NewDriver ( paths [ ] string ) ( drivers . Driver , error ) {
driver := new ( donutDriver )
driver := new ( donutDriver )
driver . storedBuckets = make ( map [ string ] storedBucket )
driver . objects = trove . NewCache ( maxSize , expiration )
driver . maxSize = maxSize
driver . expiration = expiration
driver . multiPartObjects = trove . NewCache ( 0 , time . Duration ( 0 ) )
driver . lock = new ( sync . RWMutex )
driver . objects . OnExpired = driver . expiredObject
driver . multiPartObjects . OnExpired = driver . expiredPart
// set up memory expiration
driver . objects . ExpireObjects ( time . Second * 5 )
driver . paths = paths
driver . paths = paths
driver . lock = new ( sync . RWMutex )
driver . lock = new ( sync . RWMutex )
err := initialize ( driver )
// Soon to be user configurable, when Management API is available
return driver , err
// we should remove "default" to something which is passed down
}
// from configuration paramters
var err error
func ( d donutDriver ) expiredObject ( a ... interface { } ) {
driver . donut , err = donut . NewDonut ( "default" , createNodeDiskMap ( driver . paths ) )
cacheStats := d . objects . Stats ( )
return driver , iodine . New ( err , nil )
log . Printf ( "CurrentSize: %d, CurrentItems: %d, TotalExpirations: %d" ,
cacheStats . Bytes , cacheStats . Items , cacheStats . Expired )
key := a [ 0 ] . ( string )
// loop through all buckets
for bucket , storedBucket := range d . storedBuckets {
delete ( storedBucket . objectMetadata , key )
// remove bucket if no objects found anymore
if len ( storedBucket . objectMetadata ) == 0 {
if time . Since ( d . storedBuckets [ bucket ] . bucketMetadata . Created ) > d . expiration {
delete ( d . storedBuckets , bucket )
}
}
}
go debug . FreeOSMemory ( )
}
}
// byBucketName is a type for sorting bucket metadata by bucket name
// byBucketName is a type for sorting bucket metadata by bucket name
@ -192,8 +100,17 @@ func (d donutDriver) ListBuckets() (results []drivers.BucketMetadata, err error)
if d . donut == nil {
if d . donut == nil {
return nil , iodine . New ( drivers . InternalError { } , nil )
return nil , iodine . New ( drivers . InternalError { } , nil )
}
}
for _ , storedBucket := range d . storedBuckets {
buckets , err := d . donut . ListBuckets ( )
results = append ( results , storedBucket . bucketMetadata )
if err != nil {
return nil , iodine . New ( err , nil )
}
for _ , metadata := range buckets {
result := drivers . BucketMetadata {
Name : metadata . Name ,
Created : metadata . Created ,
ACL : drivers . BucketACL ( metadata . ACL ) ,
}
results = append ( results , result )
}
}
sort . Sort ( byBucketName ( results ) )
sort . Sort ( byBucketName ( results ) )
return results , nil
return results , nil
@ -203,9 +120,6 @@ func (d donutDriver) ListBuckets() (results []drivers.BucketMetadata, err error)
func ( d donutDriver ) CreateBucket ( bucketName , acl string ) error {
func ( d donutDriver ) CreateBucket ( bucketName , acl string ) error {
d . lock . Lock ( )
d . lock . Lock ( )
defer d . lock . Unlock ( )
defer d . lock . Unlock ( )
if len ( d . storedBuckets ) == totalBuckets {
return iodine . New ( drivers . TooManyBuckets { Bucket : bucketName } , nil )
}
if d . donut == nil {
if d . donut == nil {
return iodine . New ( drivers . InternalError { } , nil )
return iodine . New ( drivers . InternalError { } , nil )
}
}
@ -223,20 +137,6 @@ func (d donutDriver) CreateBucket(bucketName, acl string) error {
}
}
return iodine . New ( err , nil )
return iodine . New ( err , nil )
}
}
var newBucket = storedBucket { }
newBucket . objectMetadata = make ( map [ string ] drivers . ObjectMetadata )
newBucket . multiPartSession = make ( map [ string ] multiPartSession )
newBucket . partMetadata = make ( map [ string ] drivers . PartMetadata )
metadata , err := d . donut . GetBucketMetadata ( bucketName )
if err != nil {
return iodine . New ( err , nil )
}
newBucket . bucketMetadata = drivers . BucketMetadata {
Name : metadata . Name ,
Created : metadata . Created ,
ACL : drivers . BucketACL ( metadata . ACL ) ,
}
d . storedBuckets [ bucketName ] = newBucket
return nil
return nil
}
}
return iodine . New ( drivers . BucketNameInvalid { Bucket : bucketName } , nil )
return iodine . New ( drivers . BucketNameInvalid { Bucket : bucketName } , nil )
@ -252,9 +152,6 @@ func (d donutDriver) GetBucketMetadata(bucketName string) (drivers.BucketMetadat
if ! drivers . IsValidBucket ( bucketName ) {
if ! drivers . IsValidBucket ( bucketName ) {
return drivers . BucketMetadata { } , drivers . BucketNameInvalid { Bucket : bucketName }
return drivers . BucketMetadata { } , drivers . BucketNameInvalid { Bucket : bucketName }
}
}
if d . storedBuckets [ bucketName ] . bucketMetadata . Name != "" {
return d . storedBuckets [ bucketName ] . bucketMetadata , nil
}
metadata , err := d . donut . GetBucketMetadata ( bucketName )
metadata , err := d . donut . GetBucketMetadata ( bucketName )
if err != nil {
if err != nil {
return drivers . BucketMetadata { } , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
return drivers . BucketMetadata { } , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
@ -286,9 +183,6 @@ func (d donutDriver) SetBucketMetadata(bucketName, acl string) error {
if err != nil {
if err != nil {
return iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
return iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
}
}
storedBucket := d . storedBuckets [ bucketName ]
storedBucket . bucketMetadata . ACL = drivers . BucketACL ( acl )
d . storedBuckets [ bucketName ] = storedBucket
return nil
return nil
}
}
@ -303,41 +197,23 @@ func (d donutDriver) GetObject(w io.Writer, bucketName, objectName string) (int6
if ! drivers . IsValidObjectName ( objectName ) {
if ! drivers . IsValidObjectName ( objectName ) {
return 0 , iodine . New ( drivers . ObjectNameInvalid { Object : objectName } , nil )
return 0 , iodine . New ( drivers . ObjectNameInvalid { Object : objectName } , nil )
}
}
if _ , ok := d . storedBuckets [ bucketName ] ; ok == false {
return 0 , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
}
d . lock . RLock ( )
d . lock . RLock ( )
defer d . lock . RUnlock ( )
defer d . lock . RUnlock ( )
objectKey := bucketName + "/" + objectName
reader , size , err := d . donut . GetObject ( bucketName , objectName )
data , ok := d . objects . Get ( objectKey )
if err != nil {
if ! ok {
switch iodine . ToError ( err ) . ( type ) {
reader , size , err := d . donut . GetObject ( bucketName , objectName )
case donut . BucketNotFound :
if err != nil {
return 0 , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
switch iodine . ToError ( err ) . ( type ) {
case donut . ObjectNotFound :
case donut . BucketNotFound :
return 0 , iodine . New ( drivers . ObjectNotFound {
return 0 , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
Bucket : bucketName ,
case donut . ObjectNotFound :
Object : objectName ,
return 0 , iodine . New ( drivers . ObjectNotFound {
} , nil )
Bucket : bucketName ,
default :
Object : objectName ,
return 0 , iodine . New ( drivers . InternalError { } , nil )
} , nil )
default :
return 0 , iodine . New ( drivers . InternalError { } , nil )
}
}
pw := newProxyWriter ( w )
n , err := io . CopyN ( pw , reader , size )
if err != nil {
return 0 , iodine . New ( err , nil )
}
}
// Save in memory for future reads
}
d . objects . Set ( objectKey , pw . writtenBytes )
written , err := io . CopyN ( w , reader , size )
// free up
pw . writtenBytes = nil
go debug . FreeOSMemory ( )
return n , nil
}
written , err := io . Copy ( w , bytes . NewBuffer ( data ) )
if err != nil {
if err != nil {
return 0 , iodine . New ( err , nil )
return 0 , iodine . New ( err , nil )
}
}
@ -369,45 +245,36 @@ func (d donutDriver) GetPartialObject(w io.Writer, bucketName, objectName string
Length : length ,
Length : length ,
} , errParams )
} , errParams )
}
}
if _ , ok := d . storedBuckets [ bucketName ] ; ok == false {
reader , size , err := d . donut . GetObject ( bucketName , objectName )
return 0 , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
if err != nil {
}
switch iodine . ToError ( err ) . ( type ) {
objectKey := bucketName + "/" + objectName
case donut . BucketNotFound :
data , ok := d . objects . Get ( objectKey )
return 0 , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
if ! ok {
case donut . ObjectNotFound :
reader , size , err := d . donut . GetObject ( bucketName , objectName )
return 0 , iodine . New ( drivers . ObjectNotFound {
if err != nil {
Bucket : bucketName ,
switch iodine . ToError ( err ) . ( type ) {
Object : objectName ,
case donut . BucketNotFound :
} , nil )
return 0 , iodine . New ( drivers . BucketNotFound { Bucket : bucketName } , nil )
default :
case donut . ObjectNotFound :
return 0 , iodine . New ( drivers . InternalError { } , nil )
return 0 , iodine . New ( drivers . ObjectNotFound {
Bucket : bucketName ,
Object : objectName ,
} , nil )
default :
return 0 , iodine . New ( drivers . InternalError { } , nil )
}
}
defer reader . Close ( )
if start > size || ( start + length - 1 ) > size {
return 0 , iodine . New ( drivers . InvalidRange {
Start : start ,
Length : length ,
} , errParams )
}
_ , err = io . CopyN ( ioutil . Discard , reader , start )
if err != nil {
return 0 , iodine . New ( err , errParams )
}
n , err := io . CopyN ( w , reader , length )
if err != nil {
return 0 , iodine . New ( err , errParams )
}
}
return n , nil
}
}
written , err := io . CopyN ( w , bytes . NewBuffer ( data [ start : ] ) , length )
defer reader . Close ( )
return written , iodine . New ( err , nil )
if start > size || ( start + length - 1 ) > size {
return 0 , iodine . New ( drivers . InvalidRange {
Start : start ,
Length : length ,
} , errParams )
}
_ , err = io . CopyN ( ioutil . Discard , reader , start )
if err != nil {
return 0 , iodine . New ( err , errParams )
}
n , err := io . CopyN ( w , reader , length )
if err != nil {
return 0 , iodine . New ( err , errParams )
}
return n , nil
}
}
// GetObjectMetadata retrieves an object's metadata
// GetObjectMetadata retrieves an object's metadata
@ -428,13 +295,6 @@ func (d donutDriver) GetObjectMetadata(bucketName, objectName string) (drivers.O
if ! drivers . IsValidObjectName ( objectName ) {
if ! drivers . IsValidObjectName ( objectName ) {
return drivers . ObjectMetadata { } , iodine . New ( drivers . ObjectNameInvalid { Object : objectName } , errParams )
return drivers . ObjectMetadata { } , iodine . New ( drivers . ObjectNameInvalid { Object : objectName } , errParams )
}
}
if _ , ok := d . storedBuckets [ bucketName ] ; ok {
storedBucket := d . storedBuckets [ bucketName ]
objectKey := bucketName + "/" + objectName
if object , ok := storedBucket . objectMetadata [ objectKey ] ; ok {
return object , nil
}
}
metadata , err := d . donut . GetObjectMetadata ( bucketName , objectName )
metadata , err := d . donut . GetObjectMetadata ( bucketName , objectName )
if err != nil {
if err != nil {
return drivers . ObjectMetadata { } , iodine . New ( drivers . ObjectNotFound {
return drivers . ObjectMetadata { } , iodine . New ( drivers . ObjectNotFound {
@ -498,24 +358,6 @@ func (d donutDriver) ListObjects(bucketName string, resources drivers.BucketReso
return results , resources , nil
return results , resources , nil
}
}
type proxyWriter struct {
writer io . Writer
writtenBytes [ ] byte
}
func ( r * proxyWriter ) Write ( p [ ] byte ) ( n int , err error ) {
n , err = r . writer . Write ( p )
if err != nil {
return
}
r . writtenBytes = append ( r . writtenBytes , p [ 0 : n ] ... )
return
}
func newProxyWriter ( w io . Writer ) * proxyWriter {
return & proxyWriter { writer : w , writtenBytes : nil }
}
// CreateObject creates a new object
// CreateObject creates a new object
func ( d donutDriver ) CreateObject ( bucketName , objectName , contentType , expectedMD5Sum string , size int64 , reader io . Reader ) ( string , error ) {
func ( d donutDriver ) CreateObject ( bucketName , objectName , contentType , expectedMD5Sum string , size int64 , reader io . Reader ) ( string , error ) {
d . lock . Lock ( )
d . lock . Lock ( )
@ -528,27 +370,12 @@ func (d donutDriver) CreateObject(bucketName, objectName, contentType, expectedM
if d . donut == nil {
if d . donut == nil {
return "" , iodine . New ( drivers . InternalError { } , errParams )
return "" , iodine . New ( drivers . InternalError { } , errParams )
}
}
// TODO - Should be able to write bigger than cache
if size > int64 ( d . maxSize ) {
generic := drivers . GenericObjectError { Bucket : bucketName , Object : objectName }
return "" , iodine . New ( drivers . EntityTooLarge {
GenericObjectError : generic ,
Size : strconv . FormatInt ( size , 10 ) ,
MaxSize : strconv . FormatUint ( d . maxSize , 10 ) ,
} , nil )
}
if ! drivers . IsValidBucket ( bucketName ) {
if ! drivers . IsValidBucket ( bucketName ) {
return "" , iodine . New ( drivers . BucketNameInvalid { Bucket : bucketName } , nil )
return "" , iodine . New ( drivers . BucketNameInvalid { Bucket : bucketName } , nil )
}
}
if ! drivers . IsValidObjectName ( objectName ) {
if ! drivers . IsValidObjectName ( objectName ) {
return "" , iodine . New ( drivers . ObjectNameInvalid { Object : objectName } , nil )
return "" , iodine . New ( drivers . ObjectNameInvalid { Object : objectName } , nil )
}
}
storedBucket := d . storedBuckets [ bucketName ]
// get object key
objectKey := bucketName + "/" + objectName
if _ , ok := storedBucket . objectMetadata [ objectKey ] ; ok == true {
return "" , iodine . New ( drivers . ObjectExists { Bucket : bucketName , Object : objectName } , nil )
}
if strings . TrimSpace ( contentType ) == "" {
if strings . TrimSpace ( contentType ) == "" {
contentType = "application/octet-stream"
contentType = "application/octet-stream"
}
}
@ -579,7 +406,5 @@ func (d donutDriver) CreateObject(bucketName, objectName, contentType, expectedM
Md5 : objMetadata . MD5Sum ,
Md5 : objMetadata . MD5Sum ,
Size : objMetadata . Size ,
Size : objMetadata . Size ,
}
}
storedBucket . objectMetadata [ objectKey ] = newObject
d . storedBuckets [ bucketName ] = storedBucket
return newObject . Md5 , nil
return newObject . Md5 , nil
}
}