mirror of
https://github.com/minio/minio.git
synced 2024-12-29 00:23:21 -05:00
cfdb29cac0
This change brings a new SignatureHandler where Presigned. Requests without Payload are handled very early before even going through the call. This change simplifies Donut codebase to not have signature related logic for all API's. Simplification is still needed for Payload based signature eg. PUT/POST calls , which are still part of the donut codebase, which will be done subsequently after donut re-write.
631 lines
19 KiB
Go
631 lines
19 KiB
Go
/*
|
|
* Minio Cloud Storage, (C) 2015 Minio, Inc.
|
|
*
|
|
* 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 donut
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"runtime/debug"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/minio/minio/pkg/crypto/sha256"
|
|
"github.com/minio/minio/pkg/donut/cache/data"
|
|
"github.com/minio/minio/pkg/donut/cache/metadata"
|
|
"github.com/minio/minio/pkg/probe"
|
|
"github.com/minio/minio/pkg/quick"
|
|
signv4 "github.com/minio/minio/pkg/signature"
|
|
)
|
|
|
|
// total Number of buckets allowed
|
|
const (
|
|
totalBuckets = 100
|
|
)
|
|
|
|
// Config donut config
|
|
type Config struct {
|
|
Version string `json:"version"`
|
|
MaxSize uint64 `json:"max-size"`
|
|
DonutName string `json:"donut-name"`
|
|
NodeDiskMap map[string][]string `json:"node-disk-map"`
|
|
}
|
|
|
|
// API - local variables
|
|
type API struct {
|
|
config *Config
|
|
lock *sync.Mutex
|
|
objects *data.Cache
|
|
multiPartObjects map[string]*data.Cache
|
|
storedBuckets *metadata.Cache
|
|
nodes map[string]node
|
|
buckets map[string]bucket
|
|
}
|
|
|
|
// storedBucket saved bucket
|
|
type storedBucket struct {
|
|
bucketMetadata BucketMetadata
|
|
objectMetadata map[string]ObjectMetadata
|
|
partMetadata map[string]map[int]PartMetadata
|
|
multiPartSession map[string]MultiPartSession
|
|
}
|
|
|
|
// New instantiate a new donut
|
|
func New() (Interface, *probe.Error) {
|
|
var conf *Config
|
|
var err *probe.Error
|
|
conf, err = LoadConfig()
|
|
if err != nil {
|
|
conf = &Config{
|
|
Version: "0.0.1",
|
|
MaxSize: 512000000,
|
|
NodeDiskMap: nil,
|
|
DonutName: "",
|
|
}
|
|
if err := quick.CheckData(conf); err != nil {
|
|
return nil, err.Trace()
|
|
}
|
|
}
|
|
a := API{config: conf}
|
|
a.storedBuckets = metadata.NewCache()
|
|
a.nodes = make(map[string]node)
|
|
a.buckets = make(map[string]bucket)
|
|
a.objects = data.NewCache(a.config.MaxSize)
|
|
a.multiPartObjects = make(map[string]*data.Cache)
|
|
a.objects.OnEvicted = a.evictedObject
|
|
a.lock = new(sync.Mutex)
|
|
|
|
if len(a.config.NodeDiskMap) > 0 {
|
|
for k, v := range a.config.NodeDiskMap {
|
|
if len(v) == 0 {
|
|
return nil, probe.NewError(InvalidDisksArgument{})
|
|
}
|
|
err := a.AttachNode(k, v)
|
|
if err != nil {
|
|
return nil, err.Trace()
|
|
}
|
|
}
|
|
/// Initialization, populate all buckets into memory
|
|
buckets, err := a.listBuckets()
|
|
if err != nil {
|
|
return nil, err.Trace()
|
|
}
|
|
for k, v := range buckets {
|
|
var newBucket = storedBucket{}
|
|
newBucket.bucketMetadata = v
|
|
newBucket.objectMetadata = make(map[string]ObjectMetadata)
|
|
newBucket.multiPartSession = make(map[string]MultiPartSession)
|
|
newBucket.partMetadata = make(map[string]map[int]PartMetadata)
|
|
a.storedBuckets.Set(k, newBucket)
|
|
}
|
|
a.Heal()
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
/// V2 API functions
|
|
|
|
// GetObject - GET object from cache buffer
|
|
func (donut API) GetObject(w io.Writer, bucket string, object string, start, length int64) (int64, *probe.Error) {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
if !IsValidBucket(bucket) {
|
|
return 0, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
|
}
|
|
if !IsValidObjectName(object) {
|
|
return 0, probe.NewError(ObjectNameInvalid{Object: object})
|
|
}
|
|
if start < 0 {
|
|
return 0, probe.NewError(InvalidRange{
|
|
Start: start,
|
|
Length: length,
|
|
})
|
|
}
|
|
if !donut.storedBuckets.Exists(bucket) {
|
|
return 0, probe.NewError(BucketNotFound{Bucket: bucket})
|
|
}
|
|
objectKey := bucket + "/" + object
|
|
data, ok := donut.objects.Get(objectKey)
|
|
var written int64
|
|
if !ok {
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
reader, size, err := donut.getObject(bucket, object)
|
|
if err != nil {
|
|
return 0, err.Trace()
|
|
}
|
|
if start > 0 {
|
|
if _, err := io.CopyN(ioutil.Discard, reader, start); err != nil {
|
|
return 0, probe.NewError(err)
|
|
}
|
|
}
|
|
// new proxy writer to capture data read from disk
|
|
pw := NewProxyWriter(w)
|
|
{
|
|
var err error
|
|
if length > 0 {
|
|
written, err = io.CopyN(pw, reader, length)
|
|
if err != nil {
|
|
return 0, probe.NewError(err)
|
|
}
|
|
} else {
|
|
written, err = io.CopyN(pw, reader, size)
|
|
if err != nil {
|
|
return 0, probe.NewError(err)
|
|
}
|
|
}
|
|
}
|
|
/// cache object read from disk
|
|
ok := donut.objects.Append(objectKey, pw.writtenBytes)
|
|
pw.writtenBytes = nil
|
|
go debug.FreeOSMemory()
|
|
if !ok {
|
|
return 0, probe.NewError(InternalError{})
|
|
}
|
|
return written, nil
|
|
}
|
|
return 0, probe.NewError(ObjectNotFound{Object: object})
|
|
}
|
|
{
|
|
var err error
|
|
if start == 0 && length == 0 {
|
|
written, err = io.CopyN(w, bytes.NewBuffer(data), int64(donut.objects.Len(objectKey)))
|
|
if err != nil {
|
|
return 0, probe.NewError(err)
|
|
}
|
|
} else {
|
|
written, err = io.CopyN(w, bytes.NewBuffer(data[start:]), length)
|
|
if err != nil {
|
|
return 0, probe.NewError(err)
|
|
}
|
|
}
|
|
return written, nil
|
|
}
|
|
}
|
|
|
|
// GetBucketMetadata -
|
|
func (donut API) GetBucketMetadata(bucket string) (BucketMetadata, *probe.Error) {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
if !IsValidBucket(bucket) {
|
|
return BucketMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
|
}
|
|
if !donut.storedBuckets.Exists(bucket) {
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
bucketMetadata, err := donut.getBucketMetadata(bucket)
|
|
if err != nil {
|
|
return BucketMetadata{}, err.Trace()
|
|
}
|
|
storedBucket := donut.storedBuckets.Get(bucket).(storedBucket)
|
|
storedBucket.bucketMetadata = bucketMetadata
|
|
donut.storedBuckets.Set(bucket, storedBucket)
|
|
}
|
|
return BucketMetadata{}, probe.NewError(BucketNotFound{Bucket: bucket})
|
|
}
|
|
return donut.storedBuckets.Get(bucket).(storedBucket).bucketMetadata, nil
|
|
}
|
|
|
|
// SetBucketMetadata -
|
|
func (donut API) SetBucketMetadata(bucket string, metadata map[string]string) *probe.Error {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
if !IsValidBucket(bucket) {
|
|
return probe.NewError(BucketNameInvalid{Bucket: bucket})
|
|
}
|
|
if !donut.storedBuckets.Exists(bucket) {
|
|
return probe.NewError(BucketNotFound{Bucket: bucket})
|
|
}
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
if err := donut.setBucketMetadata(bucket, metadata); err != nil {
|
|
return err.Trace()
|
|
}
|
|
}
|
|
storedBucket := donut.storedBuckets.Get(bucket).(storedBucket)
|
|
storedBucket.bucketMetadata.ACL = BucketACL(metadata["acl"])
|
|
donut.storedBuckets.Set(bucket, storedBucket)
|
|
return nil
|
|
}
|
|
|
|
// isMD5SumEqual - returns error if md5sum mismatches, success its `nil`
|
|
func isMD5SumEqual(expectedMD5Sum, actualMD5Sum string) *probe.Error {
|
|
if strings.TrimSpace(expectedMD5Sum) != "" && strings.TrimSpace(actualMD5Sum) != "" {
|
|
expectedMD5SumBytes, err := hex.DecodeString(expectedMD5Sum)
|
|
if err != nil {
|
|
return probe.NewError(err)
|
|
}
|
|
actualMD5SumBytes, err := hex.DecodeString(actualMD5Sum)
|
|
if err != nil {
|
|
return probe.NewError(err)
|
|
}
|
|
if !bytes.Equal(expectedMD5SumBytes, actualMD5SumBytes) {
|
|
return probe.NewError(BadDigest{})
|
|
}
|
|
return nil
|
|
}
|
|
return probe.NewError(InvalidArgument{})
|
|
}
|
|
|
|
// CreateObject - create an object
|
|
func (donut API) CreateObject(bucket, key, expectedMD5Sum string, size int64, data io.Reader, metadata map[string]string, signature *signv4.Signature) (ObjectMetadata, *probe.Error) {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
contentType := metadata["contentType"]
|
|
objectMetadata, err := donut.createObject(bucket, key, contentType, expectedMD5Sum, size, data, signature)
|
|
// free
|
|
debug.FreeOSMemory()
|
|
|
|
return objectMetadata, err.Trace()
|
|
}
|
|
|
|
// createObject - PUT object to cache buffer
|
|
func (donut API) createObject(bucket, key, contentType, expectedMD5Sum string, size int64, data io.Reader, signature *signv4.Signature) (ObjectMetadata, *probe.Error) {
|
|
if len(donut.config.NodeDiskMap) == 0 {
|
|
if size > int64(donut.config.MaxSize) {
|
|
generic := GenericObjectError{Bucket: bucket, Object: key}
|
|
return ObjectMetadata{}, probe.NewError(EntityTooLarge{
|
|
GenericObjectError: generic,
|
|
Size: strconv.FormatInt(size, 10),
|
|
MaxSize: strconv.FormatUint(donut.config.MaxSize, 10),
|
|
})
|
|
}
|
|
}
|
|
if !IsValidBucket(bucket) {
|
|
return ObjectMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
|
}
|
|
if !IsValidObjectName(key) {
|
|
return ObjectMetadata{}, probe.NewError(ObjectNameInvalid{Object: key})
|
|
}
|
|
if !donut.storedBuckets.Exists(bucket) {
|
|
return ObjectMetadata{}, probe.NewError(BucketNotFound{Bucket: bucket})
|
|
}
|
|
storedBucket := donut.storedBuckets.Get(bucket).(storedBucket)
|
|
// get object key
|
|
objectKey := bucket + "/" + key
|
|
if _, ok := storedBucket.objectMetadata[objectKey]; ok == true {
|
|
return ObjectMetadata{}, probe.NewError(ObjectExists{Object: key})
|
|
}
|
|
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
contentType = strings.TrimSpace(contentType)
|
|
if strings.TrimSpace(expectedMD5Sum) != "" {
|
|
expectedMD5SumBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(expectedMD5Sum))
|
|
if err != nil {
|
|
// pro-actively close the connection
|
|
return ObjectMetadata{}, probe.NewError(InvalidDigest{Md5: expectedMD5Sum})
|
|
}
|
|
expectedMD5Sum = hex.EncodeToString(expectedMD5SumBytes)
|
|
}
|
|
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
objMetadata, err := donut.putObject(
|
|
bucket,
|
|
key,
|
|
expectedMD5Sum,
|
|
data,
|
|
size,
|
|
map[string]string{
|
|
"contentType": contentType,
|
|
"contentLength": strconv.FormatInt(size, 10),
|
|
},
|
|
signature,
|
|
)
|
|
if err != nil {
|
|
return ObjectMetadata{}, err.Trace()
|
|
}
|
|
storedBucket.objectMetadata[objectKey] = objMetadata
|
|
donut.storedBuckets.Set(bucket, storedBucket)
|
|
return objMetadata, nil
|
|
}
|
|
|
|
// calculate md5
|
|
hash := md5.New()
|
|
sha256hash := sha256.New()
|
|
|
|
var err error
|
|
var totalLength int64
|
|
for err == nil {
|
|
var length int
|
|
byteBuffer := make([]byte, 1024*1024)
|
|
length, err = data.Read(byteBuffer)
|
|
hash.Write(byteBuffer[0:length])
|
|
sha256hash.Write(byteBuffer[0:length])
|
|
ok := donut.objects.Append(objectKey, byteBuffer[0:length])
|
|
if !ok {
|
|
return ObjectMetadata{}, probe.NewError(InternalError{})
|
|
}
|
|
totalLength += int64(length)
|
|
go debug.FreeOSMemory()
|
|
}
|
|
if size != 0 {
|
|
if totalLength != size {
|
|
// Delete perhaps the object is already saved, due to the nature of append()
|
|
donut.objects.Delete(objectKey)
|
|
return ObjectMetadata{}, probe.NewError(IncompleteBody{Bucket: bucket, Object: key})
|
|
}
|
|
}
|
|
if err != io.EOF {
|
|
return ObjectMetadata{}, probe.NewError(err)
|
|
}
|
|
md5SumBytes := hash.Sum(nil)
|
|
md5Sum := hex.EncodeToString(md5SumBytes)
|
|
// Verify if the written object is equal to what is expected, only if it is requested as such
|
|
if strings.TrimSpace(expectedMD5Sum) != "" {
|
|
if err := isMD5SumEqual(strings.TrimSpace(expectedMD5Sum), md5Sum); err != nil {
|
|
return ObjectMetadata{}, probe.NewError(BadDigest{})
|
|
}
|
|
}
|
|
if signature != nil {
|
|
ok, err := signature.DoesSignatureMatch(hex.EncodeToString(sha256hash.Sum(nil)))
|
|
if err != nil {
|
|
return ObjectMetadata{}, err.Trace()
|
|
}
|
|
if !ok {
|
|
return ObjectMetadata{}, probe.NewError(signv4.DoesNotMatch{})
|
|
}
|
|
}
|
|
|
|
m := make(map[string]string)
|
|
m["contentType"] = contentType
|
|
newObject := ObjectMetadata{
|
|
Bucket: bucket,
|
|
Object: key,
|
|
|
|
Metadata: m,
|
|
Created: time.Now().UTC(),
|
|
MD5Sum: md5Sum,
|
|
Size: int64(totalLength),
|
|
}
|
|
|
|
storedBucket.objectMetadata[objectKey] = newObject
|
|
donut.storedBuckets.Set(bucket, storedBucket)
|
|
return newObject, nil
|
|
}
|
|
|
|
// MakeBucket - create bucket in cache
|
|
func (donut API) MakeBucket(bucketName, acl string, location io.Reader, signature *signv4.Signature) *probe.Error {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
// do not have to parse location constraint, using this just for signature verification
|
|
locationSum := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
if location != nil {
|
|
locationConstraintBytes, err := ioutil.ReadAll(location)
|
|
if err != nil {
|
|
return probe.NewError(InternalError{})
|
|
}
|
|
locationSum = hex.EncodeToString(sha256.Sum256(locationConstraintBytes)[:])
|
|
}
|
|
|
|
if signature != nil {
|
|
ok, err := signature.DoesSignatureMatch(locationSum)
|
|
if err != nil {
|
|
return err.Trace()
|
|
}
|
|
if !ok {
|
|
return probe.NewError(signv4.DoesNotMatch{})
|
|
}
|
|
}
|
|
|
|
if donut.storedBuckets.Stats().Items == totalBuckets {
|
|
return probe.NewError(TooManyBuckets{Bucket: bucketName})
|
|
}
|
|
if !IsValidBucket(bucketName) {
|
|
return probe.NewError(BucketNameInvalid{Bucket: bucketName})
|
|
}
|
|
if !IsValidBucketACL(acl) {
|
|
return probe.NewError(InvalidACL{ACL: acl})
|
|
}
|
|
if donut.storedBuckets.Exists(bucketName) {
|
|
return probe.NewError(BucketExists{Bucket: bucketName})
|
|
}
|
|
|
|
if strings.TrimSpace(acl) == "" {
|
|
// default is private
|
|
acl = "private"
|
|
}
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
if err := donut.makeBucket(bucketName, BucketACL(acl)); err != nil {
|
|
return err.Trace()
|
|
}
|
|
}
|
|
var newBucket = storedBucket{}
|
|
newBucket.objectMetadata = make(map[string]ObjectMetadata)
|
|
newBucket.multiPartSession = make(map[string]MultiPartSession)
|
|
newBucket.partMetadata = make(map[string]map[int]PartMetadata)
|
|
newBucket.bucketMetadata = BucketMetadata{}
|
|
newBucket.bucketMetadata.Name = bucketName
|
|
newBucket.bucketMetadata.Created = time.Now().UTC()
|
|
newBucket.bucketMetadata.ACL = BucketACL(acl)
|
|
donut.storedBuckets.Set(bucketName, newBucket)
|
|
return nil
|
|
}
|
|
|
|
// ListObjects - list objects from cache
|
|
func (donut API) ListObjects(bucket string, resources BucketResourcesMetadata) ([]ObjectMetadata, BucketResourcesMetadata, *probe.Error) {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
if !IsValidBucket(bucket) {
|
|
return nil, BucketResourcesMetadata{IsTruncated: false}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
|
}
|
|
if !IsValidPrefix(resources.Prefix) {
|
|
return nil, BucketResourcesMetadata{IsTruncated: false}, probe.NewError(ObjectNameInvalid{Object: resources.Prefix})
|
|
}
|
|
if !donut.storedBuckets.Exists(bucket) {
|
|
return nil, BucketResourcesMetadata{IsTruncated: false}, probe.NewError(BucketNotFound{Bucket: bucket})
|
|
}
|
|
var results []ObjectMetadata
|
|
var keys []string
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
listObjects, err := donut.listObjects(
|
|
bucket,
|
|
resources.Prefix,
|
|
resources.Marker,
|
|
resources.Delimiter,
|
|
resources.Maxkeys,
|
|
)
|
|
if err != nil {
|
|
return nil, BucketResourcesMetadata{IsTruncated: false}, err.Trace()
|
|
}
|
|
resources.CommonPrefixes = listObjects.CommonPrefixes
|
|
resources.IsTruncated = listObjects.IsTruncated
|
|
for key := range listObjects.Objects {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
results = append(results, listObjects.Objects[key])
|
|
}
|
|
if resources.IsTruncated && resources.Delimiter != "" {
|
|
resources.NextMarker = results[len(results)-1].Object
|
|
}
|
|
return results, resources, nil
|
|
}
|
|
storedBucket := donut.storedBuckets.Get(bucket).(storedBucket)
|
|
for key := range storedBucket.objectMetadata {
|
|
if strings.HasPrefix(key, bucket+"/") {
|
|
key = key[len(bucket)+1:]
|
|
if strings.HasPrefix(key, resources.Prefix) {
|
|
if key > resources.Marker {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if strings.TrimSpace(resources.Prefix) != "" {
|
|
keys = TrimPrefix(keys, resources.Prefix)
|
|
}
|
|
var prefixes []string
|
|
var filteredKeys []string
|
|
filteredKeys = keys
|
|
if strings.TrimSpace(resources.Delimiter) != "" {
|
|
filteredKeys = HasNoDelimiter(keys, resources.Delimiter)
|
|
prefixes = HasDelimiter(keys, resources.Delimiter)
|
|
prefixes = SplitDelimiter(prefixes, resources.Delimiter)
|
|
prefixes = SortUnique(prefixes)
|
|
}
|
|
for _, commonPrefix := range prefixes {
|
|
resources.CommonPrefixes = append(resources.CommonPrefixes, resources.Prefix+commonPrefix)
|
|
}
|
|
filteredKeys = RemoveDuplicates(filteredKeys)
|
|
sort.Strings(filteredKeys)
|
|
|
|
for _, key := range filteredKeys {
|
|
if len(results) == resources.Maxkeys {
|
|
resources.IsTruncated = true
|
|
if resources.IsTruncated && resources.Delimiter != "" {
|
|
resources.NextMarker = results[len(results)-1].Object
|
|
}
|
|
return results, resources, nil
|
|
}
|
|
object := storedBucket.objectMetadata[bucket+"/"+resources.Prefix+key]
|
|
results = append(results, object)
|
|
}
|
|
resources.CommonPrefixes = RemoveDuplicates(resources.CommonPrefixes)
|
|
sort.Strings(resources.CommonPrefixes)
|
|
return results, resources, nil
|
|
}
|
|
|
|
// byBucketName is a type for sorting bucket metadata by bucket name
|
|
type byBucketName []BucketMetadata
|
|
|
|
func (b byBucketName) Len() int { return len(b) }
|
|
func (b byBucketName) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
|
|
func (b byBucketName) Less(i, j int) bool { return b[i].Name < b[j].Name }
|
|
|
|
// ListBuckets - List buckets from cache
|
|
func (donut API) ListBuckets() ([]BucketMetadata, *probe.Error) {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
var results []BucketMetadata
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
buckets, err := donut.listBuckets()
|
|
if err != nil {
|
|
return nil, err.Trace()
|
|
}
|
|
for _, bucketMetadata := range buckets {
|
|
results = append(results, bucketMetadata)
|
|
}
|
|
sort.Sort(byBucketName(results))
|
|
return results, nil
|
|
}
|
|
for _, bucket := range donut.storedBuckets.GetAll() {
|
|
results = append(results, bucket.(storedBucket).bucketMetadata)
|
|
}
|
|
sort.Sort(byBucketName(results))
|
|
return results, nil
|
|
}
|
|
|
|
// GetObjectMetadata - get object metadata from cache
|
|
func (donut API) GetObjectMetadata(bucket, key string) (ObjectMetadata, *probe.Error) {
|
|
donut.lock.Lock()
|
|
defer donut.lock.Unlock()
|
|
|
|
// check if bucket exists
|
|
if !IsValidBucket(bucket) {
|
|
return ObjectMetadata{}, probe.NewError(BucketNameInvalid{Bucket: bucket})
|
|
}
|
|
if !IsValidObjectName(key) {
|
|
return ObjectMetadata{}, probe.NewError(ObjectNameInvalid{Object: key})
|
|
}
|
|
if !donut.storedBuckets.Exists(bucket) {
|
|
return ObjectMetadata{}, probe.NewError(BucketNotFound{Bucket: bucket})
|
|
}
|
|
storedBucket := donut.storedBuckets.Get(bucket).(storedBucket)
|
|
objectKey := bucket + "/" + key
|
|
if objMetadata, ok := storedBucket.objectMetadata[objectKey]; ok == true {
|
|
return objMetadata, nil
|
|
}
|
|
if len(donut.config.NodeDiskMap) > 0 {
|
|
objMetadata, err := donut.getObjectMetadata(bucket, key)
|
|
if err != nil {
|
|
return ObjectMetadata{}, err.Trace()
|
|
}
|
|
// update
|
|
storedBucket.objectMetadata[objectKey] = objMetadata
|
|
donut.storedBuckets.Set(bucket, storedBucket)
|
|
return objMetadata, nil
|
|
}
|
|
return ObjectMetadata{}, probe.NewError(ObjectNotFound{Object: key})
|
|
}
|
|
|
|
// evictedObject callback function called when an item is evicted from memory
|
|
func (donut API) evictedObject(a ...interface{}) {
|
|
cacheStats := donut.objects.Stats()
|
|
log.Printf("CurrentSize: %d, CurrentItems: %d, TotalEvicted: %d",
|
|
cacheStats.Bytes, cacheStats.Items, cacheStats.Evicted)
|
|
key := a[0].(string)
|
|
// loop through all buckets
|
|
for _, bucket := range donut.storedBuckets.GetAll() {
|
|
delete(bucket.(storedBucket).objectMetadata, key)
|
|
}
|
|
debug.FreeOSMemory()
|
|
}
|