Merge pull request #472 from harshavardhana/pr_out_further_fixes_for_acl_support_currently_code_is_disabled_in_all_the_handlers

Further fixes for ACL support, currently code is disabled in all the handlers
This commit is contained in:
Harshavardhana 2015-04-22 19:35:07 -07:00
commit 4ed03881ff
22 changed files with 450 additions and 158 deletions

79
pkg/api/acl.go Normal file
View File

@ -0,0 +1,79 @@
/*
* Minimalist Object 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 api
import "net/http"
// Please read for more information - http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl
//
// Here We are only supporting 'acl's through request headers not through their request body
// http://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#setting-acls
// Minio only supports three types for now i.e 'private, public-read, public-read-write'
// ACLType - different acl types
type ACLType int
const (
unsupportedACLType ACLType = iota
privateACLType
publicReadACLType
publicReadWriteACLType
)
// Get acl type requested from 'x-amz-acl' header
func getACLType(req *http.Request) ACLType {
aclHeader := req.Header.Get("x-amz-acl")
if aclHeader != "" {
switch {
case aclHeader == "private":
return privateACLType
case aclHeader == "public-read":
return publicReadACLType
case aclHeader == "public-read-write":
return publicReadWriteACLType
default:
return unsupportedACLType
}
}
// make it default private
return privateACLType
}
// ACL type to human readable string
func getACLTypeString(acl ACLType) string {
switch acl {
case privateACLType:
{
return "private"
}
case publicReadACLType:
{
return "public-read"
}
case publicReadWriteACLType:
{
return "public-read-write"
}
case unsupportedACLType:
{
return ""
}
default:
return "private"
}
}

View File

@ -32,14 +32,28 @@ import (
// criteria to return a subset of the objects in a bucket. // criteria to return a subset of the objects in a bucket.
// //
func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req) acceptsContentType := getContentType(req)
bucket := vars["bucket"] if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
resources := getBucketResources(req.URL.Query()) resources := getBucketResources(req.URL.Query())
if resources.Maxkeys == 0 { if resources.Maxkeys == 0 {
resources.Maxkeys = maxObjectList resources.Maxkeys = maxObjectList
} }
acceptsContentType := getContentType(req)
vars := mux.Vars(req)
bucket := vars["bucket"]
// Enable this after tests supports them
// verify for if bucket is private or public
// bucketMetadata, err := server.driver.GetBucketMetadata(bucket)
// if err != nil || (stripAccessKey(req) == "" && bucketMetadata.ACL.IsPrivate()) {
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
// return
// }
objects, resources, err := server.driver.ListObjects(bucket, resources) objects, resources, err := server.driver.ListObjects(bucket, resources)
switch err.(type) { switch err.(type) {
case nil: // success case nil: // success
@ -49,8 +63,8 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
// write body // write body
response := generateObjectsListResult(bucket, objects, resources) response := generateObjectsListResult(bucket, objects, resources)
encodedResponse := encodeResponse(response, acceptsContentType) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
w.Write(encodedResponse) w.Write(encodedSuccessResponse)
} }
case drivers.BucketNotFound: case drivers.BucketNotFound:
{ {
@ -78,6 +92,11 @@ func (server *minioAPI) listObjectsHandler(w http.ResponseWriter, req *http.Requ
// owned by the authenticated sender of the request. // owned by the authenticated sender of the request.
func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
buckets, err := server.driver.ListBuckets() buckets, err := server.driver.ListBuckets()
// cannot fallthrough in (type) switch :( // cannot fallthrough in (type) switch :(
switch err := err.(type) { switch err := err.(type) {
@ -88,8 +107,8 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
setCommonHeaders(w, getContentTypeString(acceptsContentType)) setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
// write response // write response
encodedResponse := encodeResponse(response, acceptsContentType) encodedSuccessResponse := encodeSuccessResponse(response, acceptsContentType)
w.Write(encodedResponse) w.Write(encodedSuccessResponse)
} }
default: default:
{ {
@ -103,11 +122,22 @@ func (server *minioAPI) listBucketsHandler(w http.ResponseWriter, req *http.Requ
// ---------- // ----------
// This implementation of the PUT operation creates a new bucket for authenticated request // This implementation of the PUT operation creates a new bucket for authenticated request
func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
// read from 'x-amz-acl'
aclType := getACLType(req)
if aclType == unsupportedACLType {
writeErrorResponse(w, req, NotImplemented, acceptsContentType, req.URL.Path)
return
}
vars := mux.Vars(req) vars := mux.Vars(req)
bucket := vars["bucket"] bucket := vars["bucket"]
err := server.driver.CreateBucket(bucket) err := server.driver.CreateBucket(bucket, getACLTypeString(aclType))
acceptsContentType := getContentType(req)
switch err.(type) { switch err.(type) {
case nil: case nil:
{ {
@ -138,9 +168,22 @@ func (server *minioAPI) putBucketHandler(w http.ResponseWriter, req *http.Reques
// have permission to access it. Otherwise, the operation might // have permission to access it. Otherwise, the operation might
// return responses such as 404 Not Found and 403 Forbidden. // return responses such as 404 Not Found and 403 Forbidden.
func (server *minioAPI) headBucketHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) headBucketHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
vars := mux.Vars(req) vars := mux.Vars(req)
bucket := vars["bucket"] bucket := vars["bucket"]
acceptsContentType := getContentType(req)
// Enable this after tests supports them
// verify for if bucket is private or public
// bucketMetadata, err := server.driver.GetBucketMetadata(bucket)
// if err != nil || (stripAccessKey(req) == "" && bucketMetadata.ACL.IsPrivate()) {
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
// return
// }
_, err := server.driver.GetBucketMetadata(bucket) _, err := server.driver.GetBucketMetadata(bucket)
switch err.(type) { switch err.(type) {

View File

@ -48,42 +48,39 @@ func stripAccessKey(r *http.Request) string {
// Validate handler is wrapper handler used for API request validation with authorization header. // Validate handler is wrapper handler used for API request validation with authorization header.
// Current authorization layer supports S3's standard HMAC based signature request. // Current authorization layer supports S3's standard HMAC based signature request.
func validateHandler(conf config.Config, h http.Handler) http.Handler { func validateHandler(conf config.Config, h http.Handler) http.Handler {
return vHandler{conf, h} return vHandler{
conf: conf,
handler: h,
}
} }
// Validate handler ServeHTTP() wrapper // Validate handler ServeHTTP() wrapper
func (h vHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h vHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
accessKey := stripAccessKey(r) accessKey := stripAccessKey(r)
acceptsContentType := getContentType(r) acceptsContentType := getContentType(r)
if accessKey != "" { if acceptsContentType == unknownContentType {
writeErrorResponse(w, r, NotAcceptable, acceptsContentType, r.URL.Path)
return
}
switch true {
case accessKey != "":
if err := h.conf.ReadConfig(); err != nil { if err := h.conf.ReadConfig(); err != nil {
error := getErrorCode(InternalError) writeErrorResponse(w, r, InternalError, acceptsContentType, r.URL.Path)
errorResponse := getErrorResponse(error, "") return
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
w.Write(encodeErrorResponse(errorResponse, acceptsContentType))
} else {
user, ok := h.conf.Users[accessKey]
if ok == false {
error := getErrorCode(AccessDenied)
errorResponse := getErrorResponse(error, "")
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
w.Write(encodeErrorResponse(errorResponse, acceptsContentType))
} else {
ok, _ = ValidateRequest(user, r)
if ok {
h.handler.ServeHTTP(w, r)
} else {
error := getErrorCode(AccessDenied)
errorResponse := getErrorResponse(error, "")
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
w.Write(encodeErrorResponse(errorResponse, acceptsContentType))
}
}
} }
} else { user, ok := h.conf.Users[accessKey]
if !ok {
writeErrorResponse(w, r, AccessDenied, acceptsContentType, r.URL.Path)
return
}
ok, _ = ValidateRequest(user, r)
if !ok {
writeErrorResponse(w, r, AccessDenied, acceptsContentType, r.URL.Path)
return
}
// Success
h.handler.ServeHTTP(w, r)
default:
// Control reaches when no access key is found, ideally we would // Control reaches when no access key is found, ideally we would
// like to throw back `403`. But for now with our tests lacking // like to throw back `403`. But for now with our tests lacking
// this functionality it is better for us to be serving anonymous // this functionality it is better for us to be serving anonymous
@ -142,14 +139,3 @@ func ignoreUnImplementedObjectResources(req *http.Request) bool {
} }
return false return false
} }
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, acceptsContentType contentType, resource string) {
error := getErrorCode(errorType)
errorResponse := getErrorResponse(error, resource)
// set headers
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
// write body
encodedErrorResponse := encodeErrorResponse(errorResponse, acceptsContentType)
w.Write(encodedErrorResponse)
}

View File

@ -30,12 +30,26 @@ import (
// This implementation of the GET operation retrieves object. To use GET, // This implementation of the GET operation retrieves object. To use GET,
// you must have READ access to the object. // you must have READ access to the object.
func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Request) {
var object, bucket string
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string
vars := mux.Vars(req) vars := mux.Vars(req)
bucket = vars["bucket"] bucket = vars["bucket"]
object = vars["object"] object = vars["object"]
// Enable this after tests supports them
// verify for if bucket is private or public
// bucketMetadata, err := server.driver.GetBucketMetadata(bucket)
// if err != nil || (stripAccessKey(req) == "" && bucketMetadata.ACL.IsPrivate()) {
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
// return
// }
metadata, err := server.driver.GetObjectMetadata(bucket, object, "") metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
switch err := err.(type) { switch err := err.(type) {
case nil: // success case nil: // success
@ -47,23 +61,18 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
} }
switch httpRange.start == 0 && httpRange.length == 0 { switch httpRange.start == 0 && httpRange.length == 0 {
case true: case true:
{ setObjectHeaders(w, metadata)
setObjectHeaders(w, metadata) if _, err := server.driver.GetObject(w, bucket, object); err != nil {
if _, err := server.driver.GetObject(w, bucket, object); err != nil { // unable to write headers, we've already printed data. Just close the connection.
// unable to write headers, we've already printed data. Just close the connection. log.Error.Println(err)
log.Error.Println(err)
}
} }
case false: case false:
{ metadata.Size = httpRange.length
metadata.Size = httpRange.length setRangeObjectHeaders(w, metadata, httpRange)
setRangeObjectHeaders(w, metadata, httpRange) w.WriteHeader(http.StatusPartialContent)
w.WriteHeader(http.StatusPartialContent) if _, err := server.driver.GetPartialObject(w, bucket, object, httpRange.start, httpRange.length); err != nil {
_, err := server.driver.GetPartialObject(w, bucket, object, httpRange.start, httpRange.length) // unable to write headers, we've already printed data. Just close the connection.
if err != nil { log.Error.Println(iodine.New(err, nil))
// unable to write headers, we've already printed data. Just close the connection.
log.Error.Println(iodine.New(err, nil))
}
} }
} }
} }
@ -95,12 +104,25 @@ func (server *minioAPI) getObjectHandler(w http.ResponseWriter, req *http.Reques
// ----------- // -----------
// The HEAD operation retrieves metadata from an object without returning the object itself. // The HEAD operation retrieves metadata from an object without returning the object itself.
func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Request) {
var object, bucket string
acceptsContentType := getContentType(req) acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string
vars := mux.Vars(req) vars := mux.Vars(req)
bucket = vars["bucket"] bucket = vars["bucket"]
object = vars["object"] object = vars["object"]
// verify for if bucket is private or public
// verify for if bucket is private or public
// bucketMetadata, err := server.driver.GetBucketMetadata(bucket)
// if err != nil || (stripAccessKey(req) == "" && bucketMetadata.ACL.IsPrivate()) {
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
// return
// }
metadata, err := server.driver.GetObjectMetadata(bucket, object, "") metadata, err := server.driver.GetObjectMetadata(bucket, object, "")
switch err := err.(type) { switch err := err.(type) {
case nil: case nil:
@ -128,14 +150,31 @@ func (server *minioAPI) headObjectHandler(w http.ResponseWriter, req *http.Reque
// ---------- // ----------
// This implementation of the PUT operation adds an object to a bucket. // This implementation of the PUT operation adds an object to a bucket.
func (server *minioAPI) putObjectHandler(w http.ResponseWriter, req *http.Request) { func (server *minioAPI) putObjectHandler(w http.ResponseWriter, req *http.Request) {
acceptsContentType := getContentType(req)
if acceptsContentType == unknownContentType {
writeErrorResponse(w, req, NotAcceptable, acceptsContentType, req.URL.Path)
return
}
var object, bucket string var object, bucket string
vars := mux.Vars(req) vars := mux.Vars(req)
acceptsContentType := getContentType(req)
bucket = vars["bucket"] bucket = vars["bucket"]
object = vars["object"] object = vars["object"]
// get Content-MD5 sent by client // verify for if bucket is private or public
// verify for if bucket is private or public
// bucketMetadata, err := server.driver.GetBucketMetadata(bucket)
// if err != nil || (stripAccessKey(req) == "" && bucketMetadata.ACL.IsPrivate()) || bucketMetadtata.ACL.IsPublicRead() {
// writeErrorResponse(w, req, AccessDenied, acceptsContentType, req.URL.Path)
// return
// }
// get Content-MD5 sent by client and verify if valid
md5 := req.Header.Get("Content-MD5") md5 := req.Header.Get("Content-MD5")
if !isValidMD5(md5) {
writeErrorResponse(w, req, InvalidDigest, acceptsContentType, req.URL.Path)
return
}
err := server.driver.CreateObject(bucket, object, "", md5, req.Body) err := server.driver.CreateObject(bucket, object, "", md5, req.Body)
switch err := err.(type) { switch err := err.(type) {
case nil: case nil:

View File

@ -17,6 +17,7 @@
package api package api
import ( import (
"net/http"
"sort" "sort"
"github.com/minio-io/minio/pkg/storage/drivers" "github.com/minio-io/minio/pkg/storage/drivers"
@ -107,3 +108,14 @@ func generateObjectsListResult(bucket string, objects []drivers.ObjectMetadata,
data.CommonPrefixes = prefixes data.CommonPrefixes = prefixes
return data return data
} }
func writeErrorResponse(w http.ResponseWriter, req *http.Request, errorType int, acceptsContentType contentType, resource string) {
error := getErrorCode(errorType)
errorResponse := getErrorResponse(error, resource)
// set headers
setCommonHeaders(w, getContentTypeString(acceptsContentType))
w.WriteHeader(error.HTTPStatusCode)
// write body
encodedErrorResponse := encodeErrorResponse(errorResponse, acceptsContentType)
w.Write(encodedErrorResponse)
}

View File

@ -115,11 +115,11 @@ func (s *MySuite) TestNonExistantObject(c *C) {
} }
} }
driver := s.Driver driver := s.Driver
s.MockDriver.On("GetObjectMetadata", "bucket", "object", "").Return(drivers.ObjectMetadata{}, drivers.BucketNotFound{Bucket: "bucket"}).Once()
httpHandler := api.HTTPHandler("", driver) httpHandler := api.HTTPHandler("", driver)
testServer := httptest.NewServer(httpHandler) testServer := httptest.NewServer(httpHandler)
defer testServer.Close() defer testServer.Close()
s.MockDriver.On("GetObjectMetadata", "bucket", "object", "").Return(drivers.ObjectMetadata{}, drivers.BucketNotFound{Bucket: "bucket"}).Once()
response, err := http.Get(testServer.URL + "/bucket/object") response, err := http.Get(testServer.URL + "/bucket/object")
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(response.StatusCode, Equals, http.StatusNotFound) c.Assert(response.StatusCode, Equals, http.StatusNotFound)
@ -142,7 +142,7 @@ func (s *MySuite) TestEmptyObject(c *C) {
Md5: "d41d8cd98f00b204e9800998ecf8427e", Md5: "d41d8cd98f00b204e9800998ecf8427e",
Size: 0, Size: 0,
} }
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
typedDriver.On("CreateObject", "bucket", "object", "", "", mock.Anything).Return(nil).Once() typedDriver.On("CreateObject", "bucket", "object", "", "", mock.Anything).Return(nil).Once()
typedDriver.On("GetObjectMetadata", "bucket", "object", "").Return(metadata, nil).Once() typedDriver.On("GetObjectMetadata", "bucket", "object", "").Return(metadata, nil).Once()
typedDriver.On("GetObject", mock.Anything, "bucket", "object").Return(int64(0), nil).Once() typedDriver.On("GetObject", mock.Anything, "bucket", "object").Return(int64(0), nil).Once()
@ -152,7 +152,7 @@ func (s *MySuite) TestEmptyObject(c *C) {
defer testServer.Close() defer testServer.Close()
buffer := bytes.NewBufferString("") buffer := bytes.NewBufferString("")
driver.CreateBucket("bucket") driver.CreateBucket("bucket", "private")
driver.CreateObject("bucket", "object", "", "", buffer) driver.CreateObject("bucket", "object", "", "", buffer)
response, err := http.Get(testServer.URL + "/bucket/object") response, err := http.Get(testServer.URL + "/bucket/object")
@ -181,14 +181,14 @@ func (s *MySuite) TestBucket(c *C) {
Name: "bucket", Name: "bucket",
Created: time.Now(), Created: time.Now(),
} }
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
typedDriver.On("GetBucketMetadata", "bucket").Return(metadata, nil).Twice() typedDriver.On("GetBucketMetadata", "bucket").Return(metadata, nil).Twice()
httpHandler := api.HTTPHandler("", driver) httpHandler := api.HTTPHandler("", driver)
testServer := httptest.NewServer(httpHandler) testServer := httptest.NewServer(httpHandler)
defer testServer.Close() defer testServer.Close()
driver.CreateBucket("bucket") driver.CreateBucket("bucket", "private")
response, err := http.Head(testServer.URL + "/bucket") response, err := http.Head(testServer.URL + "/bucket")
c.Assert(err, IsNil) c.Assert(err, IsNil)
@ -212,7 +212,7 @@ func (s *MySuite) TestObject(c *C) {
Md5: "5eb63bbbe01eeed093cb22bb8f5acdc3", Md5: "5eb63bbbe01eeed093cb22bb8f5acdc3",
Size: 11, Size: 11,
} }
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
typedDriver.On("CreateObject", "bucket", "object", "", "", mock.Anything).Return(nil).Once() typedDriver.On("CreateObject", "bucket", "object", "", "", mock.Anything).Return(nil).Once()
typedDriver.On("GetObjectMetadata", "bucket", "object", "").Return(metadata, nil).Twice() typedDriver.On("GetObjectMetadata", "bucket", "object", "").Return(metadata, nil).Twice()
typedDriver.SetGetObjectWriter("bucket", "object", []byte("hello world")) typedDriver.SetGetObjectWriter("bucket", "object", []byte("hello world"))
@ -223,7 +223,7 @@ func (s *MySuite) TestObject(c *C) {
defer testServer.Close() defer testServer.Close()
buffer := bytes.NewBufferString("hello world") buffer := bytes.NewBufferString("hello world")
driver.CreateBucket("bucket") driver.CreateBucket("bucket", "private")
driver.CreateObject("bucket", "object", "", "", buffer) driver.CreateObject("bucket", "object", "", "", buffer)
response, err := http.Get(testServer.URL + "/bucket/object") response, err := http.Get(testServer.URL + "/bucket/object")
@ -280,8 +280,8 @@ func (s *MySuite) TestMultipleObjects(c *C) {
buffer2 := bytes.NewBufferString("hello two") buffer2 := bytes.NewBufferString("hello two")
buffer3 := bytes.NewBufferString("hello three") buffer3 := bytes.NewBufferString("hello three")
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
driver.CreateBucket("bucket") driver.CreateBucket("bucket", "private")
typedDriver.On("CreateObject", "bucket", "object1", "", "", mock.Anything).Return(nil).Once() typedDriver.On("CreateObject", "bucket", "object1", "", "", mock.Anything).Return(nil).Once()
driver.CreateObject("bucket", "object1", "", "", buffer1) driver.CreateObject("bucket", "object1", "", "", buffer1)
typedDriver.On("CreateObject", "bucket", "object2", "", "", mock.Anything).Return(nil).Once() typedDriver.On("CreateObject", "bucket", "object2", "", "", mock.Anything).Return(nil).Once()
@ -397,8 +397,8 @@ func (s *MySuite) TestHeader(c *C) {
testServer := httptest.NewServer(httpHandler) testServer := httptest.NewServer(httpHandler)
defer testServer.Close() defer testServer.Close()
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
driver.CreateBucket("bucket") driver.CreateBucket("bucket", "private")
typedDriver.On("GetObjectMetadata", "bucket", "object", "").Return(drivers.ObjectMetadata{}, drivers.ObjectNotFound{}).Once() typedDriver.On("GetObjectMetadata", "bucket", "object", "").Return(drivers.ObjectMetadata{}, drivers.ObjectNotFound{}).Once()
response, err := http.Get(testServer.URL + "/bucket/object") response, err := http.Get(testServer.URL + "/bucket/object")
@ -450,9 +450,10 @@ func (s *MySuite) TestPutBucket(c *C) {
c.Assert(len(buckets), Equals, 0) c.Assert(len(buckets), Equals, 0)
c.Assert(err, IsNil) c.Assert(err, IsNil)
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
request, err := http.NewRequest("PUT", testServer.URL+"/bucket", bytes.NewBufferString("")) request, err := http.NewRequest("PUT", testServer.URL+"/bucket", bytes.NewBufferString(""))
c.Assert(err, IsNil) c.Assert(err, IsNil)
request.Header.Add("x-amz-acl", "private")
client := http.Client{} client := http.Client{}
response, err := client.Do(request) response, err := client.Do(request)
@ -496,9 +497,10 @@ func (s *MySuite) TestPutObject(c *C) {
date1 := time.Now().Add(-time.Second) date1 := time.Now().Add(-time.Second)
// Put Bucket before - Put Object into a bucket // Put Bucket before - Put Object into a bucket
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
request, err := http.NewRequest("PUT", testServer.URL+"/bucket", bytes.NewBufferString("")) request, err := http.NewRequest("PUT", testServer.URL+"/bucket", bytes.NewBufferString(""))
c.Assert(err, IsNil) c.Assert(err, IsNil)
request.Header.Add("x-amz-acl", "private")
client := http.Client{} client := http.Client{}
response, err := client.Do(request) response, err := client.Do(request)
@ -573,8 +575,9 @@ func (s *MySuite) TestListBuckets(c *C) {
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(len(listResponse.Buckets.Bucket), Equals, 0) c.Assert(len(listResponse.Buckets.Bucket), Equals, 0)
typedDriver.On("CreateBucket", "foo").Return(nil).Once() typedDriver.On("CreateBucket", "foo", "private").Return(nil).Once()
driver.CreateBucket("foo") err = driver.CreateBucket("foo", "private")
c.Assert(err, IsNil)
bucketMetadata := []drivers.BucketMetadata{ bucketMetadata := []drivers.BucketMetadata{
{Name: "foo", Created: time.Now()}, {Name: "foo", Created: time.Now()},
@ -590,8 +593,9 @@ func (s *MySuite) TestListBuckets(c *C) {
c.Assert(len(listResponse.Buckets.Bucket), Equals, 1) c.Assert(len(listResponse.Buckets.Bucket), Equals, 1)
c.Assert(listResponse.Buckets.Bucket[0].Name, Equals, "foo") c.Assert(listResponse.Buckets.Bucket[0].Name, Equals, "foo")
typedDriver.On("CreateBucket", "bar").Return(nil).Once() typedDriver.On("CreateBucket", "bar", "private").Return(nil).Once()
driver.CreateBucket("bar") err = driver.CreateBucket("bar", "private")
c.Assert(err, IsNil)
bucketMetadata = []drivers.BucketMetadata{ bucketMetadata = []drivers.BucketMetadata{
{Name: "bar", Created: time.Now()}, {Name: "bar", Created: time.Now()},
@ -687,8 +691,8 @@ func (s *MySuite) TestXMLNameNotInBucketListJson(c *C) {
testServer := httptest.NewServer(httpHandler) testServer := httptest.NewServer(httpHandler)
defer testServer.Close() defer testServer.Close()
typedDriver.On("CreateBucket", "foo").Return(nil).Once() typedDriver.On("CreateBucket", "foo", "private").Return(nil).Once()
err := driver.CreateBucket("foo") err := driver.CreateBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
typedDriver.On("ListBuckets").Return([]drivers.BucketMetadata{{Name: "foo", Created: time.Now()}}, nil) typedDriver.On("ListBuckets").Return([]drivers.BucketMetadata{{Name: "foo", Created: time.Now()}}, nil)
@ -721,8 +725,8 @@ func (s *MySuite) TestXMLNameNotInObjectListJson(c *C) {
testServer := httptest.NewServer(httpHandler) testServer := httptest.NewServer(httpHandler)
defer testServer.Close() defer testServer.Close()
typedDriver.On("CreateBucket", "foo").Return(nil).Once() typedDriver.On("CreateBucket", "foo", "private").Return(nil).Once()
err := driver.CreateBucket("foo") err := driver.CreateBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
typedDriver.On("ListObjects", "foo", mock.Anything).Return([]drivers.ObjectMetadata{}, drivers.BucketResourcesMetadata{}, nil).Once() typedDriver.On("ListObjects", "foo", mock.Anything).Return([]drivers.ObjectMetadata{}, drivers.BucketResourcesMetadata{}, nil).Once()
@ -756,8 +760,8 @@ func (s *MySuite) TestContentTypePersists(c *C) {
testServer := httptest.NewServer(httpHandler) testServer := httptest.NewServer(httpHandler)
defer testServer.Close() defer testServer.Close()
typedDriver.On("CreateBucket", "bucket").Return(nil).Once() typedDriver.On("CreateBucket", "bucket", "private").Return(nil).Once()
err := driver.CreateBucket("bucket") err := driver.CreateBucket("bucket", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
client := http.Client{} client := http.Client{}
@ -847,10 +851,11 @@ func (s *MySuite) TestPartialContent(c *C) {
Size: 11, Size: 11,
} }
typedDriver.On("CreateBucket", "foo").Return(nil).Once() typedDriver.On("CreateBucket", "foo", "private").Return(nil).Once()
typedDriver.On("CreateObject", "foo", "bar", "", "", mock.Anything).Return(nil).Once() typedDriver.On("CreateObject", "foo", "bar", "", "", mock.Anything).Return(nil).Once()
driver.CreateBucket("foo") err := driver.CreateBucket("foo", "private")
c.Assert(err, IsNil)
driver.CreateObject("foo", "bar", "", "", bytes.NewBufferString("hello world")) driver.CreateObject("foo", "bar", "", "", bytes.NewBufferString("hello world"))
// prepare for GET on range request // prepare for GET on range request
@ -967,26 +972,41 @@ func (s *MySuite) TestPutBucketErrors(c *C) {
defer testServer.Close() defer testServer.Close()
client := http.Client{} client := http.Client{}
typedDriver.On("CreateBucket", "foo").Return(drivers.BucketNameInvalid{}).Once() typedDriver.On("CreateBucket", "foo", "private").Return(drivers.BucketNameInvalid{}).Once()
request, err := http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString("")) request, err := http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString(""))
c.Assert(err, IsNil) c.Assert(err, IsNil)
request.Header.Add("x-amz-acl", "private")
response, err := client.Do(request) response, err := client.Do(request)
c.Assert(err, IsNil) c.Assert(err, IsNil)
verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest) verifyError(c, response, "InvalidBucketName", "The specified bucket is not valid.", http.StatusBadRequest)
typedDriver.On("CreateBucket", "foo").Return(drivers.BucketExists{}).Once() typedDriver.On("CreateBucket", "foo", "private").Return(drivers.BucketExists{}).Once()
request, err = http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString("")) request, err = http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString(""))
c.Assert(err, IsNil) c.Assert(err, IsNil)
request.Header.Add("x-amz-acl", "private")
response, err = client.Do(request) response, err = client.Do(request)
c.Assert(err, IsNil) c.Assert(err, IsNil)
verifyError(c, response, "BucketAlreadyExists", "The requested bucket name is not available.", http.StatusConflict) verifyError(c, response, "BucketAlreadyExists", "The requested bucket name is not available.", http.StatusConflict)
typedDriver.On("CreateBucket", "foo").Return(drivers.BackendCorrupted{}).Once() typedDriver.On("CreateBucket", "foo", "private").Return(drivers.BackendCorrupted{}).Once()
request, err = http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString("")) request, err = http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString(""))
c.Assert(err, IsNil) c.Assert(err, IsNil)
request.Header.Add("x-amz-acl", "private")
response, err = client.Do(request) response, err = client.Do(request)
c.Assert(err, IsNil) c.Assert(err, IsNil)
verifyError(c, response, "InternalError", "We encountered an internal error, please try again.", http.StatusInternalServerError) verifyError(c, response, "InternalError", "We encountered an internal error, please try again.", http.StatusInternalServerError)
typedDriver.On("CreateBucket", "foo", "unknown").Return(nil).Once()
request, err = http.NewRequest("PUT", testServer.URL+"/foo", bytes.NewBufferString(""))
c.Assert(err, IsNil)
request.Header.Add("x-amz-acl", "unknown")
response, err = client.Do(request)
c.Assert(err, IsNil)
verifyError(c, response, "NotImplemented", "A header you provided implies functionality that is not implemented.", http.StatusNotImplemented)
} }
func (s *MySuite) TestGetObjectErrors(c *C) { func (s *MySuite) TestGetObjectErrors(c *C) {

View File

@ -16,15 +16,13 @@
package api package api
import ( import "net/http"
"net/http"
"strings"
)
type contentType int type contentType int
const ( const (
xmlContentType contentType = iota unknownContentType contentType = iota
xmlContentType
jsonContentType jsonContentType
) )
@ -32,8 +30,14 @@ const (
func getContentType(req *http.Request) contentType { func getContentType(req *http.Request) contentType {
acceptHeader := req.Header.Get("Accept") acceptHeader := req.Header.Get("Accept")
switch { switch {
case strings.HasPrefix(acceptHeader, "application/json"): case acceptHeader == "application/json":
return jsonContentType return jsonContentType
case acceptHeader == "application/xml":
return xmlContentType
case acceptHeader == "*/*":
return xmlContentType
case acceptHeader != "":
return unknownContentType
default: default:
return xmlContentType return xmlContentType
} }
@ -44,9 +48,12 @@ func getContentTypeString(content contentType) string {
switch content { switch content {
case jsonContentType: case jsonContentType:
{ {
return "application/json" return "application/json"
} }
case xmlContentType:
{
return "application/xml"
}
default: default:
{ {
return "application/xml" return "application/xml"

View File

@ -38,7 +38,7 @@ type ErrorResponse struct {
HostID string HostID string
} }
// Error codes, non exhaustive list // Error codes, non exhaustive list - http://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html
const ( const (
AccessDenied = iota AccessDenied = iota
BadDigest BadDigest
@ -63,6 +63,11 @@ const (
TooManyBuckets TooManyBuckets
) )
// Error codes, non exhaustive list - standard HTTP errors
const (
NotAcceptable = iota + 21
)
// Error code to Error structure map // Error code to Error structure map
var errorCodeResponse = map[int]Error{ var errorCodeResponse = map[int]Error{
AccessDenied: { AccessDenied: {
@ -170,6 +175,11 @@ var errorCodeResponse = map[int]Error{
Description: "You have attempted to create more buckets than allowed.", Description: "You have attempted to create more buckets than allowed.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
NotAcceptable: {
Code: "NotAcceptable",
Description: "The requested resource is only capable of generating content not acceptable according to the Accept headers sent in the request.",
HTTPStatusCode: http.StatusNotAcceptable,
},
} }
// errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown // errorCodeError provides errorCode to Error. It returns empty if the code provided is unknown

View File

@ -52,6 +52,9 @@ func encodeErrorResponse(response interface{}, acceptsType contentType) []byte {
encoder = xml.NewEncoder(&bytesBuffer) encoder = xml.NewEncoder(&bytesBuffer)
case jsonContentType: case jsonContentType:
encoder = json.NewEncoder(&bytesBuffer) encoder = json.NewEncoder(&bytesBuffer)
// by default even if unknown Accept header received handle it by sending XML contenttype response
default:
encoder = xml.NewEncoder(&bytesBuffer)
} }
encoder.Encode(response) encoder.Encode(response)
return bytesBuffer.Bytes() return bytesBuffer.Bytes()
@ -77,7 +80,7 @@ func setRangeObjectHeaders(w http.ResponseWriter, metadata drivers.ObjectMetadat
w.Header().Set("Content-Range", contentRange.getContentRange()) w.Header().Set("Content-Range", contentRange.getContentRange())
} }
func encodeResponse(response interface{}, acceptsType contentType) []byte { func encodeSuccessResponse(response interface{}, acceptsType contentType) []byte {
var encoder encoder var encoder encoder
var bytesBuffer bytes.Buffer var bytesBuffer bytes.Buffer
switch acceptsType { switch acceptsType {

14
pkg/api/utils.go Normal file
View File

@ -0,0 +1,14 @@
package api
import (
"encoding/base64"
"strings"
)
func isValidMD5(md5 string) bool {
_, err := base64.StdEncoding.DecodeString(strings.TrimSpace(md5))
if err != nil {
return false
}
return true
}

View File

@ -31,9 +31,13 @@ type donut struct {
// config files used inside Donut // config files used inside Donut
const ( const (
// donut object metadata and config
donutObjectMetadataConfig = "donutObjectMetadata.json" donutObjectMetadataConfig = "donutObjectMetadata.json"
objectMetadataConfig = "objectMetadata.json"
donutConfig = "donutMetadata.json" donutConfig = "donutMetadata.json"
// bucket, object metadata
bucketMetadataConfig = "bucketMetadata.json"
objectMetadataConfig = "objectMetadata.json"
) )
// attachDonutNode - wrapper function to instantiate a new node for associated donut // attachDonutNode - wrapper function to instantiate a new node for associated donut

View File

@ -35,22 +35,27 @@ import (
// internal struct carrying bucket specific information // internal struct carrying bucket specific information
type bucket struct { type bucket struct {
name string name string
acl string
time time.Time
donutName string donutName string
nodes map[string]Node nodes map[string]Node
objects map[string]Object objects map[string]Object
} }
// NewBucket - instantiate a new bucket // NewBucket - instantiate a new bucket
func NewBucket(bucketName, donutName string, nodes map[string]Node) (Bucket, error) { func NewBucket(bucketName, aclType, donutName string, nodes map[string]Node) (Bucket, error) {
errParams := map[string]string{ errParams := map[string]string{
"bucketName": bucketName, "bucketName": bucketName,
"donutName": donutName, "donutName": donutName,
"aclType": aclType,
} }
if strings.TrimSpace(bucketName) == "" || strings.TrimSpace(donutName) == "" { if strings.TrimSpace(bucketName) == "" || strings.TrimSpace(donutName) == "" {
return nil, iodine.New(errors.New("invalid argument"), errParams) return nil, iodine.New(errors.New("invalid argument"), errParams)
} }
b := bucket{} b := bucket{}
b.name = bucketName b.name = bucketName
b.acl = aclType
b.time = time.Now()
b.donutName = donutName b.donutName = donutName
b.objects = make(map[string]Object) b.objects = make(map[string]Object)
b.nodes = nodes b.nodes = nodes

View File

@ -32,7 +32,7 @@ type ObjectStorage interface {
GetBucketMetadata(bucket string) (map[string]string, error) GetBucketMetadata(bucket string) (map[string]string, error)
SetBucketMetadata(bucket string, metadata map[string]string) error SetBucketMetadata(bucket string, metadata map[string]string) error
ListBuckets() ([]string, error) ListBuckets() ([]string, error)
MakeBucket(bucket string) error MakeBucket(bucket, acl string) error
// Bucket Operations // Bucket Operations
ListObjects(bucket, prefix, marker, delim string, maxKeys int) (result []string, prefixes []string, isTruncated bool, err error) ListObjects(bucket, prefix, marker, delim string, maxKeys int) (result []string, prefixes []string, isTruncated bool, err error)

View File

@ -75,10 +75,10 @@ func (s *MySuite) TestBucketWithoutNameFails(c *C) {
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
// fail to create new bucket without a name // fail to create new bucket without a name
err = donut.MakeBucket("") err = donut.MakeBucket("", "private")
c.Assert(err, Not(IsNil)) c.Assert(err, Not(IsNil))
err = donut.MakeBucket(" ") err = donut.MakeBucket(" ", "private")
c.Assert(err, Not(IsNil)) c.Assert(err, Not(IsNil))
} }
@ -90,7 +90,7 @@ func (s *MySuite) TestEmptyBucket(c *C) {
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(donut.MakeBucket("foo"), IsNil) c.Assert(donut.MakeBucket("foo", "private"), IsNil)
// check if bucket is empty // check if bucket is empty
objects, _, istruncated, err := donut.ListObjects("foo", "", "", "", 1) objects, _, istruncated, err := donut.ListObjects("foo", "", "", "", 1)
c.Assert(err, IsNil) c.Assert(err, IsNil)
@ -106,7 +106,7 @@ func (s *MySuite) TestMakeBucketAndList(c *C) {
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
// create bucket // create bucket
err = donut.MakeBucket("foo") err = donut.MakeBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
// check bucket exists // check bucket exists
@ -122,10 +122,10 @@ func (s *MySuite) TestMakeBucketWithSameNameFails(c *C) {
defer os.RemoveAll(root) defer os.RemoveAll(root)
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = donut.MakeBucket("foo") err = donut.MakeBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = donut.MakeBucket("foo") err = donut.MakeBucket("foo", "private")
c.Assert(err, Not(IsNil)) c.Assert(err, Not(IsNil))
} }
@ -137,17 +137,17 @@ func (s *MySuite) TestCreateMultipleBucketsAndList(c *C) {
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
// add a second bucket // add a second bucket
err = donut.MakeBucket("foo") err = donut.MakeBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = donut.MakeBucket("bar") err = donut.MakeBucket("bar", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
buckets, err := donut.ListBuckets() buckets, err := donut.ListBuckets()
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(buckets, DeepEquals, []string{"bar", "foo"}) c.Assert(buckets, DeepEquals, []string{"bar", "foo"})
err = donut.MakeBucket("foobar") err = donut.MakeBucket("foobar", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
buckets, err = donut.ListBuckets() buckets, err = donut.ListBuckets()
@ -185,7 +185,7 @@ func (s *MySuite) TestNewObjectMetadata(c *C) {
expectedMd5Sum := hex.EncodeToString(hasher.Sum(nil)) expectedMd5Sum := hex.EncodeToString(hasher.Sum(nil))
reader := ioutil.NopCloser(bytes.NewReader([]byte(data))) reader := ioutil.NopCloser(bytes.NewReader([]byte(data)))
err = donut.MakeBucket("foo") err = donut.MakeBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = donut.PutObject("foo", "obj", expectedMd5Sum, reader, metadata) err = donut.PutObject("foo", "obj", expectedMd5Sum, reader, metadata)
@ -222,7 +222,7 @@ func (s *MySuite) TestNewObjectCanBeWritten(c *C) {
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
err = donut.MakeBucket("foo") err = donut.MakeBucket("foo", "private")
c.Assert(err, IsNil) c.Assert(err, IsNil)
metadata := make(map[string]string) metadata := make(map[string]string)
@ -263,7 +263,7 @@ func (s *MySuite) TestMultipleNewObjects(c *C) {
donut, err := NewDonut("test", createTestNodeDiskMap(root)) donut, err := NewDonut("test", createTestNodeDiskMap(root))
c.Assert(err, IsNil) c.Assert(err, IsNil)
c.Assert(donut.MakeBucket("foo"), IsNil) c.Assert(donut.MakeBucket("foo", "private"), IsNil)
one := ioutil.NopCloser(bytes.NewReader([]byte("one"))) one := ioutil.NopCloser(bytes.NewReader([]byte("one")))
err = donut.PutObject("foo", "obj1", "", one, nil) err = donut.PutObject("foo", "obj1", "", one, nil)

View File

@ -28,11 +28,11 @@ import (
) )
// MakeBucket - make a new bucket // MakeBucket - make a new bucket
func (d donut) MakeBucket(bucket string) error { func (d donut) MakeBucket(bucket, acl string) error {
if bucket == "" || strings.TrimSpace(bucket) == "" { if bucket == "" || strings.TrimSpace(bucket) == "" {
return iodine.New(errors.New("invalid argument"), nil) return iodine.New(errors.New("invalid argument"), nil)
} }
return d.makeDonutBucket(bucket) return d.makeDonutBucket(bucket, acl)
} }
// GetBucketMetadata - get bucket metadata // GetBucketMetadata - get bucket metadata
@ -47,6 +47,7 @@ func (d donut) GetBucketMetadata(bucket string) (map[string]string, error) {
metadata := make(map[string]string) metadata := make(map[string]string)
metadata["name"] = bucket metadata["name"] = bucket
metadata["created"] = time.Now().Format(time.RFC3339Nano) // TODO get this, from whatever is written from SetBucketMetadata metadata["created"] = time.Now().Format(time.RFC3339Nano) // TODO get this, from whatever is written from SetBucketMetadata
metadata["acl"] = "private"
return metadata, nil return metadata, nil
} }

View File

@ -25,7 +25,8 @@ import (
"github.com/minio-io/minio/pkg/iodine" "github.com/minio-io/minio/pkg/iodine"
) )
func (d donut) makeDonutBucket(bucketName string) error { // TODO we have to store the acl's
func (d donut) makeDonutBucket(bucketName, acl string) error {
err := d.getDonutBuckets() err := d.getDonutBuckets()
if err != nil { if err != nil {
return iodine.New(err, nil) return iodine.New(err, nil)
@ -33,7 +34,7 @@ func (d donut) makeDonutBucket(bucketName string) error {
if _, ok := d.buckets[bucketName]; ok { if _, ok := d.buckets[bucketName]; ok {
return iodine.New(errors.New("bucket exists"), nil) return iodine.New(errors.New("bucket exists"), nil)
} }
bucket, err := NewBucket(bucketName, d.name, d.nodes) bucket, err := NewBucket(bucketName, acl, d.name, d.nodes)
if err != nil { if err != nil {
return iodine.New(err, nil) return iodine.New(err, nil)
} }
@ -74,7 +75,7 @@ func (d donut) getDonutBuckets() error {
} }
bucketName := splitDir[0] bucketName := splitDir[0]
// we dont need this NewBucket once we cache from makeDonutBucket() // we dont need this NewBucket once we cache from makeDonutBucket()
bucket, err := NewBucket(bucketName, d.name, d.nodes) bucket, err := NewBucket(bucketName, "private", d.name, d.nodes)
if err != nil { if err != nil {
return iodine.New(err, nil) return iodine.New(err, nil)
} }

View File

@ -48,14 +48,14 @@ func APITestSuite(c *check.C, create func() Driver) {
func testCreateBucket(c *check.C, create func() Driver) { func testCreateBucket(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
} }
func testMultipleObjectCreation(c *check.C, create func() Driver) { func testMultipleObjectCreation(c *check.C, create func() Driver) {
objects := make(map[string][]byte) objects := make(map[string][]byte)
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
randomPerm := rand.Perm(10) randomPerm := rand.Perm(10)
@ -94,7 +94,7 @@ func testMultipleObjectCreation(c *check.C, create func() Driver) {
func testPaging(c *check.C, create func() Driver) { func testPaging(c *check.C, create func() Driver) {
drivers := create() drivers := create()
drivers.CreateBucket("bucket") drivers.CreateBucket("bucket", "")
resources := BucketResourcesMetadata{} resources := BucketResourcesMetadata{}
objects, resources, err := drivers.ListObjects("bucket", resources) objects, resources, err := drivers.ListObjects("bucket", resources)
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
@ -198,7 +198,7 @@ func testPaging(c *check.C, create func() Driver) {
func testObjectOverwriteFails(c *check.C, create func() Driver) { func testObjectOverwriteFails(c *check.C, create func() Driver) {
drivers := create() drivers := create()
drivers.CreateBucket("bucket") drivers.CreateBucket("bucket", "")
hasher1 := md5.New() hasher1 := md5.New()
hasher1.Write([]byte("one")) hasher1.Write([]byte("one"))
@ -227,7 +227,7 @@ func testNonExistantBucketOperations(c *check.C, create func() Driver) {
func testBucketMetadata(c *check.C, create func() Driver) { func testBucketMetadata(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("string") err := drivers.CreateBucket("string", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
metadata, err := drivers.GetBucketMetadata("string") metadata, err := drivers.GetBucketMetadata("string")
@ -237,15 +237,15 @@ func testBucketMetadata(c *check.C, create func() Driver) {
func testBucketRecreateFails(c *check.C, create func() Driver) { func testBucketRecreateFails(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("string") err := drivers.CreateBucket("string", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
err = drivers.CreateBucket("string") err = drivers.CreateBucket("string", "")
c.Assert(err, check.Not(check.IsNil)) c.Assert(err, check.Not(check.IsNil))
} }
func testPutObjectInSubdir(c *check.C, create func() Driver) { func testPutObjectInSubdir(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
hasher := md5.New() hasher := md5.New()
@ -270,7 +270,7 @@ func testListBuckets(c *check.C, create func() Driver) {
c.Assert(len(buckets), check.Equals, 0) c.Assert(len(buckets), check.Equals, 0)
// add one and test exists // add one and test exists
err = drivers.CreateBucket("bucket1") err = drivers.CreateBucket("bucket1", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
buckets, err = drivers.ListBuckets() buckets, err = drivers.ListBuckets()
@ -278,7 +278,7 @@ func testListBuckets(c *check.C, create func() Driver) {
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
// add two and test exists // add two and test exists
err = drivers.CreateBucket("bucket2") err = drivers.CreateBucket("bucket2", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
buckets, err = drivers.ListBuckets() buckets, err = drivers.ListBuckets()
@ -286,7 +286,7 @@ func testListBuckets(c *check.C, create func() Driver) {
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
// add three and test exists + prefix // add three and test exists + prefix
err = drivers.CreateBucket("bucket22") err = drivers.CreateBucket("bucket22", "")
buckets, err = drivers.ListBuckets() buckets, err = drivers.ListBuckets()
c.Assert(len(buckets), check.Equals, 3) c.Assert(len(buckets), check.Equals, 3)
@ -299,8 +299,8 @@ func testListBucketsOrder(c *check.C, create func() Driver) {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
drivers := create() drivers := create()
// add one and test exists // add one and test exists
drivers.CreateBucket("bucket1") drivers.CreateBucket("bucket1", "")
drivers.CreateBucket("bucket2") drivers.CreateBucket("bucket2", "")
buckets, err := drivers.ListBuckets() buckets, err := drivers.ListBuckets()
c.Assert(len(buckets), check.Equals, 2) c.Assert(len(buckets), check.Equals, 2)
@ -321,7 +321,7 @@ func testListObjectsTestsForNonExistantBucket(c *check.C, create func() Driver)
func testNonExistantObjectInBucket(c *check.C, create func() Driver) { func testNonExistantObjectInBucket(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
var byteBuffer bytes.Buffer var byteBuffer bytes.Buffer
@ -343,7 +343,7 @@ func testNonExistantObjectInBucket(c *check.C, create func() Driver) {
func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Driver) { func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
err = drivers.CreateObject("bucket", "dir1/dir2/object", "", "", bytes.NewBufferString("hello world")) err = drivers.CreateObject("bucket", "dir1/dir2/object", "", "", bytes.NewBufferString("hello world"))
@ -386,7 +386,7 @@ func testGetDirectoryReturnsObjectNotFound(c *check.C, create func() Driver) {
func testDefaultContentType(c *check.C, create func() Driver) { func testDefaultContentType(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
// test empty // test empty
@ -408,10 +408,9 @@ func testDefaultContentType(c *check.C, create func() Driver) {
c.Assert(metadata.ContentType, check.Equals, "application/json") c.Assert(metadata.ContentType, check.Equals, "application/json")
} }
/*
func testContentMd5Set(c *check.C, create func() Driver) { func testContentMd5Set(c *check.C, create func() Driver) {
drivers := create() drivers := create()
err := drivers.CreateBucket("bucket") err := drivers.CreateBucket("bucket", "")
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
// test md5 invalid // test md5 invalid
@ -420,4 +419,3 @@ func testContentMd5Set(c *check.C, create func() Driver) {
err = drivers.CreateObject("bucket", "two", "", "NWJiZjVhNTIzMjhlNzQzOWFlNmU3MTlkZmU3MTIyMDA=", bytes.NewBufferString("one")) err = drivers.CreateObject("bucket", "two", "", "NWJiZjVhNTIzMjhlNzQzOWFlNmU3MTlkZmU3MTIyMDA=", bytes.NewBufferString("one"))
c.Assert(err, check.IsNil) c.Assert(err, check.IsNil)
} }
*/

View File

@ -39,6 +39,7 @@ import (
// 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
path string
} }
const ( const (
@ -82,6 +83,7 @@ func Start(path string) (chan<- string, <-chan error, drivers.Driver) {
s := new(donutDriver) s := new(donutDriver)
s.donut = donut s.donut = donut
s.path = path
go start(ctrlChannel, errorChannel, s) go start(ctrlChannel, errorChannel, s)
return ctrlChannel, errorChannel, s return ctrlChannel, errorChannel, s
@ -117,11 +119,14 @@ func (d donutDriver) ListBuckets() (results []drivers.BucketMetadata, err error)
} }
// CreateBucket creates a new bucket // CreateBucket creates a new bucket
func (d donutDriver) CreateBucket(bucketName string) error { func (d donutDriver) CreateBucket(bucketName, acl string) error {
if drivers.IsValidBucket(bucketName) && !strings.Contains(bucketName, ".") { if !drivers.IsValidBucketACL(acl) {
return d.donut.MakeBucket(bucketName) return iodine.New(drivers.InvalidACL{ACL: acl}, nil)
} }
return iodine.New(errors.New("Invalid bucket"), map[string]string{"bucket": bucketName}) if drivers.IsValidBucket(bucketName) && !strings.Contains(bucketName, ".") {
return d.donut.MakeBucket(bucketName, acl)
}
return iodine.New(drivers.BucketNameInvalid{Bucket: bucketName}, nil)
} }
// GetBucketMetadata retrieves an bucket's metadata // GetBucketMetadata retrieves an bucket's metadata
@ -137,9 +142,14 @@ func (d donutDriver) GetBucketMetadata(bucketName string) (drivers.BucketMetadat
if err != nil { if err != nil {
return drivers.BucketMetadata{}, iodine.New(err, nil) return drivers.BucketMetadata{}, iodine.New(err, nil)
} }
acl, ok := metadata["acl"]
if !ok {
return drivers.BucketMetadata{}, iodine.New(drivers.BackendCorrupted{Path: d.path}, nil)
}
bucketMetadata := drivers.BucketMetadata{ bucketMetadata := drivers.BucketMetadata{
Name: metadata["name"], Name: metadata["name"],
Created: created, Created: created,
ACL: drivers.BucketACL(acl),
} }
return bucketMetadata, nil return bucketMetadata, nil
} }

View File

@ -27,7 +27,7 @@ import (
type Driver interface { type Driver interface {
// Bucket Operations // Bucket Operations
ListBuckets() ([]BucketMetadata, error) ListBuckets() ([]BucketMetadata, error)
CreateBucket(bucket string) error CreateBucket(bucket, acl string) error
GetBucketMetadata(bucket string) (BucketMetadata, error) GetBucketMetadata(bucket string) (BucketMetadata, error)
// Object Operations // Object Operations
@ -38,10 +38,40 @@ type Driver interface {
CreateObject(bucket string, key string, contentType string, md5sum string, data io.Reader) error CreateObject(bucket string, key string, contentType string, md5sum string, data io.Reader) error
} }
// BucketACL - bucket level access control
type BucketACL string
// different types of ACL's currently supported for buckets
const (
BucketPrivate = BucketACL("private")
BucketPublicRead = BucketACL("public-read")
BucketPublicReadWrite = BucketACL("public-read-write")
)
func (b BucketACL) String() string {
return string(b)
}
// IsPrivate - is acl Private
func (b BucketACL) IsPrivate() bool {
return b == BucketACL("private")
}
// IsPublicRead - is acl PublicRead
func (b BucketACL) IsPublicRead() bool {
return b == BucketACL("public-read")
}
// IsPublicReadWrite - is acl PublicReadWrite
func (b BucketACL) IsPublicReadWrite() bool {
return b == BucketACL("public-read-write")
}
// BucketMetadata - name and create date // BucketMetadata - name and create date
type BucketMetadata struct { type BucketMetadata struct {
Name string Name string
Created time.Time Created time.Time
ACL BucketACL
} }
// ObjectMetadata - object key and its relevant metadata // ObjectMetadata - object key and its relevant metadata
@ -98,6 +128,23 @@ func GetMode(resources BucketResourcesMetadata) FilterMode {
return f return f
} }
// IsValidBucketACL - is provided acl string supported
func IsValidBucketACL(acl string) bool {
switch acl {
case "private":
fallthrough
case "public-read":
fallthrough
case "public-read-write":
return true
case "":
// by default its "private"
return true
default:
return false
}
}
// IsDelimiterPrefixSet Delimiter and Prefix set // IsDelimiterPrefixSet Delimiter and Prefix set
func (b BucketResourcesMetadata) IsDelimiterPrefixSet() bool { func (b BucketResourcesMetadata) IsDelimiterPrefixSet() bool {
return b.Mode == DelimiterPrefixMode return b.Mode == DelimiterPrefixMode

View File

@ -54,6 +54,17 @@ type DigestError struct {
Md5 string Md5 string
} }
/// ACL related errors
// InvalidACL - acl invalid
type InvalidACL struct {
ACL string
}
func (e InvalidACL) Error() string {
return "Requested ACL is " + e.ACL + " invalid"
}
/// Bucket related errors /// Bucket related errors
// BucketNameInvalid - bucketname provided is invalid // BucketNameInvalid - bucketname provided is invalid

View File

@ -188,13 +188,16 @@ func (memory *memoryDriver) CreateObject(bucket, key, contentType, md5sum string
} }
// CreateBucket - create bucket in memory // CreateBucket - create bucket in memory
func (memory *memoryDriver) CreateBucket(bucketName string) error { func (memory *memoryDriver) CreateBucket(bucketName, acl string) error {
memory.lock.RLock() memory.lock.RLock()
if !drivers.IsValidBucket(bucketName) { if !drivers.IsValidBucket(bucketName) {
memory.lock.RUnlock() memory.lock.RUnlock()
return drivers.BucketNameInvalid{Bucket: bucketName} return drivers.BucketNameInvalid{Bucket: bucketName}
} }
if !drivers.IsValidBucketACL(acl) {
memory.lock.RUnlock()
return drivers.InvalidACL{ACL: acl}
}
if _, ok := memory.bucketMetadata[bucketName]; ok == true { if _, ok := memory.bucketMetadata[bucketName]; ok == true {
memory.lock.RUnlock() memory.lock.RUnlock()
return drivers.BucketExists{Bucket: bucketName} return drivers.BucketExists{Bucket: bucketName}
@ -205,6 +208,7 @@ func (memory *memoryDriver) CreateBucket(bucketName string) error {
newBucket.metadata = drivers.BucketMetadata{} newBucket.metadata = drivers.BucketMetadata{}
newBucket.metadata.Name = bucketName newBucket.metadata.Name = bucketName
newBucket.metadata.Created = time.Now() newBucket.metadata.Created = time.Now()
newBucket.metadata.ACL = drivers.BucketACL(acl)
memory.lock.Lock() memory.lock.Lock()
defer memory.lock.Unlock() defer memory.lock.Unlock()
memory.bucketMetadata[bucketName] = newBucket memory.bucketMetadata[bucketName] = newBucket
@ -234,7 +238,7 @@ func (memory *memoryDriver) filterDelimiterPrefix(keys []string, key, delimitedN
switch true { switch true {
case key == resources.Prefix: case key == resources.Prefix:
keys = appendUniq(keys, key) keys = appendUniq(keys, key)
// DelimitedName - requires resources.Prefix as it was trimmed off earlier in the flow // DelimitedName - requires resources.Prefix as it was trimmed off earlier in the flow
case key == resources.Prefix+delimitedName: case key == resources.Prefix+delimitedName:
keys = appendUniq(keys, key) keys = appendUniq(keys, key)
case delimitedName != "": case delimitedName != "":
@ -339,8 +343,6 @@ func (memory *memoryDriver) evictObject(key lru.Key, value interface{}) {
k := key.(string) k := key.(string)
memory.totalSize = memory.totalSize - memory.objectMetadata[k].metadata.Size memory.totalSize = memory.totalSize - memory.objectMetadata[k].metadata.Size
log.Println("evicting:", k) log.Println("evicting:", k)
delete(memory.objectMetadata, k) delete(memory.objectMetadata, k)
} }

View File

@ -27,8 +27,8 @@ func (m *Driver) ListBuckets() ([]drivers.BucketMetadata, error) {
} }
// CreateBucket is a mock // CreateBucket is a mock
func (m *Driver) CreateBucket(bucket string) error { func (m *Driver) CreateBucket(bucket, acl string) error {
ret := m.Called(bucket) ret := m.Called(bucket, acl)
r0 := ret.Error(0) r0 := ret.Error(0)