Compare commits

...

38 Commits

Author SHA1 Message Date
Aditya Manthramurthy ee5b3622a5 Evaluate where clause in aggregation queries (#7235) 2019-02-12 13:54:26 -08:00
Krishna Srinivas 14544d8d84 Validate incoming requests (#7234) 2019-02-12 13:24:14 -08:00
Harshavardhana 118270d76f Vendor the upstream changes with Avx512 (#7225)
Thanks to @fwessels we have Avx512 support with 4x improvement
2019-02-12 17:32:27 +05:30
Harshavardhana fef5416b3c Support unknown gateway errors and convert at handler layer (#7219)
Different gateway implementations due to different backend
API errors, might return different unsupported errors at
our handler layer. Current code posed a problem for us because
this information was lost and we would convert it to InternalError
in this situation all S3 clients end up retrying the request.

To avoid this unexpected situation implement a way to support
this cleanly such that the underlying information is not lost
which is returned by gateway.
2019-02-12 14:55:52 +05:30
Harshavardhana 9f87283cd5 Revert and bring back B2 gateway implementation (#7224)
This PR is simply a revert of 3265112d04
just for B2 gateway.
2019-02-12 12:44:22 +05:30
Harshavardhana b8955fe577 Fix DummyHandlers to authorize and send/validate correct XMLs (#7223) 2019-02-11 17:58:26 -08:00
Nitish Tiwari 13c3b8afe2 Update Redis client used for bucket notifications (#7213)
Current Redis client used https://github.com/garyburd/redigo/
has been archived and further development is done at
https://github.com/gomodule/redigo

This commit updates the client library accordingly.

Fixes #6392
2019-02-11 19:06:27 +05:30
Harshavardhana a8cd70f3e5 Remove GPL go-lzo dependency for parquet-go (#7220)
Also remove any other unused dependencies
2019-02-11 14:57:24 +05:30
Harshavardhana 082f777281 Revamp bucket metadata healing (#7208)
Bucket metadata healing in the current code was executed multiple
times each time for a given set. Bucket metadata just like
objects are hashed in accordance with its name on any given set,
to allow hashing to play a role we should let the top level
code decide where to navigate.

Current code also had 3 bucket metadata files hardcoded, whereas
we should make it generic by listing and navigating the .minio.sys
to heal such objects.

We also had another bug where due to isObjectDangling changes
without pre-existing bucket metadata files, we were erroneously
reporting it as grey/corrupted objects.

This PR fixes all of the above items.
2019-02-11 09:23:13 +05:30
John Liu 9600e2b35e Comment Typo: Changed 'jason' to 'json` (#7216) 2019-02-10 05:49:00 -08:00
Prashant Shahi f75f707ff4 Added documentation for MINIO_HTTP_TRACE (#7207) 2019-02-10 11:57:43 +05:30
poornas 40b8d11209 Move metadata into ObjectOptions for NewMultipart and PutObject (#7060) 2019-02-09 11:01:06 +05:30
Sidhartha Mani c1b3f1994b remove unnecessary buffer while discarding stream (#7214) 2019-02-08 19:29:51 -08:00
Adam 18c4ecbbef Fix mc event add event flag (#7210) 2019-02-08 16:18:34 -08:00
ebozduman dd52e5ebe9 Implements dummy tagging handlers for Terraform (#7040) 2019-02-08 16:18:13 -08:00
Praveen raj Mani 8af1f0cc7b Improved error message for user and access key conflict (#7190) 2019-02-07 17:25:58 -08:00
Harshavardhana 85e939636f Fix JSON parser handling for certain objects (#7162)
This PR also adds some comments and simplifies
the code. Primary handling is done to ensure
that we make sure to honor cached buffer.

Added unit tests as well

Fixes #7141
2019-02-07 08:04:42 +05:30
poornas d203e7e1cc azure gateway: return MD5Sum as ETag for S3 API compatibility (#6884)
Fixes #6872.

This PR refactors multipart upload implementation to use a per
part metadata file which is cleaned up at the end of the upload
2019-02-06 16:58:43 -08:00
Aditya Manthramurthy 4aa9ee153b Fix S3 Select request XML parsing (#7202) 2019-02-06 13:25:52 -08:00
Minio Trusted 5fb813a5cc Update yaml files to latest version RELEASE.2019-02-06T21-16-36Z 2019-02-06 21:24:08 +00:00
Harshavardhana 817269475f Make sure to drain body upon an error (#7197)
Also cleanup redundant code and use it at a common place
2019-02-06 12:07:03 -08:00
Krishna Srinivas 2d168b532b Allow format.json healing on dev/test setup (single node XL, all root disks) (#7170) 2019-02-06 11:44:19 -08:00
Aditya Manthramurthy fd4e15c116 Flush the records staging buffer periodically (#7193)
- Staging buffer is flushed every 500ms. In cases where the result
  records are slowly generated (e.g. when a where condition
  matches very few records), this change causes the server to send
  results even though the staging buffer is not full.

- Refactor messageWriter code to use simpler channel based
  co-ordination instead of atomic variables.
2019-02-06 16:03:05 +05:30
Krishna Srinivas 3dfbe0f68c Send white spaces to client till completeMultipart() process completes (#7198) 2019-02-05 20:58:09 -08:00
Harshavardhana 30135eed86 Redo how to handle stale dangling files (#7171)
foo.CORRUPTED should never be created because when
multiple sets are involved we would hash the file
to wrong a location, this PR removes the code.

But allows DeleteBucket() to work properly to delete
dangling buckets/objects. Also adds another option
to Healing where a user needs to specify `--remove`
such that all dangling objects will be deleted with
user confirmation.
2019-02-05 17:58:48 -08:00
Harshavardhana e4081aee62 Added support for reading body in STS API (#7188)
STS API supports both URL query params and reading
from a body.
2019-02-05 15:47:11 -08:00
kannappanr df418a2783 Create Cors handler with permissive configuration (#7186)
Create new Cors handler allowing all origins with all standard
methods with any header and credentials.

Fixes #7181
2019-02-05 14:06:52 -08:00
kannappanr 9a65f6dc97 Remove duplicate code in object-handlers.go (#7176)
removed duplicate code in CompleteMultipartUploadHandler
and CopyObjectPartHandler.
2019-02-05 13:36:38 -08:00
Aditya Manthramurthy f04f8bbc78 Add support for Timestamp data type in SQL Select (#7185)
This change adds support for casting strings to Timestamp via CAST:
`CAST('2010T' AS TIMESTAMP)`

It also implements the following date-time functions:
  - UTCNOW()
  - DATE_ADD()
  - DATE_DIFF()
  - EXTRACT()

For values passed to these functions, date-types are automatically
inferred.
2019-02-04 20:54:45 -08:00
Harshavardhana ea6d61ab1f Use loadCachedConfigs appropriately to load ENVs (#7187) 2019-02-04 10:31:11 +05:30
Krishna Srinivas 6f08edfb36 Use O_EXCL when creating file as we never overwrite an existing file (#7189) 2019-02-01 19:01:06 -08:00
Sidhartha Mani e9fdea05c6 Enable CI control from repository: Add Dockerfile.simpleci (#7122) 2019-02-01 12:04:28 -08:00
Harshavardhana e005910051 Add more information in our select docs (#7177) 2019-02-01 11:34:56 -08:00
Anis Elleuch de2c106386 xl: ListObjectParts uses the latest valid xl meta (#7184)
ListObjectParts is using xl.readXLMetaParts which picks the first
xl meta found in any disk, which is an inconsistent information.

E.g.: In a middle of a multipart upload, one node can go offline
and get back later with an outdated multipart information.
2019-02-01 08:58:41 -08:00
Harshavardhana 32a6dd1dd6 Remove sporadic tests which fail on windows (#7178) 2019-01-31 16:48:47 -08:00
Harshavardhana 432aec73d9 Return proper errors for invalid bodies (#7179) 2019-01-31 07:19:09 -08:00
Anis Elleuch 36dae04671 CopyObjectPart: remove duplicated etag decryption (#7174) 2019-01-30 19:33:31 -08:00
Minio Trusted 9dc9f03c02 Update yaml files to latest version RELEASE.2019-01-31T00-31-19Z 2019-01-31 00:37:43 +00:00
196 changed files with 8839 additions and 9574 deletions
+49
View File
@@ -0,0 +1,49 @@
#-------------------------------------------------------------
# Stage 1: Build and Unit tests
#-------------------------------------------------------------
FROM golang:1.11.4
COPY . /go/src/github.com/minio/minio
WORKDIR /go/src/github.com/minio/minio
RUN apt-get update && apt-get install -y jq
RUN make
RUN bash -c 'diff -au <(gofmt -s -d cmd) <(printf "")'
RUN bash -c 'diff -au <(gofmt -s -d pkg) <(printf "")'
RUN for d in $(go list ./... | grep -v browser); do go test -v -race --timeout 15m "$d"; done
RUN make verify
RUN make coverage || true
#-------------------------------------------------------------
# Stage 2: Test Frontend
#-------------------------------------------------------------
FROM node:10.15-stretch-slim
COPY browser /minio/browser
WORKDIR /minio/browser
RUN yarn
RUN yarn test
#-------------------------------------------------------------
# Stage 3: Run Gateway Tests
#-------------------------------------------------------------
FROM ubuntu:16.04
COPY --from=0 /go/src/github.com/minio/minio/minio ./minio
COPY buildscripts/gateway-tests.sh ./gateway-tests.sh
RUN apt-get update && apt-get install -y git wget jq curl dnsmasq
RUN wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gz && \
tar -C /usr/local -xzf go1.11.4.linux-amd64.tar.gz
ENV DEBIAN_FRONTEND noninteractive
ENV LANG C.UTF-8
ENV GOROOT /usr/local/go
RUN mkdir -p /go
ENV GOPATH /go
ENV PATH $GOPATH/bin:$GOROOT/bin:$PATH
RUN ./gateway-tests.sh
+60
View File
@@ -0,0 +1,60 @@
#!/bin/bash
#
# Minio Cloud Storage, (C) 2019 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.
#
set -e
set -E
set -o pipefail
export SERVER_ENDPOINT=127.0.0.1:24240
export ENABLE_HTTPS=0
export ACCESS_KEY=minio
export SECRET_KEY=minio123
export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=minio123
export AWS_ACCESS_KEY_ID=minio
export AWS_SECRET_ACCESS_KEY=minio123
trap "cat server.log;cat gateway.log" SIGHUP SIGINT SIGTERM
./minio --quiet --json server data --address 127.0.0.1:24242 > server.log &
sleep 3
./minio --quiet --json gateway s3 http://127.0.0.1:24242 --address 127.0.0.1:24240 > gateway.log &
sleep 3
mkdir -p /mint
git clone https://github.com/minio/mint /mint
cd /mint
export MINT_ROOT_DIR=${MINT_ROOT_DIR:-/mint}
export MINT_RUN_CORE_DIR="$MINT_ROOT_DIR/run/core"
export MINT_RUN_SECURITY_DIR="$MINT_ROOT_DIR/run/security"
export WGET="wget --quiet --no-check-certificate"
go get github.com/go-ini/ini
./create-data-files.sh
./preinstall.sh
# install mint app packages
for pkg in "build"/*/install.sh; do
echo "Running $pkg"
$pkg
done
./postinstall.sh
/mint/entrypoint.sh || cat server.log gateway.log fail
+8 -8
View File
@@ -64,21 +64,21 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
// Allow getBucketACL if policy action is set, since this is a dummy call // Allow getBucketACL if policy action is set, since this is a dummy call
// we are simply re-purposing the bucketPolicyAction. // we are simply re-purposing the bucketPolicyAction.
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Before proceeding validate if bucket exists. // Before proceeding validate if bucket exists.
_, err := objAPI.GetBucketInfo(ctx, bucket) _, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -92,7 +92,7 @@ func (api objectAPIHandlers) GetBucketACLHandler(w http.ResponseWriter, r *http.
Permission: "FULL_CONTROL", Permission: "FULL_CONTROL",
}) })
if err := xml.NewEncoder(w).Encode(acl); err != nil { if err := xml.NewEncoder(w).Encode(acl); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -114,21 +114,21 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
// Allow getObjectACL if policy action is set, since this is a dummy call // Allow getObjectACL if policy action is set, since this is a dummy call
// we are simply re-purposing the bucketPolicyAction. // we are simply re-purposing the bucketPolicyAction.
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Before proceeding validate if object exists. // Before proceeding validate if object exists.
_, err := objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{}) _, err := objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -142,7 +142,7 @@ func (api objectAPIHandlers) GetObjectACLHandler(w http.ResponseWriter, r *http.
Permission: "FULL_CONTROL", Permission: "FULL_CONTROL",
}) })
if err := xml.NewEncoder(w).Encode(acl); err != nil { if err := xml.NewEncoder(w).Encode(acl); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
+160 -326
View File
File diff suppressed because it is too large Load Diff
+12 -84
View File
@@ -270,10 +270,16 @@ func prepareAdminXLTestBed() (*adminXLTestBed, error) {
// Init global heal state // Init global heal state
initAllHealState(globalIsXL) initAllHealState(globalIsXL)
globalNotificationSys = NewNotificationSys(globalServerConfig, globalEndpoints) globalConfigSys = NewConfigSys()
globalIAMSys = NewIAMSys()
globalIAMSys.Init(objLayer)
// Create new policy system.
globalPolicySys = NewPolicySys() globalPolicySys = NewPolicySys()
globalPolicySys.Init(objLayer)
globalNotificationSys = NewNotificationSys(globalServerConfig, globalEndpoints)
globalNotificationSys.Init(objLayer)
// Setup admin mgmt REST API handlers. // Setup admin mgmt REST API handlers.
adminRouter := mux.NewRouter() adminRouter := mux.NewRouter()
@@ -309,7 +315,7 @@ func (atb *adminXLTestBed) GenerateHealTestData(t *testing.T) {
objectName := fmt.Sprintf("%s-%d", objName, i) objectName := fmt.Sprintf("%s-%d", objName, i)
_, err = atb.objLayer.PutObject(context.Background(), bucketName, objectName, _, err = atb.objLayer.PutObject(context.Background(), bucketName, objectName,
mustGetPutObjReader(t, bytes.NewReader([]byte("hello")), mustGetPutObjReader(t, bytes.NewReader([]byte("hello")),
int64(len("hello")), "", ""), nil, ObjectOptions{}) int64(len("hello")), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("Failed to create %s - %v", objectName, t.Fatalf("Failed to create %s - %v", objectName,
err) err)
@@ -321,7 +327,7 @@ func (atb *adminXLTestBed) GenerateHealTestData(t *testing.T) {
{ {
objName := "mpObject" objName := "mpObject"
uploadID, err := atb.objLayer.NewMultipartUpload(context.Background(), bucketName, uploadID, err := atb.objLayer.NewMultipartUpload(context.Background(), bucketName,
objName, nil, ObjectOptions{}) objName, ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("mp new error: %v", err) t.Fatalf("mp new error: %v", err)
} }
@@ -842,8 +848,8 @@ func TestAdminServerInfo(t *testing.T) {
} }
} }
// TestToAdminAPIErr - test for toAdminAPIErr helper function. // TestToAdminAPIErrCode - test for toAdminAPIErrCode helper function.
func TestToAdminAPIErr(t *testing.T) { func TestToAdminAPIErrCode(t *testing.T) {
testCases := []struct { testCases := []struct {
err error err error
expectedAPIErr APIErrorCode expectedAPIErr APIErrorCode
@@ -960,81 +966,3 @@ func collectHealResults(t *testing.T, adminTestBed *adminXLTestBed, bucket,
return res return res
} }
func TestHealStartNStatusHandler(t *testing.T) {
adminTestBed, err := prepareAdminXLTestBed()
if err != nil {
t.Fatal("Failed to initialize a single node XL backend for admin handler tests.")
}
defer adminTestBed.TearDown()
// gen. test data
adminTestBed.GenerateHealTestData(t)
defer adminTestBed.CleanupHealTestData(t)
// Prepare heal-start request to send to the server.
healOpts := madmin.HealOpts{
Recursive: true,
DryRun: false,
}
bucketName, objName := "mybucket", "myobject-0"
var hss madmin.HealStartSuccess
{
req := mkHealStartReq(t, bucketName, objName, healOpts)
rec := httptest.NewRecorder()
adminTestBed.router.ServeHTTP(rec, req)
if http.StatusOK != rec.Code {
t.Errorf("Unexpected status code - got %d but expected %d",
rec.Code, http.StatusOK)
}
err = json.Unmarshal(rec.Body.Bytes(), &hss)
if err != nil {
t.Fatal("unable to unmarshal response")
}
if hss.ClientToken == "" {
t.Errorf("unexpected result")
}
}
{
// test with an invalid client token
req := mkHealStatusReq(t, bucketName, objName, hss.ClientToken+hss.ClientToken)
rec := httptest.NewRecorder()
adminTestBed.router.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("Unexpected status code")
}
}
{
// fetch heal status
results := collectHealResults(t, adminTestBed, bucketName,
objName, hss.ClientToken, 5)
// check if we got back an expected record
foundIt := false
for _, item := range results.Items {
if item.Type == madmin.HealItemObject &&
item.Bucket == bucketName && item.Object == objName {
foundIt = true
}
}
if !foundIt {
t.Error("did not find expected heal record in heal results")
}
// check that the heal settings in the results is the
// same as what we started the heal seq. with.
if results.HealSettings != healOpts {
t.Errorf("unexpected heal settings: %v",
results.HealSettings)
}
if results.Summary == healStoppedStatus {
t.Errorf("heal sequence stopped unexpectedly")
}
}
}
+28 -27
View File
@@ -63,8 +63,7 @@ var (
errHealStopSignalled = fmt.Errorf("heal stop signaled") errHealStopSignalled = fmt.Errorf("heal stop signaled")
errFnHealFromAPIErr = func(ctx context.Context, err error) error { errFnHealFromAPIErr = func(ctx context.Context, err error) error {
errCode := toAPIErrorCode(ctx, err) apiErr := toAPIError(ctx, err)
apiErr := getAPIError(errCode)
return fmt.Errorf("Heal internal error: %s: %s", return fmt.Errorf("Heal internal error: %s: %s",
apiErr.Code, apiErr.Description) apiErr.Code, apiErr.Description)
} }
@@ -150,7 +149,7 @@ func (ahs *allHealState) getHealSequence(path string) (h *healSequence, exists b
return h, exists return h, exists
} }
func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIErrorCode) { func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIError) {
var hsp madmin.HealStopSuccess var hsp madmin.HealStopSuccess
he, exists := ahs.getHealSequence(path) he, exists := ahs.getHealSequence(path)
if !exists { if !exists {
@@ -176,7 +175,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIErrorCode) {
} }
b, err := json.Marshal(&hsp) b, err := json.Marshal(&hsp)
return b, toAdminAPIErrCode(context.Background(), err) return b, toAdminAPIErr(context.Background(), err)
} }
// LaunchNewHealSequence - launches a background routine that performs // LaunchNewHealSequence - launches a background routine that performs
@@ -190,7 +189,7 @@ func (ahs *allHealState) stopHealSequence(path string) ([]byte, APIErrorCode) {
// background routine to clean up heal results after the // background routine to clean up heal results after the
// aforementioned duration. // aforementioned duration.
func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) ( func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
respBytes []byte, errCode APIErrorCode, errMsg string) { respBytes []byte, apiErr APIError, errMsg string) {
existsAndLive := false existsAndLive := false
he, exists := ahs.getHealSequence(h.path) he, exists := ahs.getHealSequence(h.path)
@@ -213,8 +212,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
"(use force-start option to stop and start afresh). " + "(use force-start option to stop and start afresh). " +
fmt.Sprintf("The heal was started by IP %s at %s, token is %s", fmt.Sprintf("The heal was started by IP %s at %s, token is %s",
h.clientAddress, h.startTime.Format(http.TimeFormat), h.clientToken) h.clientAddress, h.startTime.Format(http.TimeFormat), h.clientToken)
return nil, errorCodes.ToAPIErr(ErrHealAlreadyRunning), errMsg
return nil, ErrHealAlreadyRunning, errMsg
} }
} }
@@ -229,7 +227,7 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
errMsg = "The provided heal sequence path overlaps with an existing " + errMsg = "The provided heal sequence path overlaps with an existing " +
fmt.Sprintf("heal path: %s", k) fmt.Sprintf("heal path: %s", k)
return nil, ErrHealOverlappingPaths, errMsg return nil, errorCodes.ToAPIErr(ErrHealOverlappingPaths), errMsg
} }
} }
@@ -246,9 +244,9 @@ func (ahs *allHealState) LaunchNewHealSequence(h *healSequence) (
}) })
if err != nil { if err != nil {
logger.LogIf(h.ctx, err) logger.LogIf(h.ctx, err)
return nil, ErrInternalError, "" return nil, toAPIError(h.ctx, err), ""
} }
return b, ErrNone, "" return b, noError, ""
} }
// PopHealStatusJSON - Called by heal-status API. It fetches the heal // PopHealStatusJSON - Called by heal-status API. It fetches the heal
@@ -546,8 +544,11 @@ func (h *healSequence) traverseAndHeal() {
// Start with format healing // Start with format healing
checkErr(h.healDiskFormat) checkErr(h.healDiskFormat)
// Start healing the config. // Start healing the config prefix.
checkErr(h.healConfig) checkErr(h.healMinioSysMeta(minioConfigPrefix))
// Start healing the bucket config prefix.
checkErr(h.healMinioSysMeta(bucketConfigPrefix))
// Heal buckets and objects // Heal buckets and objects
checkErr(h.healBuckets) checkErr(h.healBuckets)
@@ -559,17 +560,19 @@ func (h *healSequence) traverseAndHeal() {
close(h.traverseAndHealDoneCh) close(h.traverseAndHealDoneCh)
} }
// healConfig - heals config.json, retrun value indicates if a failure occurred. // healMinioSysMeta - heals all files under a given meta prefix, returns a function
func (h *healSequence) healConfig() error { // which in-turn heals the respective meta directory path and any files in int.
func (h *healSequence) healMinioSysMeta(metaPrefix string) func() error {
return func() error {
// Get current object layer instance. // Get current object layer instance.
objectAPI := newObjectLayerFn() objectAPI := newObjectLayerFn()
if objectAPI == nil { if objectAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
// NOTE: Healing on configs is run regardless // NOTE: Healing on meta is run regardless
// of any bucket being selected, this is to ensure that // of any bucket being selected, this is to ensure that
// configs are always uptodate and correct. // meta are always upto date and correct.
marker := "" marker := ""
isTruncated := true isTruncated := true
for isTruncated { for isTruncated {
@@ -585,7 +588,7 @@ func (h *healSequence) healConfig() error {
} }
// Lists all objects under `config` prefix. // Lists all objects under `config` prefix.
objectInfos, err := objectAPI.ListObjectsHeal(h.ctx, minioMetaBucket, minioConfigPrefix, objectInfos, err := objectAPI.ListObjectsHeal(h.ctx, minioMetaBucket, metaPrefix,
marker, "", 1000) marker, "", 1000)
if err != nil { if err != nil {
return errFnHealFromAPIErr(h.ctx, err) return errFnHealFromAPIErr(h.ctx, err)
@@ -596,7 +599,7 @@ func (h *healSequence) healConfig() error {
return errHealStopSignalled return errHealStopSignalled
} }
o := objectInfos.Objects[index] o := objectInfos.Objects[index]
res, herr := objectAPI.HealObject(h.ctx, o.Bucket, o.Name, h.settings.DryRun) res, herr := objectAPI.HealObject(h.ctx, o.Bucket, o.Name, h.settings.DryRun, h.settings.Remove)
// Object might have been deleted, by the time heal // Object might have been deleted, by the time heal
// was attempted we ignore this file an move on. // was attempted we ignore this file an move on.
if isErrObjectNotFound(herr) { if isErrObjectNotFound(herr) {
@@ -614,9 +617,9 @@ func (h *healSequence) healConfig() error {
isTruncated = objectInfos.IsTruncated isTruncated = objectInfos.IsTruncated
marker = objectInfos.NextMarker marker = objectInfos.NextMarker
} }
return nil return nil
} }
}
// healDiskFormat - heals format.json, return value indicates if a // healDiskFormat - heals format.json, return value indicates if a
// failure error occurred. // failure error occurred.
@@ -692,18 +695,16 @@ func (h *healSequence) healBucket(bucket string) error {
return errServerNotInitialized return errServerNotInitialized
} }
results, err := objectAPI.HealBucket(h.ctx, bucket, h.settings.DryRun) result, err := objectAPI.HealBucket(h.ctx, bucket, h.settings.DryRun, h.settings.Remove)
// push any available results before checking for error
for _, result := range results {
if perr := h.pushHealResultItem(result); perr != nil {
return perr
}
}
// handle heal-bucket error // handle heal-bucket error
if err != nil { if err != nil {
return err return err
} }
if err = h.pushHealResultItem(result); err != nil {
return err
}
if !h.settings.Recursive { if !h.settings.Recursive {
if h.objPrefix != "" { if h.objPrefix != "" {
// Check if an object named as the objPrefix exists, // Check if an object named as the objPrefix exists,
@@ -775,7 +776,7 @@ func (h *healSequence) healObject(bucket, object string) error {
return errServerNotInitialized return errServerNotInitialized
} }
hri, err := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun) hri, err := objectAPI.HealObject(h.ctx, bucket, object, h.settings.DryRun, h.settings.Remove)
if isErrObjectNotFound(err) { if isErrObjectNotFound(err) {
return nil return nil
} }
+80 -5
View File
@@ -22,6 +22,11 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/Azure/azure-sdk-for-go/storage"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"google.golang.org/api/googleapi"
minio "github.com/minio/minio-go"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
@@ -301,11 +306,22 @@ const (
ErrAdminConfigNotificationTargetsFailed ErrAdminConfigNotificationTargetsFailed
ErrAdminProfilerNotEnabled ErrAdminProfilerNotEnabled
ErrInvalidDecompressedSize ErrInvalidDecompressedSize
ErrAddUserInvalidArgument
) )
type errorCodeMap map[APIErrorCode]APIError
func (e errorCodeMap) ToAPIErr(errCode APIErrorCode) APIError {
apiErr, ok := e[errCode]
if !ok {
return e[ErrInternalError]
}
return apiErr
}
// error code to APIError structure, these fields carry respective // error code to APIError structure, these fields carry respective
// descriptions for all the error responses. // descriptions for all the error responses.
var errorCodeResponse = map[APIErrorCode]APIError{ var errorCodes = errorCodeMap{
ErrInvalidCopyDest: { ErrInvalidCopyDest: {
Code: "InvalidRequest", Code: "InvalidRequest",
Description: "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.", Description: "This copy request is illegal because it is trying to copy an object to itself without changing the object's metadata, storage class, website redirect location or encryption attributes.",
@@ -1447,6 +1463,11 @@ var errorCodeResponse = map[APIErrorCode]APIError{
Description: "The data provided is unfit for decompression", Description: "The data provided is unfit for decompression",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrAddUserInvalidArgument: {
Code: "XMinioInvalidIAMCredentials",
Description: "User is not allowed to be same as admin access key",
HTTPStatusCode: http.StatusConflict,
},
// Add your error structure here. // Add your error structure here.
} }
@@ -1630,12 +1651,66 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
return apiErr return apiErr
} }
// getAPIError provides API Error for input API error code. var noError = APIError{}
func getAPIError(code APIErrorCode) APIError {
if apiErr, ok := errorCodeResponse[code]; ok { // toAPIError - Converts embedded errors. Convenience
// function written to handle all cases where we have known types of
// errors returned by underlying layers.
func toAPIError(ctx context.Context, err error) APIError {
if err == nil {
return noError
}
var apiErr = errorCodes.ToAPIErr(toAPIErrorCode(ctx, err))
if apiErr.Code == "InternalError" {
// If we see an internal error try to interpret
// any underlying errors if possible depending on
// their internal error types. This code is only
// useful with gateway implementations.
switch e := err.(type) {
case minio.ErrorResponse:
apiErr = APIError{
Code: e.Code,
Description: e.Message,
HTTPStatusCode: e.StatusCode,
}
case *googleapi.Error:
apiErr = APIError{
Code: "XGCSInternalError",
Description: e.Message,
HTTPStatusCode: e.Code,
}
// GCS may send multiple errors, just pick the first one
// since S3 only sends one Error XML response.
if len(e.Errors) >= 1 {
apiErr.Code = e.Errors[0].Reason
}
case storage.AzureStorageServiceError:
apiErr = APIError{
Code: e.Code,
Description: e.Message,
HTTPStatusCode: e.StatusCode,
}
case oss.ServiceError:
apiErr = APIError{
Code: e.Code,
Description: e.Message,
HTTPStatusCode: e.StatusCode,
}
// Add more Gateway SDKs here if any in future.
}
}
return apiErr return apiErr
} }
return errorCodeResponse[ErrInternalError]
// getAPIError provides API Error for input API error code.
func getAPIError(code APIErrorCode) APIError {
if apiErr, ok := errorCodes[code]; ok {
return apiErr
}
return errorCodes.ToAPIErr(ErrInternalError)
} }
// getErrorResponse gets in standard error and resource value and // getErrorResponse gets in standard error and resource value and
+2 -2
View File
@@ -25,7 +25,7 @@ import (
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
) )
var toAPIErrorCodeTests = []struct { var toAPIErrorTests = []struct {
err error err error
errCode APIErrorCode errCode APIErrorCode
}{ }{
@@ -66,7 +66,7 @@ var toAPIErrorCodeTests = []struct {
func TestAPIErrCode(t *testing.T) { func TestAPIErrCode(t *testing.T) {
ctx := context.Background() ctx := context.Background()
for i, testCase := range toAPIErrorCodeTests { for i, testCase := range toAPIErrorTests {
errCode := toAPIErrorCode(ctx, testCase.err) errCode := toAPIErrorCode(ctx, testCase.err)
if errCode != testCase.errCode { if errCode != testCase.errCode {
t.Errorf("Test %d: Expected error code %d, got %d", i+1, testCase.errCode, errCode) t.Errorf("Test %d: Expected error code %d, got %d", i+1, testCase.errCode, errCode)
+1 -1
View File
@@ -45,7 +45,7 @@ type completeMultipartAPIError struct {
// of this function. // of this function.
func writePartSmallErrorResponse(w http.ResponseWriter, r *http.Request, err PartTooSmall) { func writePartSmallErrorResponse(w http.ResponseWriter, r *http.Request, err PartTooSmall) {
apiError := getAPIError(toAPIErrorCode(context.Background(), err)) apiError := toAPIError(context.Background(), err)
// Generate complete multipart error response. // Generate complete multipart error response.
errorResponse := getAPIErrorResponse(apiError, r.URL.Path, w.Header().Get(responseRequestIDKey)) errorResponse := getAPIErrorResponse(apiError, r.URL.Path, w.Header().Get(responseRequestIDKey))
cmpErrResp := completeMultipartAPIError{err.PartSize, int64(5242880), err.PartNumber, err.PartETag, errorResponse} cmpErrResp := completeMultipartAPIError{err.PartSize, int64(5242880), err.PartNumber, err.PartETag, errorResponse}
+14 -18
View File
@@ -568,13 +568,13 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
} }
// writeErrorRespone writes error headers // writeErrorRespone writes error headers
func writeErrorResponse(w http.ResponseWriter, errorCode APIErrorCode, reqURL *url.URL, browser bool) { func writeErrorResponse(w http.ResponseWriter, err APIError, reqURL *url.URL, browser bool) {
switch errorCode { switch err.Code {
case ErrSlowDown, ErrServerNotInitialized, ErrReadQuorum, ErrWriteQuorum: case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
// Set retry-after header to indicate user-agents to retry request after 120secs. // Set retry-after header to indicate user-agents to retry request after 120secs.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
w.Header().Set("Retry-After", "120") w.Header().Set("Retry-After", "120")
case ErrAccessDenied: case "AccessDenied":
// The request is from browser and also if browser // The request is from browser and also if browser
// is enabled we need to redirect. // is enabled we need to redirect.
if browser && globalIsBrowserEnabled { if browser && globalIsBrowserEnabled {
@@ -584,42 +584,38 @@ func writeErrorResponse(w http.ResponseWriter, errorCode APIErrorCode, reqURL *u
} }
} }
apiError := getAPIError(errorCode)
// Generate error response. // Generate error response.
errorResponse := getAPIErrorResponse(apiError, reqURL.Path, w.Header().Get(responseRequestIDKey)) errorResponse := getAPIErrorResponse(err, reqURL.Path, w.Header().Get(responseRequestIDKey))
encodedErrorResponse := encodeResponse(errorResponse) encodedErrorResponse := encodeResponse(errorResponse)
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeXML) writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeXML)
} }
func writeErrorResponseHeadersOnly(w http.ResponseWriter, errorCode APIErrorCode) { func writeErrorResponseHeadersOnly(w http.ResponseWriter, err APIError) {
apiError := getAPIError(errorCode) writeResponse(w, err.HTTPStatusCode, nil, mimeNone)
writeResponse(w, apiError.HTTPStatusCode, nil, mimeNone)
} }
// writeErrorResponseJSON - writes error response in JSON format; // writeErrorResponseJSON - writes error response in JSON format;
// useful for admin APIs. // useful for admin APIs.
func writeErrorResponseJSON(w http.ResponseWriter, errorCode APIErrorCode, reqURL *url.URL) { func writeErrorResponseJSON(w http.ResponseWriter, err APIError, reqURL *url.URL) {
apiError := getAPIError(errorCode)
// Generate error response. // Generate error response.
errorResponse := getAPIErrorResponse(apiError, reqURL.Path, w.Header().Get(responseRequestIDKey)) errorResponse := getAPIErrorResponse(err, reqURL.Path, w.Header().Get(responseRequestIDKey))
encodedErrorResponse := encodeResponseJSON(errorResponse) encodedErrorResponse := encodeResponseJSON(errorResponse)
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeJSON) writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
} }
// writeCustomErrorResponseJSON - similar to writeErrorResponseJSON, // writeCustomErrorResponseJSON - similar to writeErrorResponseJSON,
// but accepts the error message directly (this allows messages to be // but accepts the error message directly (this allows messages to be
// dynamically generated.) // dynamically generated.)
func writeCustomErrorResponseJSON(w http.ResponseWriter, errorCode APIErrorCode, func writeCustomErrorResponseJSON(w http.ResponseWriter, err APIError,
errBody string, reqURL *url.URL) { errBody string, reqURL *url.URL) {
apiError := getAPIError(errorCode)
errorResponse := APIErrorResponse{ errorResponse := APIErrorResponse{
Code: apiError.Code, Code: err.Code,
Message: errBody, Message: errBody,
Resource: reqURL.Path, Resource: reqURL.Path,
RequestID: w.Header().Get(responseRequestIDKey), RequestID: w.Header().Get(responseRequestIDKey),
HostID: "3L137", HostID: "3L137",
} }
encodedErrorResponse := encodeResponseJSON(errorResponse) encodedErrorResponse := encodeResponseJSON(errorResponse)
writeResponse(w, apiError.HTTPStatusCode, encodedErrorResponse, mimeJSON) writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
} }
+25
View File
@@ -67,6 +67,8 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.AbortMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}") bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(httpTraceAll(api.AbortMultipartUploadHandler)).Queries("uploadId", "{uploadId:.*}")
// GetObjectACL - this is a dummy call. // GetObjectACL - this is a dummy call.
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectACLHandler)).Queries("acl", "") bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectACLHandler)).Queries("acl", "")
// GetObjectTagging - this is a dummy call.
bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.GetObjectTaggingHandler)).Queries("tagging", "")
// SelectObjectContent // SelectObjectContent
bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.SelectObjectContentHandler)).Queries("select", "").Queries("select-type", "2") bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(httpTraceHdrs(api.SelectObjectContentHandler)).Queries("select", "").Queries("select-type", "2")
// GetObject // GetObject
@@ -84,8 +86,31 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled bool) {
// GetBucketPolicy // GetBucketPolicy
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketPolicyHandler)).Queries("policy", "") bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketPolicyHandler)).Queries("policy", "")
// Dummy Bucket Calls
// GetBucketACL -- this is a dummy call. // GetBucketACL -- this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketACLHandler)).Queries("acl", "") bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketACLHandler)).Queries("acl", "")
// GetBucketCors - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketCorsHandler)).Queries("cors", "")
// GetBucketWebsiteHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketWebsiteHandler)).Queries("website", "")
// GetBucketVersioningHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketVersioningHandler)).Queries("versioning", "")
// GetBucketAccelerateHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketAccelerateHandler)).Queries("accelerate", "")
// GetBucketRequestPaymentHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketRequestPaymentHandler)).Queries("requestPayment", "")
// GetBucketLoggingHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLoggingHandler)).Queries("logging", "")
// GetBucketLifecycleHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketLifecycleHandler)).Queries("lifecycle", "")
// GetBucketReplicationHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketReplicationHandler)).Queries("replication", "")
// GetBucketTaggingHandler - this is a dummy call.
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketTaggingHandler)).Queries("tagging", "")
//DeleteBucketWebsiteHandler
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketWebsiteHandler)).Queries("website", "")
// DeleteBucketTaggingHandler
bucket.Methods("DELETE").HandlerFunc(httpTraceAll(api.DeleteBucketTaggingHandler)).Queries("tagging", "")
// GetBucketNotification // GetBucketNotification
bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketNotificationHandler)).Queries("notification", "") bucket.Methods("GET").HandlerFunc(httpTraceAll(api.GetBucketNotificationHandler)).Queries("notification", "")
+1 -1
View File
@@ -416,7 +416,7 @@ func (a authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.handler.ServeHTTP(w, r) a.handler.ServeHTTP(w, r)
return return
} }
writeErrorResponse(w, ErrSignatureVersionNotSupported, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrSignatureVersionNotSupported), r.URL, guessIsBrowserReq(r))
} }
// isPutAllowed - check if PUT operation is allowed on the resource, this // isPutAllowed - check if PUT operation is allowed on the resource, this
+1 -1
View File
@@ -383,7 +383,7 @@ func TestIsReqAuthenticated(t *testing.T) {
for i, testCase := range testCases { for i, testCase := range testCases {
if s3Error := isReqAuthenticated(ctx, testCase.req, globalServerConfig.GetRegion()); s3Error != testCase.s3Error { if s3Error := isReqAuthenticated(ctx, testCase.req, globalServerConfig.GetRegion()); s3Error != testCase.s3Error {
if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error { if _, err := ioutil.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %d)", i, testCase.s3Error, s3Error, toAPIErrorCode(ctx, err)) t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
} }
} }
} }
+6 -12
View File
@@ -49,7 +49,6 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
textData := generateBytesData(objSize) textData := generateBytesData(objSize)
// generate md5sum for the generated data. // generate md5sum for the generated data.
// md5sum of the data to written is required as input for PutObject. // md5sum of the data to written is required as input for PutObject.
metadata := make(map[string]string)
md5hex := getMD5Hash(textData) md5hex := getMD5Hash(textData)
sha256hex := "" sha256hex := ""
@@ -61,7 +60,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
// insert the object. // insert the object.
objInfo, err := obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i), objInfo, err := obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i),
mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), metadata, ObjectOptions{}) mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), ObjectOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -96,9 +95,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
textData := generateBytesData(objSize) textData := generateBytesData(objSize)
// generate md5sum for the generated data. // generate md5sum for the generated data.
// md5sum of the data to written is required as input for NewMultipartUpload. // md5sum of the data to written is required as input for NewMultipartUpload.
metadata := make(map[string]string) uploadID, err = obj.NewMultipartUpload(context.Background(), bucket, object, ObjectOptions{})
opts := ObjectOptions{}
uploadID, err = obj.NewMultipartUpload(context.Background(), bucket, object, metadata, opts)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -123,7 +120,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
md5hex = getMD5Hash([]byte(textPartData)) md5hex = getMD5Hash([]byte(textPartData))
var partInfo PartInfo var partInfo PartInfo
partInfo, err = obj.PutObjectPart(context.Background(), bucket, object, uploadID, j, partInfo, err = obj.PutObjectPart(context.Background(), bucket, object, uploadID, j,
mustGetPutObjReader(b, bytes.NewBuffer(textPartData), int64(len(textPartData)), md5hex, sha256hex), opts) mustGetPutObjReader(b, bytes.NewBuffer(textPartData), int64(len(textPartData)), md5hex, sha256hex), ObjectOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -194,7 +191,6 @@ func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
// generate etag for the generated data. // generate etag for the generated data.
// etag of the data to written is required as input for PutObject. // etag of the data to written is required as input for PutObject.
// PutObject is the functions which writes the data onto the FS/XL backend. // PutObject is the functions which writes the data onto the FS/XL backend.
metadata := make(map[string]string)
// get text data generated for number of bytes equal to object size. // get text data generated for number of bytes equal to object size.
md5hex := getMD5Hash(textData) md5hex := getMD5Hash(textData)
@@ -204,7 +200,7 @@ func runGetObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
// insert the object. // insert the object.
var objInfo ObjectInfo var objInfo ObjectInfo
objInfo, err = obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i), objInfo, err = obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i),
mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), metadata, ObjectOptions{}) mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), ObjectOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -289,7 +285,6 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
textData := generateBytesData(objSize) textData := generateBytesData(objSize)
// generate md5sum for the generated data. // generate md5sum for the generated data.
// md5sum of the data to written is required as input for PutObject. // md5sum of the data to written is required as input for PutObject.
metadata := make(map[string]string)
md5hex := getMD5Hash([]byte(textData)) md5hex := getMD5Hash([]byte(textData))
sha256hex := "" sha256hex := ""
@@ -304,7 +299,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
for pb.Next() { for pb.Next() {
// insert the object. // insert the object.
objInfo, err := obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i), objInfo, err := obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i),
mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), metadata, ObjectOptions{}) mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), ObjectOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
@@ -335,7 +330,6 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
// generate md5sum for the generated data. // generate md5sum for the generated data.
// md5sum of the data to written is required as input for PutObject. // md5sum of the data to written is required as input for PutObject.
// PutObject is the functions which writes the data onto the FS/XL backend. // PutObject is the functions which writes the data onto the FS/XL backend.
metadata := make(map[string]string)
md5hex := getMD5Hash([]byte(textData)) md5hex := getMD5Hash([]byte(textData))
sha256hex := "" sha256hex := ""
@@ -344,7 +338,7 @@ func runGetObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
// insert the object. // insert the object.
var objInfo ObjectInfo var objInfo ObjectInfo
objInfo, err = obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i), objInfo, err = obj.PutObject(context.Background(), bucket, "object"+strconv.Itoa(i),
mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), metadata, ObjectOptions{}) mustGetPutObjReader(b, bytes.NewBuffer(textData), int64(len(textData)), md5hex, sha256hex), ObjectOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
+18 -21
View File
@@ -66,12 +66,12 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -79,18 +79,18 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
// Extract all the listObjectsV2 query params to their native values. // Extract all the listObjectsV2 query params to their native values.
prefix, token, startAfter, delimiter, fetchOwner, maxKeys, _, errCode := getListObjectsV2Args(urlValues) prefix, token, startAfter, delimiter, fetchOwner, maxKeys, _, errCode := getListObjectsV2Args(urlValues)
if errCode != ErrNone { if errCode != ErrNone {
writeErrorResponse(w, errCode, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return return
} }
// Validate the query params before beginning to serve the request. // Validate the query params before beginning to serve the request.
// fetch-owner is not validated since it is a boolean // fetch-owner is not validated since it is a boolean
if s3Error := validateListObjectsArgs(prefix, token, delimiter, maxKeys); s3Error != ErrNone { if s3Error := validateListObjectsArgs(prefix, token, delimiter, maxKeys); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
listObjectsV2 := objectAPI.ListObjectsV2 listObjectsV2 := objectAPI.ListObjectsV2
if api.CacheAPI() != nil { if api.CacheAPI() != nil {
listObjectsV2 = api.CacheAPI().ListObjectsV2 listObjectsV2 = api.CacheAPI().ListObjectsV2
@@ -100,7 +100,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
// marshaled into S3 compatible XML header. // marshaled into S3 compatible XML header.
listObjectsV2Info, err := listObjectsV2(ctx, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter) listObjectsV2Info, err := listObjectsV2(ctx, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -110,7 +110,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
// Read the decompressed size from the meta.json. // Read the decompressed size from the meta.json.
actualSize = listObjectsV2Info.Objects[i].GetActualSize() actualSize = listObjectsV2Info.Objects[i].GetActualSize()
if actualSize < 0 { if actualSize < 0 {
writeErrorResponse(w, ErrInvalidDecompressedSize, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidDecompressedSize), r.URL, guessIsBrowserReq(r))
return return
} }
// Set the info.Size to the actualSize. // Set the info.Size to the actualSize.
@@ -119,7 +119,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
listObjectsV2Info.Objects[i].ETag = getDecryptedETag(r.Header, listObjectsV2Info.Objects[i], false) listObjectsV2Info.Objects[i].ETag = getDecryptedETag(r.Header, listObjectsV2Info.Objects[i], false)
listObjectsV2Info.Objects[i].Size, err = listObjectsV2Info.Objects[i].DecryptedSize() listObjectsV2Info.Objects[i].Size, err = listObjectsV2Info.Objects[i].DecryptedSize()
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -148,42 +148,39 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Extract all the litsObjectsV1 query params to their native values. // Extract all the litsObjectsV1 query params to their native values.
prefix, marker, delimiter, maxKeys, _, s3Error := getListObjectsV1Args(r.URL.Query()) prefix, marker, delimiter, maxKeys, _, s3Error := getListObjectsV1Args(r.URL.Query())
if s3Error != ErrNone { if s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Validate the maxKeys lowerbound. When maxKeys > 1000, S3 returns 1000 but // Validate all the query params before beginning to serve the request.
// does not throw an error.
if maxKeys < 0 {
writeErrorResponse(w, ErrInvalidMaxKeys, r.URL, guessIsBrowserReq(r))
return
} // Validate all the query params before beginning to serve the request.
if s3Error := validateListObjectsArgs(prefix, marker, delimiter, maxKeys); s3Error != ErrNone { if s3Error := validateListObjectsArgs(prefix, marker, delimiter, maxKeys); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
listObjects := objectAPI.ListObjects listObjects := objectAPI.ListObjects
if api.CacheAPI() != nil { if api.CacheAPI() != nil {
listObjects = api.CacheAPI().ListObjects listObjects = api.CacheAPI().ListObjects
} }
// Inititate a list objects operation based on the input params. // Inititate a list objects operation based on the input params.
// On success would return back ListObjectsInfo object to be // On success would return back ListObjectsInfo object to be
// marshaled into S3 compatible XML header. // marshaled into S3 compatible XML header.
listObjectsInfo, err := listObjects(ctx, bucket, prefix, marker, delimiter, maxKeys) listObjectsInfo, err := listObjects(ctx, bucket, prefix, marker, delimiter, maxKeys)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -193,7 +190,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
// Read the decompressed size from the meta.json. // Read the decompressed size from the meta.json.
actualSize = listObjectsInfo.Objects[i].GetActualSize() actualSize = listObjectsInfo.Objects[i].GetActualSize()
if actualSize < 0 { if actualSize < 0 {
writeErrorResponse(w, ErrInvalidDecompressedSize, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidDecompressedSize), r.URL, guessIsBrowserReq(r))
return return
} }
// Set the info.Size to the actualSize. // Set the info.Size to the actualSize.
@@ -202,7 +199,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
listObjectsInfo.Objects[i].ETag = getDecryptedETag(r.Header, listObjectsInfo.Objects[i], false) listObjectsInfo.Objects[i].ETag = getDecryptedETag(r.Header, listObjectsInfo.Objects[i], false)
listObjectsInfo.Objects[i].Size, err = listObjectsInfo.Objects[i].DecryptedSize() listObjectsInfo.Objects[i].Size, err = listObjectsInfo.Objects[i].DecryptedSize()
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
} }
+77 -70
View File
@@ -96,12 +96,12 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLocationAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketLocationAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -110,7 +110,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
getBucketInfo = api.CacheAPI().GetBucketInfo getBucketInfo = api.CacheAPI().GetBucketInfo
} }
if _, err := getBucketInfo(ctx, bucket); err != nil { if _, err := getBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -146,35 +146,37 @@ func (api objectAPIHandlers) ListMultipartUploadsHandler(w http.ResponseWriter,
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketMultipartUploadsAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketMultipartUploadsAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, _, s3Error := getBucketMultipartResources(r.URL.Query()) prefix, keyMarker, uploadIDMarker, delimiter, maxUploads, _, errCode := getBucketMultipartResources(r.URL.Query())
if s3Error != ErrNone { if errCode != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return return
} }
if maxUploads < 0 { if maxUploads < 0 {
writeErrorResponse(w, ErrInvalidMaxUploads, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidMaxUploads), r.URL, guessIsBrowserReq(r))
return return
} }
if keyMarker != "" { if keyMarker != "" {
// Marker not common with prefix is not implemented. // Marker not common with prefix is not implemented.
if !hasPrefix(keyMarker, prefix) { if !hasPrefix(keyMarker, prefix) {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
} }
listMultipartsInfo, err := objectAPI.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads) listMultipartsInfo, err := objectAPI.ListMultipartUploads(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, maxUploads)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
// generate response // generate response
@@ -196,25 +198,26 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
listBuckets := objectAPI.ListBuckets
listBuckets := objectAPI.ListBuckets
if api.CacheAPI() != nil { if api.CacheAPI() != nil {
listBuckets = api.CacheAPI().ListBuckets listBuckets = api.CacheAPI().ListBuckets
} }
if s3Error := checkRequestAuthType(ctx, r, policy.ListAllMyBucketsAction, "", ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.ListAllMyBucketsAction, "", ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// If etcd, dns federation configured list buckets from etcd. // If etcd, dns federation configured list buckets from etcd.
var bucketsInfo []BucketInfo var bucketsInfo []BucketInfo
if globalDNSConfig != nil { if globalDNSConfig != nil {
dnsBuckets, err := globalDNSConfig.List() dnsBuckets, err := globalDNSConfig.List()
if err != nil && err != dns.ErrNoEntriesFound { if err != nil && err != dns.ErrNoEntriesFound {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
bucketSet := set.NewStringSet() bucketSet := set.NewStringSet()
@@ -233,7 +236,7 @@ func (api objectAPIHandlers) ListBucketsHandler(w http.ResponseWriter, r *http.R
var err error var err error
bucketsInfo, err = listBuckets(ctx) bucketsInfo, err = listBuckets(ctx)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -257,7 +260,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -266,7 +269,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// In the event access is denied, a 200 response should still be returned // In the event access is denied, a 200 response should still be returned
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if s3Error != ErrAccessDenied { if s3Error != ErrAccessDenied {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -274,14 +277,14 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Content-Length is required and should be non-zero // Content-Length is required and should be non-zero
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if r.ContentLength <= 0 { if r.ContentLength <= 0 {
writeErrorResponse(w, ErrMissingContentLength, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return return
} }
// Content-Md5 is requied should be set // Content-Md5 is requied should be set
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html // http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
if _, ok := r.Header["Content-Md5"]; !ok { if _, ok := r.Header["Content-Md5"]; !ok {
writeErrorResponse(w, ErrMissingContentMD5, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMissingContentMD5), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -297,7 +300,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Read incoming body XML bytes. // Read incoming body XML bytes.
if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil { if _, err := io.ReadFull(r.Body, deleteXMLBytes); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(w, ErrInternalError, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAdminAPIErr(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -305,7 +308,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
deleteObjects := &DeleteObjectsRequest{} deleteObjects := &DeleteObjectsRequest{}
if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil { if err := xml.Unmarshal(deleteXMLBytes, deleteObjects); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(w, ErrMalformedXML, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedXML), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -313,7 +316,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
if globalWORMEnabled { if globalWORMEnabled {
// Not required to check whether given objects exist or not, because // Not required to check whether given objects exist or not, because
// DeleteMultipleObject is always successful irrespective of object existence. // DeleteMultipleObject is always successful irrespective of object existence.
writeErrorResponse(w, ErrMethodNotAllowed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -352,10 +355,11 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
deletedObjects = append(deletedObjects, object) deletedObjects = append(deletedObjects, object)
continue continue
} }
apiErr := toAPIError(ctx, err)
// Error during delete should be collected separately. // Error during delete should be collected separately.
deleteErrors = append(deleteErrors, DeleteError{ deleteErrors = append(deleteErrors, DeleteError{
Code: errorCodeResponse[toAPIErrorCode(ctx, err)].Code, Code: apiErr.Code,
Message: errorCodeResponse[toAPIErrorCode(ctx, err)].Description, Message: apiErr.Description,
Key: object.ObjectName, Key: object.ObjectName,
}) })
} }
@@ -401,7 +405,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -409,21 +413,21 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
bucket := vars["bucket"] bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.CreateBucketAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.CreateBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Parse incoming location constraint. // Parse incoming location constraint.
location, s3Error := parseLocationConstraint(r) location, s3Error := parseLocationConstraint(r)
if s3Error != ErrNone { if s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Validate if location sent by the client is valid, reject // Validate if location sent by the client is valid, reject
// requests which do not follow valid region requirements. // requests which do not follow valid region requirements.
if !isValidLocation(location) { if !isValidLocation(location) {
writeErrorResponse(w, ErrInvalidRegion, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidRegion), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -432,12 +436,12 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
if err == dns.ErrNoEntriesFound { if err == dns.ErrNoEntriesFound {
// Proceed to creating a bucket. // Proceed to creating a bucket.
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, location); err != nil { if err = objectAPI.MakeBucketWithLocation(ctx, bucket, location); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
if err = globalDNSConfig.Put(bucket); err != nil { if err = globalDNSConfig.Put(bucket); err != nil {
objectAPI.DeleteBucket(ctx, bucket) objectAPI.DeleteBucket(ctx, bucket)
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -447,18 +451,18 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
return return
} }
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
writeErrorResponse(w, ErrBucketAlreadyOwnedByYou, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrBucketAlreadyOwnedByYou), r.URL, guessIsBrowserReq(r))
return return
} }
// Proceed to creating a bucket. // Proceed to creating a bucket.
err := objectAPI.MakeBucketWithLocation(ctx, bucket, location) err := objectAPI.MakeBucketWithLocation(ctx, bucket, location)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -479,33 +483,36 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported if crypto.S3KMS.IsRequested(r.Header) { // SSE-KMS is not supported
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) { if !api.EncryptionEnabled() && hasServerSideEncryptionHeader(r.Header) {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
bucket := mux.Vars(r)["bucket"] bucket := mux.Vars(r)["bucket"]
// Require Content-Length to be set in the request // Require Content-Length to be set in the request
size := r.ContentLength size := r.ContentLength
if size < 0 { if size < 0 {
writeErrorResponse(w, ErrMissingContentLength, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return return
} }
resource, err := getResource(r.URL.Path, r.Host, globalDomainName) resource, err := getResource(r.URL.Path, r.Host, globalDomainName)
if err != nil { if err != nil {
writeErrorResponse(w, ErrInvalidRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL, guessIsBrowserReq(r))
return return
} }
// Make sure that the URL does not contain object name. // Make sure that the URL does not contain object name.
if bucket != filepath.Clean(resource[1:]) { if bucket != filepath.Clean(resource[1:]) {
writeErrorResponse(w, ErrMethodNotAllowed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -514,7 +521,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
reader, err := r.MultipartReader() reader, err := r.MultipartReader()
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -522,7 +529,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
form, err := reader.ReadForm(maxFormMemory) form, err := reader.ReadForm(maxFormMemory)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -533,13 +540,13 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form) fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
// Check if file is provided, error out otherwise. // Check if file is provided, error out otherwise.
if fileBody == nil { if fileBody == nil {
writeErrorResponse(w, ErrPOSTFileRequired, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPOSTFileRequired), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -561,21 +568,21 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
if successRedirect != "" { if successRedirect != "" {
redirectURL, err = url.Parse(successRedirect) redirectURL, err = url.Parse(successRedirect)
if err != nil { if err != nil {
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
} }
// Verify policy signature. // Verify policy signature.
apiErr := doesPolicySignatureMatch(formValues) errCode := doesPolicySignatureMatch(formValues)
if apiErr != ErrNone { if errCode != ErrNone {
writeErrorResponse(w, apiErr, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return return
} }
policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy")) policyBytes, err := base64.StdEncoding.DecodeString(formValues.Get("Policy"))
if err != nil { if err != nil {
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -583,13 +590,13 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
if len(policyBytes) > 0 { if len(policyBytes) > 0 {
postPolicyForm, err := parsePostPolicyForm(string(policyBytes)) postPolicyForm, err := parsePostPolicyForm(string(policyBytes))
if err != nil { if err != nil {
writeErrorResponse(w, ErrMalformedPOSTRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL, guessIsBrowserReq(r))
return return
} }
// Make sure formValues adhere to policy restrictions. // Make sure formValues adhere to policy restrictions.
if apiErr = checkPostPolicy(formValues, postPolicyForm); apiErr != ErrNone { if errCode = checkPostPolicy(formValues, postPolicyForm); errCode != ErrNone {
writeErrorResponse(w, apiErr, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -598,12 +605,12 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
lengthRange := postPolicyForm.Conditions.ContentLengthRange lengthRange := postPolicyForm.Conditions.ContentLengthRange
if lengthRange.Valid { if lengthRange.Valid {
if fileSize < lengthRange.Min { if fileSize < lengthRange.Min {
writeErrorResponse(w, toAPIErrorCode(ctx, errDataTooSmall), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, errDataTooSmall), r.URL, guessIsBrowserReq(r))
return return
} }
if fileSize > lengthRange.Max || isMaxObjectSize(fileSize) { if fileSize > lengthRange.Max || isMaxObjectSize(fileSize) {
writeErrorResponse(w, toAPIErrorCode(ctx, errDataTooLarge), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, errDataTooLarge), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -613,14 +620,14 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
metadata := make(map[string]string) metadata := make(map[string]string)
err = extractMetadataFromMap(ctx, formValues, metadata) err = extractMetadataFromMap(ctx, formValues, metadata)
if err != nil { if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize) hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
rawReader := hashReader rawReader := hashReader
@@ -633,9 +640,9 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
} }
// get gateway encryption options // get gateway encryption options
var opts ObjectOptions var opts ObjectOptions
opts, err = putEncryptionOpts(ctx, r, bucket, object, nil) opts, err = putOpts(ctx, r, bucket, object, metadata)
if err != nil { if err != nil {
writeErrorResponseHeadersOnly(w, toAPIErrorCode(ctx, err)) writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
return return
} }
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
@@ -645,28 +652,28 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
if crypto.SSEC.IsRequested(formValues) { if crypto.SSEC.IsRequested(formValues) {
key, err = ParseSSECustomerHeader(formValues) key, err = ParseSSECustomerHeader(formValues)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
} }
reader, objectEncryptionKey, err = newEncryptReader(hashReader, key, bucket, object, metadata, crypto.S3.IsRequested(formValues)) reader, objectEncryptionKey, err = newEncryptReader(hashReader, key, bucket, object, metadata, crypto.S3.IsRequested(formValues))
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
info := ObjectInfo{Size: fileSize} info := ObjectInfo{Size: fileSize}
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize) // do not try to verify encrypted content hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize) // do not try to verify encrypted content
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey) pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey)
} }
} }
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, metadata, opts) objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -732,12 +739,12 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponseHeadersOnly(w, ErrServerNotInitialized) writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrServerNotInitialized))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.ListBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponseHeadersOnly(w, s3Error) writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(s3Error))
return return
} }
@@ -746,7 +753,7 @@ func (api objectAPIHandlers) HeadBucketHandler(w http.ResponseWriter, r *http.Re
getBucketInfo = api.CacheAPI().GetBucketInfo getBucketInfo = api.CacheAPI().GetBucketInfo
} }
if _, err := getBucketInfo(ctx, bucket); err != nil { if _, err := getBucketInfo(ctx, bucket); err != nil {
writeErrorResponseHeadersOnly(w, toAPIErrorCode(ctx, err)) writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
return return
} }
@@ -764,12 +771,12 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -779,7 +786,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
} }
// Attempt to delete bucket. // Attempt to delete bucket.
if err := deleteBucket(ctx, bucket); err != nil { if err := deleteBucket(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -791,7 +798,7 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
if err := globalDNSConfig.Delete(bucket); err != nil { if err := globalDNSConfig.Delete(bucket); err != nil {
// Deleting DNS entry failed, attempt to create the bucket again. // Deleting DNS entry failed, attempt to create the bucket again.
objectAPI.MakeBucketWithLocation(ctx, bucket, "") objectAPI.MakeBucketWithLocation(ctx, bucket, "")
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
} }
+3 -3
View File
@@ -625,7 +625,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
objectName := "test-object-" + strconv.Itoa(i) objectName := "test-object-" + strconv.Itoa(i)
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewBuffer(contentBytes), int64(len(contentBytes)), "", sha256sum), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewBuffer(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object %d: Error uploading object: <ERROR> %v", i, err) t.Fatalf("Put Object %d: Error uploading object: <ERROR> %v", i, err)
@@ -645,8 +645,8 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
getDeleteErrorList := func(objects []ObjectIdentifier) (deleteErrorList []DeleteError) { getDeleteErrorList := func(objects []ObjectIdentifier) (deleteErrorList []DeleteError) {
for _, obj := range objects { for _, obj := range objects {
deleteErrorList = append(deleteErrorList, DeleteError{ deleteErrorList = append(deleteErrorList, DeleteError{
Code: errorCodeResponse[ErrAccessDenied].Code, Code: errorCodes[ErrAccessDenied].Code,
Message: errorCodeResponse[ErrAccessDenied].Description, Message: errorCodes[ErrAccessDenied].Description,
Key: obj.ObjectName, Key: obj.ObjectName,
}) })
} }
+35 -30
View File
@@ -51,23 +51,23 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if !objAPI.IsNotificationSupported() { if !objAPI.IsNotificationSupported() {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
_, err := objAPI.GetBucketInfo(ctx, bucketName) _, err := objAPI.GetBucketInfo(ctx, bucketName)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -76,7 +76,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
if err != nil { if err != nil {
// Ignore errNoSuchNotifications to comply with AWS S3. // Ignore errNoSuchNotifications to comply with AWS S3.
if err != errNoSuchNotifications { if err != errNoSuchNotifications {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -90,7 +90,7 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
notificationBytes, err := xml.Marshal(nConfig) notificationBytes, err := xml.Marshal(nConfig)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -106,12 +106,12 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
objectAPI := api.ObjectAPI() objectAPI := api.ObjectAPI()
if objectAPI == nil { if objectAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if !objectAPI.IsNotificationSupported() { if !objectAPI.IsNotificationSupported() {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -119,28 +119,28 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
bucketName := vars["bucket"] bucketName := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketNotificationAction, bucketName, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
_, err := objectAPI.GetBucketInfo(ctx, bucketName) _, err := objectAPI.GetBucketInfo(ctx, bucketName)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
// PutBucketNotification always needs a Content-Length. // PutBucketNotification always needs a Content-Length.
if r.ContentLength <= 0 { if r.ContentLength <= 0 {
writeErrorResponse(w, ErrMissingContentLength, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return return
} }
var config *event.Config var config *event.Config
config, err = event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalServerConfig.GetRegion(), globalNotificationSys.targetList) config, err = event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalServerConfig.GetRegion(), globalNotificationSys.targetList)
if err != nil { if err != nil {
apiErr := ErrMalformedXML apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
if event.IsEventError(err) { if event.IsEventError(err) {
apiErr = toAPIErrorCode(ctx, err) apiErr = toAPIError(ctx, err)
} }
writeErrorResponse(w, apiErr, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, apiErr, r.URL, guessIsBrowserReq(r))
@@ -148,7 +148,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
} }
if err = saveNotificationConfig(ctx, objectAPI, bucketName, config); err != nil { if err = saveNotificationConfig(ctx, objectAPI, bucketName, config); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -169,23 +169,24 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
// Validate if bucket exists. // Validate if bucket exists.
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
if !objAPI.IsNotificationSupported() { if !objAPI.IsNotificationSupported() {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
if !objAPI.IsListenBucketSupported() { if !objAPI.IsListenBucketSupported() {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
vars := mux.Vars(r) vars := mux.Vars(r)
bucketName := vars["bucket"] bucketName := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.ListenBucketNotificationAction, bucketName, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.ListenBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -193,11 +194,13 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
var prefix string var prefix string
if len(values["prefix"]) > 1 { if len(values["prefix"]) > 1 {
writeErrorResponse(w, ErrFilterNamePrefix, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrFilterNamePrefix), r.URL, guessIsBrowserReq(r))
return
} }
if len(values["prefix"]) == 1 { if len(values["prefix"]) == 1 {
if err := event.ValidateFilterRuleValue(values["prefix"][0]); err != nil { if err := event.ValidateFilterRuleValue(values["prefix"][0]); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -206,11 +209,13 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
var suffix string var suffix string
if len(values["suffix"]) > 1 { if len(values["suffix"]) > 1 {
writeErrorResponse(w, ErrFilterNameSuffix, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrFilterNameSuffix), r.URL, guessIsBrowserReq(r))
return
} }
if len(values["suffix"]) == 1 { if len(values["suffix"]) == 1 {
if err := event.ValidateFilterRuleValue(values["suffix"][0]); err != nil { if err := event.ValidateFilterRuleValue(values["suffix"][0]); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -223,7 +228,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
for _, s := range values["events"] { for _, s := range values["events"] {
eventName, err := event.ParseName(s) eventName, err := event.ParseName(s)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -231,19 +236,19 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
} }
if _, err := objAPI.GetBucketInfo(ctx, bucketName); err != nil { if _, err := objAPI.GetBucketInfo(ctx, bucketName); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
host, err := xnet.ParseHost(r.RemoteAddr) host, err := xnet.ParseHost(r.RemoteAddr)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
target, err := target.NewHTTPClientTarget(*host, w) target, err := target.NewHTTPClientTarget(*host, w)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -251,7 +256,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
if err = globalNotificationSys.AddRemoteTarget(bucketName, target, rulesMap); err != nil { if err = globalNotificationSys.AddRemoteTarget(bucketName, target, rulesMap); err != nil {
logger.GetReqInfo(ctx).AppendTags("target", target.ID().Name) logger.GetReqInfo(ctx).AppendTags("target", target.ID().Name)
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
defer globalNotificationSys.RemoveRemoteTarget(bucketName, target.ID()) defer globalNotificationSys.RemoveRemoteTarget(bucketName, target.ID())
@@ -259,13 +264,13 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints)) thisAddr, err := xnet.ParseHost(GetLocalPeer(globalEndpoints))
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
if err = SaveListener(objAPI, bucketName, eventNames, pattern, target.ID(), *thisAddr); err != nil { if err = SaveListener(objAPI, bucketName, eventNames, pattern, target.ID(), *thisAddr); err != nil {
logger.GetReqInfo(ctx).AppendTags("target", target.ID().Name) logger.GetReqInfo(ctx).AppendTags("target", target.ID().Name)
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -275,7 +280,7 @@ func (api objectAPIHandlers) ListenBucketNotificationHandler(w http.ResponseWrit
if err = RemoveListener(objAPI, bucketName, target.ID(), *thisAddr); err != nil { if err = RemoveListener(objAPI, bucketName, target.ID(), *thisAddr); err != nil {
logger.GetReqInfo(ctx).AppendTags("target", target.ID().Name) logger.GetReqInfo(ctx).AppendTags("target", target.ID().Name)
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
} }
+17 -17
View File
@@ -44,7 +44,7 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -52,43 +52,43 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
bucket := vars["bucket"] bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.PutBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Check if bucket exists. // Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil { if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
// Error out if Content-Length is missing. // Error out if Content-Length is missing.
// PutBucketPolicy always needs Content-Length. // PutBucketPolicy always needs Content-Length.
if r.ContentLength <= 0 { if r.ContentLength <= 0 {
writeErrorResponse(w, ErrMissingContentLength, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMissingContentLength), r.URL, guessIsBrowserReq(r))
return return
} }
// Error out if Content-Length is beyond allowed size. // Error out if Content-Length is beyond allowed size.
if r.ContentLength > maxBucketPolicySize { if r.ContentLength > maxBucketPolicySize {
writeErrorResponse(w, ErrEntityTooLarge, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrEntityTooLarge), r.URL, guessIsBrowserReq(r))
return return
} }
bucketPolicy, err := policy.ParseConfig(io.LimitReader(r.Body, r.ContentLength), bucket) bucketPolicy, err := policy.ParseConfig(io.LimitReader(r.Body, r.ContentLength), bucket)
if err != nil { if err != nil {
writeErrorResponse(w, ErrMalformedPolicy, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL, guessIsBrowserReq(r))
return return
} }
// Version in policy must not be empty // Version in policy must not be empty
if bucketPolicy.Version == "" { if bucketPolicy.Version == "" {
writeErrorResponse(w, ErrMalformedPolicy, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL, guessIsBrowserReq(r))
return return
} }
if err = objAPI.SetBucketPolicy(ctx, bucket, bucketPolicy); err != nil { if err = objAPI.SetBucketPolicy(ctx, bucket, bucketPolicy); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -107,7 +107,7 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -115,18 +115,18 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
bucket := vars["bucket"] bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketPolicyAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.DeleteBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Check if bucket exists. // Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil { if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
if err := objAPI.DeleteBucketPolicy(ctx, bucket); err != nil { if err := objAPI.DeleteBucketPolicy(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -145,7 +145,7 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
objAPI := api.ObjectAPI() objAPI := api.ObjectAPI()
if objAPI == nil { if objAPI == nil {
writeErrorResponse(w, ErrServerNotInitialized, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -153,26 +153,26 @@ func (api objectAPIHandlers) GetBucketPolicyHandler(w http.ResponseWriter, r *ht
bucket := vars["bucket"] bucket := vars["bucket"]
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone { if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, s3Error, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return return
} }
// Check if bucket exists. // Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil { if _, err := objAPI.GetBucketInfo(ctx, bucket); err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
// Read bucket access policy. // Read bucket access policy.
bucketPolicy, err := objAPI.GetBucketPolicy(ctx, bucket) bucketPolicy, err := objAPI.GetBucketPolicy(ctx, bucket)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
policyData, err := json.Marshal(bucketPolicy) policyData, err := json.Marshal(bucketPolicy)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
+1 -1
View File
@@ -122,7 +122,7 @@ func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() s
func handleCommonCmdArgs(ctx *cli.Context) { func handleCommonCmdArgs(ctx *cli.Context) {
// Get "json" flag from command line argument and // Get "json" flag from command line argument and
// enable json and quite modes if jason flag is turned on. // enable json and quite modes if json flag is turned on.
globalCLIContext.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json") globalCLIContext.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
if globalCLIContext.JSON { if globalCLIContext.JSON {
logger.EnableJSON() logger.EnableJSON()
+1 -1
View File
@@ -78,7 +78,7 @@ func saveConfig(ctx context.Context, objAPI ObjectLayer, configFile string, data
return err return err
} }
_, err = objAPI.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader, nil, nil), nil, ObjectOptions{}) _, err = objAPI.PutObject(ctx, minioMetaBucket, configFile, NewPutObjReader(hashReader, nil, nil), ObjectOptions{})
return err return err
} }
+3 -2
View File
@@ -25,6 +25,7 @@ import (
"sync" "sync"
"github.com/minio/minio/cmd/crypto" "github.com/minio/minio/cmd/crypto"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/event" "github.com/minio/minio/pkg/event"
@@ -282,7 +283,7 @@ func (s *serverConfig) loadFromEnvs() {
if jwksURL, ok := os.LookupEnv("MINIO_IAM_JWKS_URL"); ok { if jwksURL, ok := os.LookupEnv("MINIO_IAM_JWKS_URL"); ok {
if u, err := xnet.ParseURL(jwksURL); err == nil { if u, err := xnet.ParseURL(jwksURL); err == nil {
s.OpenID.JWKS.URL = u s.OpenID.JWKS.URL = u
s.OpenID.JWKS.PopulatePublicKey() logger.FatalIf(s.OpenID.JWKS.PopulatePublicKey(), "Unable to populate public key from JWKS URL")
} }
} }
@@ -552,7 +553,7 @@ func (s *serverConfig) loadToCachedConfigs() {
URL: s.Policy.OPA.URL, URL: s.Policy.OPA.URL,
AuthToken: s.Policy.OPA.AuthToken, AuthToken: s.Policy.OPA.AuthToken,
Transport: NewCustomHTTPTransport(), Transport: NewCustomHTTPTransport(),
CloseRespFn: CloseResponse, CloseRespFn: xhttp.DrainBody,
}) })
} }
} }
+5 -4
View File
@@ -17,21 +17,22 @@
package cmd package cmd
import ( import (
"context"
"net/http" "net/http"
"net/url" "net/url"
) )
// Writes S3 compatible copy part range error. // Writes S3 compatible copy part range error.
func writeCopyPartErr(w http.ResponseWriter, err error, url *url.URL, browser bool) { func writeCopyPartErr(ctx context.Context, w http.ResponseWriter, err error, url *url.URL, browser bool) {
switch err { switch err {
case errInvalidRange: case errInvalidRange:
writeErrorResponse(w, ErrInvalidCopyPartRange, url, browser) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidCopyPartRange), url, browser)
return return
case errInvalidRangeSource: case errInvalidRangeSource:
writeErrorResponse(w, ErrInvalidCopyPartRangeSource, url, browser) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidCopyPartRangeSource), url, browser)
return return
default: default:
writeErrorResponse(w, ErrInternalError, url, browser) writeErrorResponse(w, toAPIError(ctx, err), url, browser)
return return
} }
} }
+6 -6
View File
@@ -257,7 +257,7 @@ func (cfs *cacheFSObjects) IsOnline() bool {
} }
// Caches the object to disk // Caches the object to disk
func (cfs *cacheFSObjects) Put(ctx context.Context, bucket, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) error { func (cfs *cacheFSObjects) Put(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) error {
if cfs.diskUsageHigh() { if cfs.diskUsageHigh() {
select { select {
case cfs.purgeChan <- struct{}{}: case cfs.purgeChan <- struct{}{}:
@@ -274,7 +274,7 @@ func (cfs *cacheFSObjects) Put(ctx context.Context, bucket, object string, data
return pErr return pErr
} }
} }
_, err := cfs.PutObject(ctx, bucket, object, data, metadata, opts) _, err := cfs.PutObject(ctx, bucket, object, data, opts)
// if err is due to disk being offline , mark cache drive as offline // if err is due to disk being offline , mark cache drive as offline
if IsErr(err, baseErrs...) { if IsErr(err, baseErrs...) {
cfs.setOnline(false) cfs.setOnline(false)
@@ -300,7 +300,7 @@ func (cfs *cacheFSObjects) Exists(ctx context.Context, bucket, object string) bo
// Identical to fs PutObject operation except that it uses ETag in metadata // Identical to fs PutObject operation except that it uses ETag in metadata
// headers. // headers.
func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object string, r *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, retErr error) { func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, retErr error) {
data := r.Reader data := r.Reader
fs := cfs.FSObjects fs := cfs.FSObjects
// Lock the object. // Lock the object.
@@ -312,7 +312,7 @@ func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object
// No metadata is set, allocate a new one. // No metadata is set, allocate a new one.
meta := make(map[string]string) meta := make(map[string]string)
for k, v := range metadata { for k, v := range opts.UserDefined {
meta[k] = v meta[k] = v
} }
@@ -438,7 +438,7 @@ func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object
// Implements S3 compatible initiate multipart API. Operation here is identical // Implements S3 compatible initiate multipart API. Operation here is identical
// to fs backend implementation - with the exception that cache FS uses the uploadID // to fs backend implementation - with the exception that cache FS uses the uploadID
// generated on the backend // generated on the backend
func (cfs *cacheFSObjects) NewMultipartUpload(ctx context.Context, bucket, object string, meta map[string]string, uploadID string, opts ObjectOptions) (string, error) { func (cfs *cacheFSObjects) NewMultipartUpload(ctx context.Context, bucket, object string, uploadID string, opts ObjectOptions) (string, error) {
if cfs.diskUsageHigh() { if cfs.diskUsageHigh() {
select { select {
case cfs.purgeChan <- struct{}{}: case cfs.purgeChan <- struct{}{}:
@@ -472,7 +472,7 @@ func (cfs *cacheFSObjects) NewMultipartUpload(ctx context.Context, bucket, objec
// Initialize fs.json values. // Initialize fs.json values.
fsMeta := newFSMetaV1() fsMeta := newFSMetaV1()
fsMeta.Meta = meta fsMeta.Meta = opts.UserDefined
fsMetaBytes, err := json.Marshal(fsMeta) fsMetaBytes, err := json.Marshal(fsMeta)
if err != nil { if err != nil {
+25 -24
View File
@@ -60,13 +60,13 @@ type cacheObjects struct {
GetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) GetObjectNInfoFn func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
GetObjectFn func(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) GetObjectFn func(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error)
GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) GetObjectInfoFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
PutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) PutObjectFn func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObjectFn func(ctx context.Context, bucket, object string) error DeleteObjectFn func(ctx context.Context, bucket, object string) error
ListObjectsFn func(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error) ListObjectsFn func(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (result ListObjectsInfo, err error)
ListObjectsV2Fn func(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error) ListObjectsV2Fn func(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (result ListObjectsV2Info, err error)
ListBucketsFn func(ctx context.Context) (buckets []BucketInfo, err error) ListBucketsFn func(ctx context.Context) (buckets []BucketInfo, err error)
GetBucketInfoFn func(ctx context.Context, bucket string) (bucketInfo BucketInfo, err error) GetBucketInfoFn func(ctx context.Context, bucket string) (bucketInfo BucketInfo, err error)
NewMultipartUploadFn func(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) NewMultipartUploadFn func(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error)
PutObjectPartFn func(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) PutObjectPartFn func(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error)
AbortMultipartUploadFn func(ctx context.Context, bucket, object, uploadID string) error AbortMultipartUploadFn func(ctx context.Context, bucket, object, uploadID string) error
CompleteMultipartUploadFn func(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error) CompleteMultipartUploadFn func(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error)
@@ -92,11 +92,11 @@ type CacheObjectLayer interface {
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error)
GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error)
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObject(ctx context.Context, bucket, object string) error DeleteObject(ctx context.Context, bucket, object string) error
// Multipart operations. // Multipart operations.
NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error)
PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error)
AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string) error AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string) error
CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error) CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error)
@@ -246,8 +246,7 @@ func (c cacheObjects) GetObjectNInfo(ctx context.Context, bucket, object string,
} }
go func() { go func() {
opts := ObjectOptions{} putErr := dcache.Put(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), ObjectOptions{UserDefined: c.getMetadata(bkReader.ObjInfo)})
putErr := dcache.Put(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), c.getMetadata(bkReader.ObjInfo), opts)
// close the write end of the pipe, so the error gets // close the write end of the pipe, so the error gets
// propagated to getObjReader // propagated to getObjReader
pipeWriter.CloseWithError(putErr) pipeWriter.CloseWithError(putErr)
@@ -320,7 +319,9 @@ func (c cacheObjects) GetObject(ctx context.Context, bucket, object string, star
gerr := GetObjectFn(ctx, bucket, object, 0, objInfo.Size, io.MultiWriter(writer, pipeWriter), etag, opts) gerr := GetObjectFn(ctx, bucket, object, 0, objInfo.Size, io.MultiWriter(writer, pipeWriter), etag, opts)
pipeWriter.CloseWithError(gerr) // Close writer explicitly signaling we wrote all data. pipeWriter.CloseWithError(gerr) // Close writer explicitly signaling we wrote all data.
}() }()
err = dcache.Put(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), c.getMetadata(objInfo), opts)
opts.UserDefined = c.getMetadata(objInfo)
err = dcache.Put(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), opts)
if err != nil { if err != nil {
return err return err
} }
@@ -644,25 +645,25 @@ func (c cacheObjects) isCacheExclude(bucket, object string) bool {
} }
// PutObject - caches the uploaded object for single Put operations // PutObject - caches the uploaded object for single Put operations
func (c cacheObjects) PutObject(ctx context.Context, bucket, object string, r *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) { func (c cacheObjects) PutObject(ctx context.Context, bucket, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
putObjectFn := c.PutObjectFn putObjectFn := c.PutObjectFn
data := r.Reader data := r.Reader
dcache, err := c.cache.getCacheFS(ctx, bucket, object) dcache, err := c.cache.getCacheFS(ctx, bucket, object)
if err != nil { if err != nil {
// disk cache could not be located,execute backend call. // disk cache could not be located,execute backend call.
return putObjectFn(ctx, bucket, object, r, metadata, opts) return putObjectFn(ctx, bucket, object, r, opts)
} }
size := r.Size() size := r.Size()
// fetch from backend if there is no space on cache drive // fetch from backend if there is no space on cache drive
if !dcache.diskAvailable(size) { if !dcache.diskAvailable(size) {
return putObjectFn(ctx, bucket, object, r, metadata, opts) return putObjectFn(ctx, bucket, object, r, opts)
} }
// fetch from backend if cache exclude pattern or cache-control // fetch from backend if cache exclude pattern or cache-control
// directive set to exclude // directive set to exclude
if c.isCacheExclude(bucket, object) || filterFromCache(metadata) { if c.isCacheExclude(bucket, object) || filterFromCache(opts.UserDefined) {
dcache.Delete(ctx, bucket, object) dcache.Delete(ctx, bucket, object)
return putObjectFn(ctx, bucket, object, r, metadata, opts) return putObjectFn(ctx, bucket, object, r, opts)
} }
objInfo = ObjectInfo{} objInfo = ObjectInfo{}
// Initialize pipe to stream data to backend // Initialize pipe to stream data to backend
@@ -680,7 +681,7 @@ func (c cacheObjects) PutObject(ctx context.Context, bucket, object string, r *P
oinfoCh := make(chan ObjectInfo) oinfoCh := make(chan ObjectInfo)
errCh := make(chan error) errCh := make(chan error)
go func() { go func() {
oinfo, perr := putObjectFn(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), metadata, opts) oinfo, perr := putObjectFn(ctx, bucket, object, NewPutObjReader(hashReader, nil, nil), opts)
if perr != nil { if perr != nil {
pipeWriter.CloseWithError(perr) pipeWriter.CloseWithError(perr)
wPipe.CloseWithError(perr) wPipe.CloseWithError(perr)
@@ -693,7 +694,7 @@ func (c cacheObjects) PutObject(ctx context.Context, bucket, object string, r *P
}() }()
go func() { go func() {
if err = dcache.Put(ctx, bucket, object, NewPutObjReader(cHashReader, nil, nil), metadata, opts); err != nil { if err = dcache.Put(ctx, bucket, object, NewPutObjReader(cHashReader, nil, nil), opts); err != nil {
wPipe.CloseWithError(err) wPipe.CloseWithError(err)
return return
} }
@@ -712,25 +713,25 @@ func (c cacheObjects) PutObject(ctx context.Context, bucket, object string, r *P
} }
// NewMultipartUpload - Starts a new multipart upload operation to backend and cache. // NewMultipartUpload - Starts a new multipart upload operation to backend and cache.
func (c cacheObjects) NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) { func (c cacheObjects) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error) {
newMultipartUploadFn := c.NewMultipartUploadFn newMultipartUploadFn := c.NewMultipartUploadFn
if c.isCacheExclude(bucket, object) || filterFromCache(metadata) { if c.isCacheExclude(bucket, object) || filterFromCache(opts.UserDefined) {
return newMultipartUploadFn(ctx, bucket, object, metadata, opts) return newMultipartUploadFn(ctx, bucket, object, opts)
} }
dcache, err := c.cache.getCacheFS(ctx, bucket, object) dcache, err := c.cache.getCacheFS(ctx, bucket, object)
if err != nil { if err != nil {
// disk cache could not be located,execute backend call. // disk cache could not be located,execute backend call.
return newMultipartUploadFn(ctx, bucket, object, metadata, opts) return newMultipartUploadFn(ctx, bucket, object, opts)
} }
uploadID, err = newMultipartUploadFn(ctx, bucket, object, metadata, opts) uploadID, err = newMultipartUploadFn(ctx, bucket, object, opts)
if err != nil { if err != nil {
return return
} }
// create new multipart upload in cache with same uploadID // create new multipart upload in cache with same uploadID
dcache.NewMultipartUpload(ctx, bucket, object, metadata, uploadID, opts) dcache.NewMultipartUpload(ctx, bucket, object, uploadID, opts)
return uploadID, err return uploadID, err
} }
@@ -971,8 +972,8 @@ func newServerCacheObjects(config CacheConfig) (CacheObjectLayer, error) {
GetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) { GetObjectNInfoFn: func(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (gr *GetObjectReader, err error) {
return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts) return newObjectLayerFn().GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
}, },
PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) { PutObjectFn: func(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return newObjectLayerFn().PutObject(ctx, bucket, object, data, metadata, opts) return newObjectLayerFn().PutObject(ctx, bucket, object, data, opts)
}, },
DeleteObjectFn: func(ctx context.Context, bucket, object string) error { DeleteObjectFn: func(ctx context.Context, bucket, object string) error {
return newObjectLayerFn().DeleteObject(ctx, bucket, object) return newObjectLayerFn().DeleteObject(ctx, bucket, object)
@@ -989,8 +990,8 @@ func newServerCacheObjects(config CacheConfig) (CacheObjectLayer, error) {
GetBucketInfoFn: func(ctx context.Context, bucket string) (bucketInfo BucketInfo, err error) { GetBucketInfoFn: func(ctx context.Context, bucket string) (bucketInfo BucketInfo, err error) {
return newObjectLayerFn().GetBucketInfo(ctx, bucket) return newObjectLayerFn().GetBucketInfo(ctx, bucket)
}, },
NewMultipartUploadFn: func(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) { NewMultipartUploadFn: func(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error) {
return newObjectLayerFn().NewMultipartUpload(ctx, bucket, object, metadata, opts) return newObjectLayerFn().NewMultipartUpload(ctx, bucket, object, opts)
}, },
PutObjectPartFn: func(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) { PutObjectPartFn: func(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) {
return newObjectLayerFn().PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts) return newObjectLayerFn().PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
+4 -5
View File
@@ -192,14 +192,13 @@ func TestDiskCache(t *testing.T) {
objInfo.ContentType = contentType objInfo.ContentType = contentType
objInfo.ETag = etag objInfo.ETag = etag
objInfo.UserDefined = httpMeta objInfo.UserDefined = httpMeta
opts := ObjectOptions{} var opts ObjectOptions
byteReader := bytes.NewReader([]byte(content)) byteReader := bytes.NewReader([]byte(content))
hashReader, err := hash.NewReader(byteReader, int64(size), "", "", int64(size)) hashReader, err := hash.NewReader(byteReader, int64(size), "", "", int64(size))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = cache.Put(ctx, bucketName, objectName, NewPutObjReader(hashReader, nil, nil), httpMeta, opts) err = cache.Put(ctx, bucketName, objectName, NewPutObjReader(hashReader, nil, nil), ObjectOptions{UserDefined: httpMeta})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -275,12 +274,12 @@ func TestDiskCacheMaxUse(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if !cache.diskAvailable(int64(size)) { if !cache.diskAvailable(int64(size)) {
err = cache.Put(ctx, bucketName, objectName, NewPutObjReader(hashReader, nil, nil), httpMeta, opts) err = cache.Put(ctx, bucketName, objectName, NewPutObjReader(hashReader, nil, nil), ObjectOptions{UserDefined: httpMeta})
if err != errDiskFull { if err != errDiskFull {
t.Fatal("Cache max-use limit violated.") t.Fatal("Cache max-use limit violated.")
} }
} else { } else {
err = cache.Put(ctx, bucketName, objectName, NewPutObjReader(hashReader, nil, nil), httpMeta, opts) err = cache.Put(ctx, bucketName, objectName, NewPutObjReader(hashReader, nil, nil), ObjectOptions{UserDefined: httpMeta})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+234
View File
@@ -0,0 +1,234 @@
/*
* Minio Cloud Storage, (C) 2018, 2019 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 cmd
import (
"encoding/xml"
"net/http"
"github.com/gorilla/mux"
"github.com/minio/minio/pkg/policy"
)
// Data types used for returning dummy tagging XML.
// These variables shouldn't be used elsewhere.
// They are only defined to be used in this file alone.
type tagging struct {
XMLName xml.Name `xml:"Tagging"`
TagSet tagSet `xml:"TagSet"`
}
type tagSet struct {
Tag []tagElem `xml:"Tag"`
}
type tagElem struct {
Key string `xml:"Key"`
Value string `xml:"Value"`
}
// GetBucketWebsite - GET bucket website, a dummy api
func (api objectAPIHandlers) GetBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// GetBucketVersioning - GET bucket versioning, a dummy api
func (api objectAPIHandlers) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// GetBucketAccelerate - GET bucket accelerate, a dummy api
func (api objectAPIHandlers) GetBucketAccelerateHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// GetBucketRequestPaymentHandler - GET bucket requestPayment, a dummy api
func (api objectAPIHandlers) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// GetBucketLoggingHandler - GET bucket logging, a dummy api
func (api objectAPIHandlers) GetBucketLoggingHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// GetBucketLifecycleHandler - GET bucket lifecycle, a dummy api
func (api objectAPIHandlers) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// GetBucketReplicationHandler - GET bucket replication, a dummy api
func (api objectAPIHandlers) GetBucketReplicationHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// DeleteBucketTaggingHandler - DELETE bucket tagging, a dummy api
func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
// DeleteBucketWebsiteHandler - DELETE bucket website, a dummy api
func (api objectAPIHandlers) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Request) {
writeSuccessResponseHeadersOnly(w)
w.(http.Flusher).Flush()
}
type allowedMethod string
// Define strings
const (
GET allowedMethod = http.MethodGet
PUT allowedMethod = http.MethodPut
HEAD allowedMethod = http.MethodHead
POST allowedMethod = http.MethodPost
DELETE allowedMethod = http.MethodDelete
)
// GetBucketCorsHandler - GET bucket cors, a dummy api
func (api objectAPIHandlers) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketCorsHandler")
type corsRule struct {
AllowedHeaders []string `xml:"AllowedHeaders"`
AllowedMethods []allowedMethod `xml:"AllowedMethod"`
AllowedOrigins []string `xml:"AllowedOrigin"`
ExposeHeaders []string `xml:"ExposeHeader"`
MaxAgeSeconds int64 `xml:"MaxAgeSeconds"`
}
type corsConfiguration struct {
XMLName xml.Name `xml:"CORSConfiguration"`
CorsRule []corsRule `xml:"CORSRule"`
}
vars := mux.Vars(r)
bucket := vars["bucket"]
objAPI := api.ObjectAPI()
if objAPI == nil {
writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
// Allow getBucketCors if policy action is set, since this is a dummy call
// we are simply re-purposing the bucketPolicyAction.
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
// Validate if bucket exists, before proceeding further...
_, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil {
writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
cors := &corsConfiguration{}
if err := xml.NewEncoder(w).Encode(cors); err != nil {
writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
w.(http.Flusher).Flush()
}
// GetBucketTaggingHandler - GET bucket tagging, a dummy api
func (api objectAPIHandlers) GetBucketTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetBucketTagging")
vars := mux.Vars(r)
bucket := vars["bucket"]
objAPI := api.ObjectAPI()
if objAPI == nil {
writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
// Allow getBucketTagging if policy action is set, since this is a dummy call
// we are simply re-purposing the bucketPolicyAction.
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
// Validate if bucket exists, before proceeding further...
_, err := objAPI.GetBucketInfo(ctx, bucket)
if err != nil {
writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
tags := &tagging{}
tags.TagSet.Tag = append(tags.TagSet.Tag, tagElem{})
if err := xml.NewEncoder(w).Encode(tags); err != nil {
writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
w.(http.Flusher).Flush()
}
// GetObjectTaggingHandler - GET object tagging, a dummy api
func (api objectAPIHandlers) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "GetObjectTagging")
vars := mux.Vars(r)
bucket := vars["bucket"]
object := vars["object"]
objAPI := api.ObjectAPI()
if objAPI == nil {
writeErrorResponse(w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL, guessIsBrowserReq(r))
return
}
// Allow getObjectTagging if policy action is set, since this is a dummy call
// we are simply re-purposing the bucketPolicyAction.
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketPolicyAction, bucket, ""); s3Error != ErrNone {
writeErrorResponse(w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
return
}
// Validate if object exists, before proceeding further...
_, err := objAPI.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
if err != nil {
writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
tags := &tagging{}
tags.TagSet.Tag = append(tags.TagSet.Tag, tagElem{})
if err := xml.NewEncoder(w).Encode(tags); err != nil {
writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return
}
w.(http.Flusher).Flush()
}
+4 -4
View File
@@ -71,7 +71,7 @@ func (api *DummyObjectLayer) GetObjectInfo(ctx context.Context, bucket, object s
return return
} }
func (api *DummyObjectLayer) PutObject(ctx context.Context, bucket, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) { func (api *DummyObjectLayer) PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return return
} }
@@ -87,7 +87,7 @@ func (api *DummyObjectLayer) ListMultipartUploads(ctx context.Context, bucket, p
return return
} }
func (api *DummyObjectLayer) NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) { func (api *DummyObjectLayer) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error) {
return return
} }
@@ -119,11 +119,11 @@ func (api *DummyObjectLayer) HealFormat(ctx context.Context, dryRun bool) (item
return return
} }
func (api *DummyObjectLayer) HealBucket(ctx context.Context, bucket string, dryRun bool) (items []madmin.HealResultItem, err error) { func (api *DummyObjectLayer) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (items madmin.HealResultItem, err error) {
return return
} }
func (api *DummyObjectLayer) HealObject(ctx context.Context, bucket, object string, dryRun bool) (item madmin.HealResultItem, err error) { func (api *DummyObjectLayer) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool) (item madmin.HealResultItem, err error) {
return return
} }
+27 -25
View File
@@ -1103,22 +1103,22 @@ func (o *ObjectInfo) EncryptedSize() int64 {
// decryption succeeded. // decryption succeeded.
// //
// DecryptCopyObjectInfo also returns whether the object is encrypted or not. // DecryptCopyObjectInfo also returns whether the object is encrypted or not.
func DecryptCopyObjectInfo(info *ObjectInfo, headers http.Header) (apiErr APIErrorCode, encrypted bool) { func DecryptCopyObjectInfo(info *ObjectInfo, headers http.Header) (errCode APIErrorCode, encrypted bool) {
// Directories are never encrypted. // Directories are never encrypted.
if info.IsDir { if info.IsDir {
return ErrNone, false return ErrNone, false
} }
if apiErr, encrypted = ErrNone, crypto.IsEncrypted(info.UserDefined); !encrypted && crypto.SSECopy.IsRequested(headers) { if errCode, encrypted = ErrNone, crypto.IsEncrypted(info.UserDefined); !encrypted && crypto.SSECopy.IsRequested(headers) {
apiErr = ErrInvalidEncryptionParameters errCode = ErrInvalidEncryptionParameters
} else if encrypted { } else if encrypted {
if (!crypto.SSECopy.IsRequested(headers) && crypto.SSEC.IsEncrypted(info.UserDefined)) || if (!crypto.SSECopy.IsRequested(headers) && crypto.SSEC.IsEncrypted(info.UserDefined)) ||
(crypto.SSECopy.IsRequested(headers) && crypto.S3.IsEncrypted(info.UserDefined)) { (crypto.SSECopy.IsRequested(headers) && crypto.S3.IsEncrypted(info.UserDefined)) {
apiErr = ErrSSEEncryptedObject errCode = ErrSSEEncryptedObject
return return
} }
var err error var err error
if info.Size, err = info.DecryptedSize(); err != nil { if info.Size, err = info.DecryptedSize(); err != nil {
apiErr = toAPIErrorCode(context.Background(), err) errCode = toAPIErrorCode(context.Background(), err)
} }
} }
return return
@@ -1171,8 +1171,8 @@ func deriveClientKey(clientKey [32]byte, bucket, object string) [32]byte {
return key return key
} }
// extract encryption options for pass through to backend in the case of gateway // set encryption options for pass through to backend in the case of gateway and UserDefined metadata
func extractEncryptionOption(header http.Header, copySource bool, metadata map[string]string) (opts ObjectOptions, err error) { func getDefaultOpts(header http.Header, copySource bool, metadata map[string]string) (opts ObjectOptions, err error) {
var clientKey [32]byte var clientKey [32]byte
var sse encrypt.ServerSide var sse encrypt.ServerSide
@@ -1185,7 +1185,7 @@ func extractEncryptionOption(header http.Header, copySource bool, metadata map[s
if sse, err = encrypt.NewSSEC(clientKey[:]); err != nil { if sse, err = encrypt.NewSSEC(clientKey[:]); err != nil {
return return
} }
return ObjectOptions{ServerSideEncryption: encrypt.SSECopy(sse)}, nil return ObjectOptions{ServerSideEncryption: encrypt.SSECopy(sse), UserDefined: metadata}, nil
} }
return return
} }
@@ -1198,16 +1198,16 @@ func extractEncryptionOption(header http.Header, copySource bool, metadata map[s
if sse, err = encrypt.NewSSEC(clientKey[:]); err != nil { if sse, err = encrypt.NewSSEC(clientKey[:]); err != nil {
return return
} }
return ObjectOptions{ServerSideEncryption: sse}, nil return ObjectOptions{ServerSideEncryption: sse, UserDefined: metadata}, nil
} }
if crypto.S3.IsRequested(header) || (metadata != nil && crypto.S3.IsEncrypted(metadata)) { if crypto.S3.IsRequested(header) || (metadata != nil && crypto.S3.IsEncrypted(metadata)) {
return ObjectOptions{ServerSideEncryption: encrypt.NewSSE()}, nil return ObjectOptions{ServerSideEncryption: encrypt.NewSSE(), UserDefined: metadata}, nil
} }
return opts, nil return ObjectOptions{UserDefined: metadata}, nil
} }
// get ObjectOptions for GET calls from encryption headers // get ObjectOptions for GET calls from encryption headers
func getEncryptionOpts(ctx context.Context, r *http.Request, bucket, object string) (ObjectOptions, error) { func getOpts(ctx context.Context, r *http.Request, bucket, object string) (ObjectOptions, error) {
var ( var (
encryption encrypt.ServerSide encryption encrypt.ServerSide
opts ObjectOptions opts ObjectOptions
@@ -1223,30 +1223,32 @@ func getEncryptionOpts(ctx context.Context, r *http.Request, bucket, object stri
return ObjectOptions{ServerSideEncryption: encryption}, nil return ObjectOptions{ServerSideEncryption: encryption}, nil
} }
// default case of passing encryption headers to backend // default case of passing encryption headers to backend
return extractEncryptionOption(r.Header, false, nil) return getDefaultOpts(r.Header, false, nil)
} }
// get ObjectOptions for PUT calls from encryption headers // get ObjectOptions for PUT calls from encryption headers and metadata
func putEncryptionOpts(ctx context.Context, r *http.Request, bucket, object string, metadata map[string]string) (opts ObjectOptions, err error) { func putOpts(ctx context.Context, r *http.Request, bucket, object string, metadata map[string]string) (opts ObjectOptions, err error) {
// In the case of multipart custom format, the metadata needs to be checked in addition to header to see if it // In the case of multipart custom format, the metadata needs to be checked in addition to header to see if it
// is SSE-S3 encrypted, primarily because S3 protocol does not require SSE-S3 headers in PutObjectPart calls // is SSE-S3 encrypted, primarily because S3 protocol does not require SSE-S3 headers in PutObjectPart calls
if GlobalGatewaySSE.SSES3() && (crypto.S3.IsRequested(r.Header) || crypto.S3.IsEncrypted(metadata)) { if GlobalGatewaySSE.SSES3() && (crypto.S3.IsRequested(r.Header) || crypto.S3.IsEncrypted(metadata)) {
return ObjectOptions{ServerSideEncryption: encrypt.NewSSE()}, nil return ObjectOptions{ServerSideEncryption: encrypt.NewSSE(), UserDefined: metadata}, nil
} }
if GlobalGatewaySSE.SSEC() && crypto.SSEC.IsRequested(r.Header) { if GlobalGatewaySSE.SSEC() && crypto.SSEC.IsRequested(r.Header) {
return getEncryptionOpts(ctx, r, bucket, object) opts, err = getOpts(ctx, r, bucket, object)
opts.UserDefined = metadata
return
} }
// default case of passing encryption headers to backend // default case of passing encryption headers and UserDefined metadata to backend
return extractEncryptionOption(r.Header, false, metadata) return getDefaultOpts(r.Header, false, metadata)
} }
// get ObjectOptions for Copy calls for encryption headers provided on the target side // get ObjectOptions for Copy calls with encryption headers provided on the target side and source side metadata
func copyDstEncryptionOpts(ctx context.Context, r *http.Request, bucket, object string, metadata map[string]string) (opts ObjectOptions, err error) { func copyDstOpts(ctx context.Context, r *http.Request, bucket, object string, metadata map[string]string) (opts ObjectOptions, err error) {
return putEncryptionOpts(ctx, r, bucket, object, metadata) return putOpts(ctx, r, bucket, object, metadata)
} }
// get ObjectOptions for Copy calls for encryption headers provided on the source side // get ObjectOptions for Copy calls with encryption headers provided on the source side
func copySrcEncryptionOpts(ctx context.Context, r *http.Request, bucket, object string) (ObjectOptions, error) { func copySrcOpts(ctx context.Context, r *http.Request, bucket, object string) (ObjectOptions, error) {
var ( var (
ssec encrypt.ServerSide ssec encrypt.ServerSide
opts ObjectOptions opts ObjectOptions
@@ -1266,5 +1268,5 @@ func copySrcEncryptionOpts(ctx context.Context, r *http.Request, bucket, object
} }
// default case of passing encryption headers to backend // default case of passing encryption headers to backend
return extractEncryptionOption(r.Header, true, nil) return getDefaultOpts(r.Header, true, nil)
} }
+4 -4
View File
@@ -678,7 +678,7 @@ func TestGetDecryptedRange(t *testing.T) {
} }
} }
var extractEncryptionOptionTests = []struct { var getDefaultOptsTests = []struct {
headers http.Header headers http.Header
copySource bool copySource bool
metadata map[string]string metadata map[string]string
@@ -741,9 +741,9 @@ var extractEncryptionOptionTests = []struct {
err: nil}, // 7 err: nil}, // 7
} }
func TestExtractEncryptionOptions(t *testing.T) { func TestGetDefaultOpts(t *testing.T) {
for i, test := range extractEncryptionOptionTests { for i, test := range getDefaultOptsTests {
opts, err := extractEncryptionOption(test.headers, test.copySource, test.metadata) opts, err := getDefaultOpts(test.headers, test.copySource, test.metadata)
if test.err != err { if test.err != err {
t.Errorf("Case %d: expected err: %v , actual err: %v", i, test.err, err) t.Errorf("Case %d: expected err: %v , actual err: %v", i, test.err, err)
} }
+2 -2
View File
@@ -54,7 +54,7 @@ func TestReadFSMetadata(t *testing.T) {
if err := obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil { if err := obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil {
t.Fatal("Unexpected err: ", err) t.Fatal("Unexpected err: ", err)
} }
if _, err := obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}); err != nil { if _, err := obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{}); err != nil {
t.Fatal("Unexpected err: ", err) t.Fatal("Unexpected err: ", err)
} }
@@ -89,7 +89,7 @@ func TestWriteFSMetadata(t *testing.T) {
if err := obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil { if err := obj.MakeBucketWithLocation(context.Background(), bucketName, ""); err != nil {
t.Fatal("Unexpected err: ", err) t.Fatal("Unexpected err: ", err)
} }
if _, err := obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}); err != nil { if _, err := obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{}); err != nil {
t.Fatal("Unexpected err: ", err) t.Fatal("Unexpected err: ", err)
} }
+2 -2
View File
@@ -210,7 +210,7 @@ func (fs *FSObjects) ListMultipartUploads(ctx context.Context, bucket, object, k
// subsequent request each UUID is unique. // subsequent request each UUID is unique.
// //
// Implements S3 compatible initiate multipart API. // Implements S3 compatible initiate multipart API.
func (fs *FSObjects) NewMultipartUpload(ctx context.Context, bucket, object string, meta map[string]string, opts ObjectOptions) (string, error) { func (fs *FSObjects) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (string, error) {
if err := checkNewMultipartArgs(ctx, bucket, object, fs); err != nil { if err := checkNewMultipartArgs(ctx, bucket, object, fs); err != nil {
return "", toObjectErr(err, bucket) return "", toObjectErr(err, bucket)
} }
@@ -230,7 +230,7 @@ func (fs *FSObjects) NewMultipartUpload(ctx context.Context, bucket, object stri
// Initialize fs.json values. // Initialize fs.json values.
fsMeta := newFSMetaV1() fsMeta := newFSMetaV1()
fsMeta.Meta = meta fsMeta.Meta = opts.UserDefined
fsMetaBytes, err := json.Marshal(fsMeta) fsMetaBytes, err := json.Marshal(fsMeta)
if err != nil { if err != nil {
+7 -7
View File
@@ -42,7 +42,7 @@ func TestFSCleanupMultipartUploadsInRoutine(t *testing.T) {
objectName := "object" objectName := "object"
obj.MakeBucketWithLocation(context.Background(), bucketName, "") obj.MakeBucketWithLocation(context.Background(), bucketName, "")
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, nil, ObjectOptions{}) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{})
if err != nil { if err != nil {
t.Fatal("Unexpected err: ", err) t.Fatal("Unexpected err: ", err)
} }
@@ -81,7 +81,7 @@ func TestNewMultipartUploadFaultyDisk(t *testing.T) {
// Test with disk removed. // Test with disk removed.
os.RemoveAll(disk) os.RemoveAll(disk)
if _, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}, ObjectOptions{}); err != nil { if _, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}}); err != nil {
if !isSameType(err, BucketNotFound{}) { if !isSameType(err, BucketNotFound{}) {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -105,7 +105,7 @@ func TestPutObjectPartFaultyDisk(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}, ObjectOptions{}) uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -136,7 +136,7 @@ func TestCompleteMultipartUploadFaultyDisk(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}, ObjectOptions{}) uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -168,7 +168,7 @@ func TestCompleteMultipartUpload(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}, ObjectOptions{}) uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -202,7 +202,7 @@ func TestAbortMultipartUpload(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}, ObjectOptions{}) uploadID, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
@@ -234,7 +234,7 @@ func TestListMultipartUploadsFaultyDisk(t *testing.T) {
t.Fatal("Cannot create bucket, err: ", err) t.Fatal("Cannot create bucket, err: ", err)
} }
_, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, map[string]string{"X-Amz-Meta-xid": "3f"}, ObjectOptions{}) _, err := fs.NewMultipartUpload(context.Background(), bucketName, objectName, ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-xid": "3f"}})
if err != nil { if err != nil {
t.Fatal("Unexpected error ", err) t.Fatal("Unexpected error ", err)
} }
+8 -8
View File
@@ -449,7 +449,7 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu
// Return the new object info. // Return the new object info.
return fsMeta.ToObjectInfo(srcBucket, srcObject, fi), nil return fsMeta.ToObjectInfo(srcBucket, srcObject, fi), nil
} }
objInfo, err := fs.putObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, srcInfo.UserDefined, dstOpts) objInfo, err := fs.putObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, ObjectOptions{ServerSideEncryption: dstOpts.ServerSideEncryption, UserDefined: srcInfo.UserDefined})
if err != nil { if err != nil {
return oi, toObjectErr(err, dstBucket, dstObject) return oi, toObjectErr(err, dstBucket, dstObject)
} }
@@ -804,7 +804,7 @@ func (fs *FSObjects) parentDirIsObject(ctx context.Context, bucket, parent strin
// until EOF, writes data directly to configured filesystem path. // until EOF, writes data directly to configured filesystem path.
// Additionally writes `fs.json` which carries the necessary metadata // Additionally writes `fs.json` which carries the necessary metadata
// for future object operations. // for future object operations.
func (fs *FSObjects) PutObject(ctx context.Context, bucket string, object string, r *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, retErr error) { func (fs *FSObjects) PutObject(ctx context.Context, bucket string, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, retErr error) {
if err := checkPutObjectArgs(ctx, bucket, object, fs, r.Size()); err != nil { if err := checkPutObjectArgs(ctx, bucket, object, fs, r.Size()); err != nil {
return ObjectInfo{}, err return ObjectInfo{}, err
} }
@@ -815,16 +815,16 @@ func (fs *FSObjects) PutObject(ctx context.Context, bucket string, object string
return objInfo, err return objInfo, err
} }
defer objectLock.Unlock() defer objectLock.Unlock()
return fs.putObject(ctx, bucket, object, r, metadata, opts) return fs.putObject(ctx, bucket, object, r, opts)
} }
// putObject - wrapper for PutObject // putObject - wrapper for PutObject
func (fs *FSObjects) putObject(ctx context.Context, bucket string, object string, r *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, retErr error) { func (fs *FSObjects) putObject(ctx context.Context, bucket string, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, retErr error) {
data := r.Reader data := r.Reader
// No metadata is set, allocate a new one. // No metadata is set, allocate a new one.
meta := make(map[string]string) meta := make(map[string]string)
for k, v := range metadata { for k, v := range opts.UserDefined {
meta[k] = v meta[k] = v
} }
var err error var err error
@@ -1245,17 +1245,17 @@ func (fs *FSObjects) HealFormat(ctx context.Context, dryRun bool) (madmin.HealRe
} }
// HealObject - no-op for fs. Valid only for XL. // HealObject - no-op for fs. Valid only for XL.
func (fs *FSObjects) HealObject(ctx context.Context, bucket, object string, dryRun bool) ( func (fs *FSObjects) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool) (
res madmin.HealResultItem, err error) { res madmin.HealResultItem, err error) {
logger.LogIf(ctx, NotImplemented{}) logger.LogIf(ctx, NotImplemented{})
return res, NotImplemented{} return res, NotImplemented{}
} }
// HealBucket - no-op for fs, Valid only for XL. // HealBucket - no-op for fs, Valid only for XL.
func (fs *FSObjects) HealBucket(ctx context.Context, bucket string, dryRun bool) ([]madmin.HealResultItem, func (fs *FSObjects) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem,
error) { error) {
logger.LogIf(ctx, NotImplemented{}) logger.LogIf(ctx, NotImplemented{})
return nil, NotImplemented{} return madmin.HealResultItem{}, NotImplemented{}
} }
// ListObjectsHeal - list all objects to be healed. Valid only for XL // ListObjectsHeal - list all objects to be healed. Valid only for XL
+9 -9
View File
@@ -40,7 +40,7 @@ func TestFSParentDirIsObject(t *testing.T) {
} }
objectContent := "12345" objectContent := "12345"
objInfo, err := obj.PutObject(context.Background(), bucketName, objectName, objInfo, err := obj.PutObject(context.Background(), bucketName, objectName,
mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), nil, ObjectOptions{}) mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -124,7 +124,7 @@ func TestFSShutdown(t *testing.T) {
objectContent := "12345" objectContent := "12345"
obj.MakeBucketWithLocation(context.Background(), bucketName, "") obj.MakeBucketWithLocation(context.Background(), bucketName, "")
obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), nil, ObjectOptions{}) obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{})
return fs, disk return fs, disk
} }
@@ -203,7 +203,7 @@ func TestFSPutObject(t *testing.T) {
} }
// With a regular object. // With a regular object.
_, err := obj.PutObject(context.Background(), bucketName+"non-existent", objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}) _, err := obj.PutObject(context.Background(), bucketName+"non-existent", objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{})
if err == nil { if err == nil {
t.Fatal("Unexpected should fail here, bucket doesn't exist") t.Fatal("Unexpected should fail here, bucket doesn't exist")
} }
@@ -212,7 +212,7 @@ func TestFSPutObject(t *testing.T) {
} }
// With a directory object. // With a directory object.
_, err = obj.PutObject(context.Background(), bucketName+"non-existent", objectName+"/", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), 0, "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName+"non-existent", objectName+"/", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), 0, "", ""), ObjectOptions{})
if err == nil { if err == nil {
t.Fatal("Unexpected should fail here, bucket doesn't exist") t.Fatal("Unexpected should fail here, bucket doesn't exist")
} }
@@ -220,11 +220,11 @@ func TestFSPutObject(t *testing.T) {
t.Fatalf("Expected error type BucketNotFound, got %#v", err) t.Fatalf("Expected error type BucketNotFound, got %#v", err)
} }
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
_, err = obj.PutObject(context.Background(), bucketName, objectName+"/1", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName+"/1", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{})
if err == nil { if err == nil {
t.Fatal("Unexpected should fail here, backend corruption occurred") t.Fatal("Unexpected should fail here, backend corruption occurred")
} }
@@ -239,7 +239,7 @@ func TestFSPutObject(t *testing.T) {
} }
} }
_, err = obj.PutObject(context.Background(), bucketName, objectName+"/1/", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), 0, "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName+"/1/", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), 0, "", ""), ObjectOptions{})
if err == nil { if err == nil {
t.Fatal("Unexpected should fail here, backned corruption occurred") t.Fatal("Unexpected should fail here, backned corruption occurred")
} }
@@ -267,7 +267,7 @@ func TestFSDeleteObject(t *testing.T) {
objectName := "object" objectName := "object"
obj.MakeBucketWithLocation(context.Background(), bucketName, "") obj.MakeBucketWithLocation(context.Background(), bucketName, "")
obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}) obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{})
// Test with invalid bucket name // Test with invalid bucket name
if err := fs.DeleteObject(context.Background(), "fo", objectName); !isSameType(err, BucketNameInvalid{}) { if err := fs.DeleteObject(context.Background(), "fo", objectName); !isSameType(err, BucketNameInvalid{}) {
@@ -390,7 +390,7 @@ func TestFSHealObject(t *testing.T) {
defer os.RemoveAll(disk) defer os.RemoveAll(disk)
obj := initFSObjects(disk, t) obj := initFSObjects(disk, t)
_, err := obj.HealObject(context.Background(), "bucket", "object", false) _, err := obj.HealObject(context.Background(), "bucket", "object", false, false)
if err == nil || !isSameType(err, NotImplemented{}) { if err == nil || !isSameType(err, NotImplemented{}) {
t.Fatalf("Heal Object should return NotImplemented error ") t.Fatalf("Heal Object should return NotImplemented error ")
} }
+3
View File
@@ -203,6 +203,9 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// Override any values from ENVs. // Override any values from ENVs.
srvCfg.loadFromEnvs() srvCfg.loadFromEnvs()
// Load values to cached global values.
srvCfg.loadToCachedConfigs()
// hold the mutex lock before a new config is assigned. // hold the mutex lock before a new config is assigned.
globalServerConfigMu.Lock() globalServerConfigMu.Lock()
globalServerConfig = srvCfg globalServerConfig = srvCfg
+4 -4
View File
@@ -33,7 +33,7 @@ func (a GatewayUnsupported) ListMultipartUploads(ctx context.Context, bucket str
} }
// NewMultipartUpload upload object in multiple parts // NewMultipartUpload upload object in multiple parts
func (a GatewayUnsupported) NewMultipartUpload(ctx context.Context, bucket string, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) { func (a GatewayUnsupported) NewMultipartUpload(ctx context.Context, bucket string, object string, opts ObjectOptions) (uploadID string, err error) {
return "", NotImplemented{} return "", NotImplemented{}
} }
@@ -92,8 +92,8 @@ func (a GatewayUnsupported) HealFormat(ctx context.Context, dryRun bool) (madmin
} }
// HealBucket - Not implemented stub // HealBucket - Not implemented stub
func (a GatewayUnsupported) HealBucket(ctx context.Context, bucket string, dryRun bool) ([]madmin.HealResultItem, error) { func (a GatewayUnsupported) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error) {
return nil, NotImplemented{} return madmin.HealResultItem{}, NotImplemented{}
} }
// ListBucketsHeal - Not implemented stub // ListBucketsHeal - Not implemented stub
@@ -102,7 +102,7 @@ func (a GatewayUnsupported) ListBucketsHeal(ctx context.Context) (buckets []Buck
} }
// HealObject - Not implemented stub // HealObject - Not implemented stub
func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object string, dryRun bool) (h madmin.HealResultItem, e error) { func (a GatewayUnsupported) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool) (h madmin.HealResultItem, e error) {
return h, NotImplemented{} return h, NotImplemented{}
} }
+177 -151
View File
@@ -26,6 +26,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"path"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -52,6 +53,8 @@ const (
metadataObjectNameTemplate = minio.GatewayMinioSysTmp + "multipart/v1/%s.%x/azure.json" metadataObjectNameTemplate = minio.GatewayMinioSysTmp + "multipart/v1/%s.%x/azure.json"
azureBackend = "azure" azureBackend = "azure"
azureMarkerPrefix = "{minio}" azureMarkerPrefix = "{minio}"
metadataPartNamePrefix = minio.GatewayMinioSysTmp + "multipart/v1/%s.%x"
maxPartsCount = 10000
) )
func init() { func init() {
@@ -261,6 +264,26 @@ func s3MetaToAzureProperties(ctx context.Context, s3Metadata map[string]string)
return blobMeta, props, nil return blobMeta, props, nil
} }
const (
partMetaVersionV1 = "1"
)
// partMetadataV1 struct holds the part specific metadata for
// multipart operations.
type partMetadataV1 struct {
Version string `json:"version"`
Size int64 `json:"Size"`
BlockIDs []string `json:"blockIDs"`
ETag string `json:"etag"`
}
// Returns the initialized part metadata struct
func newPartMetaV1(uploadID string, partID int) (partMeta *partMetadataV1) {
p := &partMetadataV1{}
p.Version = partMetaVersionV1
return p
}
// azurePropertiesToS3Meta converts Azure metadata/properties to S3 // azurePropertiesToS3Meta converts Azure metadata/properties to S3
// metadata. It is the reverse of s3MetaToAzureProperties. Azure's // metadata. It is the reverse of s3MetaToAzureProperties. Azure's
// `.GetMetadata()` lower-cases all header keys, so this is taken into // `.GetMetadata()` lower-cases all header keys, so this is taken into
@@ -405,37 +428,13 @@ func checkAzureUploadID(ctx context.Context, uploadID string) (err error) {
return nil return nil
} }
// Encode partID, subPartNumber, uploadID and md5Hex to blockID. // parses partID from part metadata file name
func azureGetBlockID(partID, subPartNumber int, uploadID, md5Hex string) string { func parseAzurePart(metaPartFileName, prefix string) (partID int, err error) {
return base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%05d.%02d.%s.%s", partID, subPartNumber, uploadID, md5Hex))) partStr := strings.TrimPrefix(metaPartFileName, prefix+"/")
} if partID, err = strconv.Atoi(partStr); err != nil || partID <= 0 {
err = fmt.Errorf("invalid part number in block id '%s'", string(partID))
// Parse blockID into partID, subPartNumber and md5Hex.
func azureParseBlockID(blockID string) (partID, subPartNumber int, uploadID, md5Hex string, err error) {
var blockIDBytes []byte
if blockIDBytes, err = base64.StdEncoding.DecodeString(blockID); err != nil {
return return
} }
tokens := strings.Split(string(blockIDBytes), ".")
if len(tokens) != 4 {
err = fmt.Errorf("invalid block id '%s'", string(blockIDBytes))
return
}
if partID, err = strconv.Atoi(tokens[0]); err != nil || partID <= 0 {
err = fmt.Errorf("invalid part number in block id '%s'", string(blockIDBytes))
return
}
if subPartNumber, err = strconv.Atoi(tokens[1]); err != nil || subPartNumber <= 0 {
err = fmt.Errorf("invalid sub-part number in block id '%s'", string(blockIDBytes))
return
}
uploadID = tokens[2]
md5Hex = tokens[3]
return return
} }
@@ -736,25 +735,18 @@ func (a *azureObjects) GetObjectInfo(ctx context.Context, bucket, object string,
// PutObject - Create a new blob with the incoming data, // PutObject - Create a new blob with the incoming data,
// uses Azure equivalent CreateBlockBlobFromReader. // uses Azure equivalent CreateBlockBlobFromReader.
func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *minio.PutObjReader, metadata map[string]string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) { func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
data := r.Reader data := r.Reader
if data.Size() < azureBlockSize/10 { if data.Size() < azureBlockSize/10 {
blob := a.client.GetContainerReference(bucket).GetBlobReference(object) blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
blob.Metadata, blob.Properties, err = s3MetaToAzureProperties(ctx, metadata) blob.Metadata, blob.Properties, err = s3MetaToAzureProperties(ctx, opts.UserDefined)
if err = blob.CreateBlockBlobFromReader(data, nil); err != nil { if err = blob.CreateBlockBlobFromReader(data, nil); err != nil {
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
return a.GetObjectInfo(ctx, bucket, object, opts) return a.GetObjectInfo(ctx, bucket, object, opts)
} }
uuid, err := getAzureUploadID() blockIDs := make(map[string]string)
if err != nil {
return objInfo, err
}
etag := data.MD5HexString()
if etag == "" {
etag = minio.GenETag()
}
blob := a.client.GetContainerReference(bucket).GetBlobReference(object) blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
subPartSize, subPartNumber := int64(azureBlockSize), 1 subPartSize, subPartNumber := int64(azureBlockSize), 1
@@ -768,7 +760,8 @@ func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *
subPartSize = remainingSize subPartSize = remainingSize
} }
id := azureGetBlockID(1, subPartNumber, uuid, etag) id := base64.StdEncoding.EncodeToString([]byte(minio.MustGetUUID()))
blockIDs[id] = ""
if err = blob.PutBlockWithLength(id, uint64(subPartSize), io.LimitReader(data, subPartSize), nil); err != nil { if err = blob.PutBlockWithLength(id, uint64(subPartSize), io.LimitReader(data, subPartSize), nil); err != nil {
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
@@ -780,17 +773,9 @@ func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *
if err != nil { if err != nil {
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
getBlocks := func(blocksMap map[string]string) (blocks []storage.Block, size int64, aerr error) {
getBlocks := func(partNumber int, etag string) (blocks []storage.Block, size int64, aerr error) {
for _, part := range resp.UncommittedBlocks { for _, part := range resp.UncommittedBlocks {
var partID int if _, ok := blocksMap[part.Name]; ok {
var readUploadID string
var md5Hex string
if partID, _, readUploadID, md5Hex, aerr = azureParseBlockID(part.Name); aerr != nil {
return nil, 0, aerr
}
if partNumber == partID && uuid == readUploadID && etag == md5Hex {
blocks = append(blocks, storage.Block{ blocks = append(blocks, storage.Block{
ID: part.Name, ID: part.Name,
Status: storage.BlockStatusUncommitted, Status: storage.BlockStatusUncommitted,
@@ -808,7 +793,7 @@ func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *
} }
var blocks []storage.Block var blocks []storage.Block
blocks, _, err = getBlocks(1, etag) blocks, _, err = getBlocks(blockIDs)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return objInfo, err return objInfo, err
@@ -818,13 +803,13 @@ func (a *azureObjects) PutObject(ctx context.Context, bucket, object string, r *
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
if len(metadata) == 0 { if len(opts.UserDefined) == 0 {
metadata = map[string]string{} opts.UserDefined = map[string]string{}
} }
// Save md5sum for future processing on the object. // Save md5sum for future processing on the object.
metadata["x-amz-meta-md5sum"] = hex.EncodeToString(data.MD5Current()) opts.UserDefined["x-amz-meta-md5sum"] = hex.EncodeToString(data.MD5Current())
objBlob.Metadata, objBlob.Properties, err = s3MetaToAzureProperties(ctx, metadata) objBlob.Metadata, objBlob.Properties, err = s3MetaToAzureProperties(ctx, opts.UserDefined)
if err != nil { if err != nil {
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
@@ -896,6 +881,17 @@ func getAzureMetadataObjectName(objectName, uploadID string) string {
return fmt.Sprintf(metadataObjectNameTemplate, uploadID, sha256.Sum256([]byte(objectName))) return fmt.Sprintf(metadataObjectNameTemplate, uploadID, sha256.Sum256([]byte(objectName)))
} }
// gets the name of part metadata file for multipart upload operations
func getAzureMetadataPartName(objectName, uploadID string, partID int) string {
partMetaPrefix := getAzureMetadataPartPrefix(uploadID, objectName)
return path.Join(partMetaPrefix, fmt.Sprintf("%d", partID))
}
// gets the prefix of part metadata file
func getAzureMetadataPartPrefix(uploadID, objectName string) string {
return fmt.Sprintf(metadataPartNamePrefix, uploadID, sha256.Sum256([]byte(objectName)))
}
func (a *azureObjects) checkUploadIDExists(ctx context.Context, bucketName, objectName, uploadID string) (err error) { func (a *azureObjects) checkUploadIDExists(ctx context.Context, bucketName, objectName, uploadID string) (err error) {
blob := a.client.GetContainerReference(bucketName).GetBlobReference( blob := a.client.GetContainerReference(bucketName).GetBlobReference(
getAzureMetadataObjectName(objectName, uploadID)) getAzureMetadataObjectName(objectName, uploadID))
@@ -914,7 +910,7 @@ func (a *azureObjects) checkUploadIDExists(ctx context.Context, bucketName, obje
} }
// NewMultipartUpload - Use Azure equivalent CreateBlockBlob. // NewMultipartUpload - Use Azure equivalent CreateBlockBlob.
func (a *azureObjects) NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, opts minio.ObjectOptions) (uploadID string, err error) { func (a *azureObjects) NewMultipartUpload(ctx context.Context, bucket, object string, opts minio.ObjectOptions) (uploadID string, err error) {
uploadID, err = getAzureUploadID() uploadID, err = getAzureUploadID()
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
@@ -923,7 +919,7 @@ func (a *azureObjects) NewMultipartUpload(ctx context.Context, bucket, object st
metadataObject := getAzureMetadataObjectName(object, uploadID) metadataObject := getAzureMetadataObjectName(object, uploadID)
var jsonData []byte var jsonData []byte
if jsonData, err = json.Marshal(azureMultipartMetadata{Name: object, Metadata: metadata}); err != nil { if jsonData, err = json.Marshal(azureMultipartMetadata{Name: object, Metadata: opts.UserDefined}); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return "", err return "", err
} }
@@ -948,11 +944,7 @@ func (a *azureObjects) PutObjectPart(ctx context.Context, bucket, object, upload
return info, err return info, err
} }
etag := data.MD5HexString() partMetaV1 := newPartMetaV1(uploadID, partID)
if etag == "" {
etag = minio.GenETag()
}
subPartSize, subPartNumber := int64(azureBlockSize), 1 subPartSize, subPartNumber := int64(azureBlockSize), 1
for remainingSize := data.Size(); remainingSize >= 0; remainingSize -= subPartSize { for remainingSize := data.Size(); remainingSize >= 0; remainingSize -= subPartSize {
// Allow to create zero sized part. // Allow to create zero sized part.
@@ -964,7 +956,9 @@ func (a *azureObjects) PutObjectPart(ctx context.Context, bucket, object, upload
subPartSize = remainingSize subPartSize = remainingSize
} }
id := azureGetBlockID(partID, subPartNumber, uploadID, etag) id := base64.StdEncoding.EncodeToString([]byte(minio.MustGetUUID()))
partMetaV1.BlockIDs = append(partMetaV1.BlockIDs, id)
blob := a.client.GetContainerReference(bucket).GetBlobReference(object) blob := a.client.GetContainerReference(bucket).GetBlobReference(object)
err = blob.PutBlockWithLength(id, uint64(subPartSize), io.LimitReader(data, subPartSize), nil) err = blob.PutBlockWithLength(id, uint64(subPartSize), io.LimitReader(data, subPartSize), nil)
if err != nil { if err != nil {
@@ -973,8 +967,26 @@ func (a *azureObjects) PutObjectPart(ctx context.Context, bucket, object, upload
subPartNumber++ subPartNumber++
} }
partMetaV1.ETag = r.MD5CurrentHexString()
partMetaV1.Size = data.Size()
// maintain per part md5sum in a temporary part metadata file until upload
// is finalized.
metadataObject := getAzureMetadataPartName(object, uploadID, partID)
var jsonData []byte
if jsonData, err = json.Marshal(partMetaV1); err != nil {
logger.LogIf(ctx, err)
return info, err
}
blob := a.client.GetContainerReference(bucket).GetBlobReference(metadataObject)
err = blob.CreateBlockBlobFromReader(bytes.NewBuffer(jsonData), nil)
if err != nil {
return info, azureToObjectError(err, bucket, metadataObject)
}
info.PartNumber = partID info.PartNumber = partID
info.ETag = etag info.ETag = partMetaV1.ETag
info.LastModified = minio.UTCNow() info.LastModified = minio.UTCNow()
info.Size = data.Size() info.Size = data.Size()
return info, nil return info, nil
@@ -991,48 +1003,57 @@ func (a *azureObjects) ListObjectParts(ctx context.Context, bucket, object, uplo
result.UploadID = uploadID result.UploadID = uploadID
result.MaxParts = maxParts result.MaxParts = maxParts
objBlob := a.client.GetContainerReference(bucket).GetBlobReference(object) var parts []minio.PartInfo
resp, err := objBlob.GetBlockList(storage.BlockListTypeUncommitted, nil) var marker, delimiter string
azureErr, ok := err.(storage.AzureStorageServiceError) maxKeys := maxPartsCount
if ok && azureErr.StatusCode == http.StatusNotFound { if partNumberMarker == 0 {
// If no parts are uploaded yet then we return empty list. maxKeys = maxParts
return result, nil
} }
prefix := getAzureMetadataPartPrefix(uploadID, object)
container := a.client.GetContainerReference(bucket)
resp, err := container.ListBlobs(storage.ListBlobsParameters{
Prefix: prefix,
Marker: marker,
Delimiter: delimiter,
MaxResults: uint(maxKeys),
})
if err != nil { if err != nil {
return result, azureToObjectError(err, bucket, prefix)
}
for _, blob := range resp.Blobs {
if delimiter == "" && !strings.HasPrefix(blob.Name, minio.GatewayMinioSysTmp) {
// We filter out non minio.GatewayMinioSysTmp entries in the recursive listing.
continue
}
// filter temporary metadata file for blob
if strings.HasSuffix(blob.Name, "azure.json") {
continue
}
if !isAzureMarker(marker) && blob.Name <= marker {
// If the application used ListObjectsV1 style marker then we
// skip all the entries till we reach the marker.
continue
}
partNumber, err := parseAzurePart(blob.Name, prefix)
if err != nil {
return result, azureToObjectError(fmt.Errorf("Unexpected error"), bucket, object)
}
var metadata partMetadataV1
var metadataReader io.Reader
blob := a.client.GetContainerReference(bucket).GetBlobReference(blob.Name)
if metadataReader, err = blob.Get(nil); err != nil {
return result, azureToObjectError(fmt.Errorf("Unexpected error"), bucket, object)
}
if err = json.NewDecoder(metadataReader).Decode(&metadata); err != nil {
logger.LogIf(ctx, err)
return result, azureToObjectError(err, bucket, object) return result, azureToObjectError(err, bucket, object)
} }
// Build a sorted list of parts and return the requested entries. parts = append(parts, minio.PartInfo{
partsMap := make(map[int]minio.PartInfo)
for _, block := range resp.UncommittedBlocks {
var partNumber int
var parsedUploadID string
var md5Hex string
if partNumber, _, parsedUploadID, md5Hex, err = azureParseBlockID(block.Name); err != nil {
return result, azureToObjectError(fmt.Errorf("Unexpected error"), bucket, object)
}
if parsedUploadID != uploadID {
continue
}
part, ok := partsMap[partNumber]
if !ok {
partsMap[partNumber] = minio.PartInfo{
PartNumber: partNumber, PartNumber: partNumber,
Size: block.Size, Size: metadata.Size,
ETag: md5Hex, ETag: metadata.ETag,
} })
continue
}
if part.ETag != md5Hex {
// If two parts of same partNumber were uploaded with different contents
// return error as we won't be able to decide which the latest part is.
return result, azureToObjectError(fmt.Errorf("Unexpected error"), bucket, object)
}
part.Size += block.Size
partsMap[partNumber] = part
}
var parts []minio.PartInfo
for _, part := range partsMap {
parts = append(parts, part)
} }
sort.Slice(parts, func(i int, j int) bool { sort.Slice(parts, func(i int, j int) bool {
return parts[i].PartNumber < parts[j].PartNumber return parts[i].PartNumber < parts[j].PartNumber
@@ -1071,6 +1092,22 @@ func (a *azureObjects) AbortMultipartUpload(ctx context.Context, bucket, object,
if err = a.checkUploadIDExists(ctx, bucket, object, uploadID); err != nil { if err = a.checkUploadIDExists(ctx, bucket, object, uploadID); err != nil {
return err return err
} }
var partNumberMarker int
for {
lpi, err := a.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxPartsCount, minio.ObjectOptions{})
if err != nil {
break
}
for _, part := range lpi.Parts {
pblob := a.client.GetContainerReference(bucket).GetBlobReference(
getAzureMetadataPartName(object, uploadID, part.PartNumber))
pblob.Delete(nil)
}
partNumberMarker = lpi.NextPartNumberMarker
if !lpi.IsTruncated {
break
}
}
blob := a.client.GetContainerReference(bucket).GetBlobReference( blob := a.client.GetContainerReference(bucket).GetBlobReference(
getAzureMetadataObjectName(object, uploadID)) getAzureMetadataObjectName(object, uploadID))
@@ -1100,66 +1137,33 @@ func (a *azureObjects) CompleteMultipartUpload(ctx context.Context, bucket, obje
return objInfo, azureToObjectError(err, bucket, metadataObject) return objInfo, azureToObjectError(err, bucket, metadataObject)
} }
defer func() {
blob := a.client.GetContainerReference(bucket).GetBlobReference(metadataObject)
derr := blob.Delete(nil)
logger.GetReqInfo(ctx).AppendTags("uploadID", uploadID)
logger.LogIf(ctx, derr)
}()
objBlob := a.client.GetContainerReference(bucket).GetBlobReference(object) objBlob := a.client.GetContainerReference(bucket).GetBlobReference(object)
resp, err := objBlob.GetBlockList(storage.BlockListTypeUncommitted, nil)
if err != nil {
return objInfo, azureToObjectError(err, bucket, object)
}
getBlocks := func(partNumber int, etag string) (blocks []storage.Block, size int64, err error) {
for _, part := range resp.UncommittedBlocks {
var partID int
var readUploadID string
var md5Hex string
if partID, _, readUploadID, md5Hex, err = azureParseBlockID(part.Name); err != nil {
return nil, 0, err
}
if partNumber == partID && uploadID == readUploadID && etag == md5Hex {
blocks = append(blocks, storage.Block{
ID: part.Name,
Status: storage.BlockStatusUncommitted,
})
size += part.Size
}
}
if len(blocks) == 0 {
return nil, 0, minio.InvalidPart{}
}
return blocks, size, nil
}
var allBlocks []storage.Block var allBlocks []storage.Block
partSizes := make([]int64, len(uploadedParts))
for i, part := range uploadedParts { for i, part := range uploadedParts {
var blocks []storage.Block var partMetadataReader io.Reader
var size int64 var partMetadata partMetadataV1
blocks, size, err = getBlocks(part.PartNumber, part.ETag) partMetadataObject := getAzureMetadataPartName(object, uploadID, part.PartNumber)
if err != nil { pblob := a.client.GetContainerReference(bucket).GetBlobReference(partMetadataObject)
if partMetadataReader, err = pblob.Get(nil); err != nil {
return objInfo, azureToObjectError(err, bucket, partMetadataObject)
}
if err = json.NewDecoder(partMetadataReader).Decode(&partMetadata); err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return objInfo, err return objInfo, azureToObjectError(err, bucket, partMetadataObject)
} }
allBlocks = append(allBlocks, blocks...) if partMetadata.ETag != part.ETag {
partSizes[i] = size return objInfo, minio.InvalidPart{}
} }
for _, blockID := range partMetadata.BlockIDs {
// Error out if parts except last part sizing < 5MiB. allBlocks = append(allBlocks, storage.Block{ID: blockID, Status: storage.BlockStatusUncommitted})
for i, size := range partSizes[:len(partSizes)-1] { }
if size < azureS3MinPartSize { if i < (len(uploadedParts)-1) && partMetadata.Size < azureS3MinPartSize {
return objInfo, minio.PartTooSmall{ return objInfo, minio.PartTooSmall{
PartNumber: uploadedParts[i].PartNumber, PartNumber: uploadedParts[i].PartNumber,
PartSize: size, PartSize: partMetadata.Size,
PartETag: uploadedParts[i].ETag, PartETag: uploadedParts[i].ETag,
} }
} }
@@ -1183,6 +1187,28 @@ func (a *azureObjects) CompleteMultipartUpload(ctx context.Context, bucket, obje
return objInfo, azureToObjectError(err, bucket, object) return objInfo, azureToObjectError(err, bucket, object)
} }
} }
var partNumberMarker int
for {
lpi, err := a.ListObjectParts(ctx, bucket, object, uploadID, partNumberMarker, maxPartsCount, minio.ObjectOptions{})
if err != nil {
break
}
for _, part := range lpi.Parts {
pblob := a.client.GetContainerReference(bucket).GetBlobReference(
getAzureMetadataPartName(object, uploadID, part.PartNumber))
pblob.Delete(nil)
}
partNumberMarker = lpi.NextPartNumberMarker
if !lpi.IsTruncated {
break
}
}
blob = a.client.GetContainerReference(bucket).GetBlobReference(metadataObject)
derr := blob.Delete(nil)
logger.GetReqInfo(ctx).AppendTags("uploadID", uploadID)
logger.LogIf(ctx, derr)
return a.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{}) return a.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{})
} }
-66
View File
@@ -188,72 +188,6 @@ func TestAzureToObjectError(t *testing.T) {
} }
} }
// Test azureGetBlockID().
func TestAzureGetBlockID(t *testing.T) {
testCases := []struct {
partID int
subPartNumber int
uploadID string
md5 string
blockID string
}{
{1, 7, "f328c35cad938137", "d41d8cd98f00b204e9800998ecf8427e", "MDAwMDEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ=="},
{2, 19, "abcdc35cad938137", "a7fb6b7b36ee4ed66b5546fac4690273", "MDAwMDIuMTkuYWJjZGMzNWNhZDkzODEzNy5hN2ZiNmI3YjM2ZWU0ZWQ2NmI1NTQ2ZmFjNDY5MDI3Mw=="},
}
for _, test := range testCases {
blockID := azureGetBlockID(test.partID, test.subPartNumber, test.uploadID, test.md5)
if blockID != test.blockID {
t.Fatalf("%s is not equal to %s", blockID, test.blockID)
}
}
}
// Test azureParseBlockID().
func TestAzureParseBlockID(t *testing.T) {
testCases := []struct {
blockID string
partID int
subPartNumber int
uploadID string
md5 string
success bool
}{
// Invalid base64.
{"MDAwMDEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ=", 0, 0, "", "", false},
// Invalid number of tokens.
{"MDAwMDEuQUEuZjMyOGMzNWNhZDkzODEzNwo=", 0, 0, "", "", false},
// Invalid encoded part ID.
{"MDAwMGEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQo=", 0, 0, "", "", false},
// Invalid sub part ID.
{"MDAwMDEuQUEuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQo=", 0, 0, "", "", false},
{"MDAwMDEuMDcuZjMyOGMzNWNhZDkzODEzNy5kNDFkOGNkOThmMDBiMjA0ZTk4MDA5OThlY2Y4NDI3ZQ==", 1, 7, "f328c35cad938137", "d41d8cd98f00b204e9800998ecf8427e", true},
{"MDAwMDIuMTkuYWJjZGMzNWNhZDkzODEzNy5hN2ZiNmI3YjM2ZWU0ZWQ2NmI1NTQ2ZmFjNDY5MDI3Mw==", 2, 19, "abcdc35cad938137", "a7fb6b7b36ee4ed66b5546fac4690273", true},
}
for i, test := range testCases {
partID, subPartNumber, uploadID, md5, err := azureParseBlockID(test.blockID)
if err != nil && test.success {
t.Errorf("Test %d: Expected success but failed %s", i+1, err)
}
if err == nil && !test.success {
t.Errorf("Test %d: Expected to fail but succeeeded insteadl", i+1)
}
if err == nil {
if partID != test.partID {
t.Errorf("Test %d: %d not equal to %d", i+1, partID, test.partID)
}
if subPartNumber != test.subPartNumber {
t.Errorf("Test %d: %d not equal to %d", i+1, subPartNumber, test.subPartNumber)
}
if uploadID != test.uploadID {
t.Errorf("Test %d: %s not equal to %s", i+1, uploadID, test.uploadID)
}
if md5 != test.md5 {
t.Errorf("Test %d: %s not equal to %s", i+1, md5, test.md5)
}
}
}
}
func TestAnonErrToObjectErr(t *testing.T) { func TestAnonErrToObjectErr(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
+850
View File
@@ -0,0 +1,850 @@
/*
* Minio Cloud Storage, (C) 2017, 2018 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 b2
import (
"context"
"crypto/sha1"
"fmt"
"hash"
"io"
"io/ioutil"
"net/http"
"strings"
"sync"
"time"
b2 "github.com/minio/blazer/base"
"github.com/minio/cli"
miniogopolicy "github.com/minio/minio-go/pkg/policy"
"github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth"
h2 "github.com/minio/minio/pkg/hash"
"github.com/minio/minio/pkg/policy"
"github.com/minio/minio/pkg/policy/condition"
minio "github.com/minio/minio/cmd"
)
// Supported bucket types by B2 backend.
const (
bucketTypePrivate = "allPrivate"
bucketTypeReadOnly = "allPublic"
b2Backend = "b2"
)
func init() {
const b2GatewayTemplate = `NAME:
{{.HelpName}} - {{.Usage}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[FLAGS]{{end}}
{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
ENVIRONMENT VARIABLES:
ACCESS:
MINIO_ACCESS_KEY: B2 account id.
MINIO_SECRET_KEY: B2 application key.
BROWSER:
MINIO_BROWSER: To disable web browser access, set this value to "off".
DOMAIN:
MINIO_DOMAIN: To enable virtual-host-style requests, set this value to Minio host domain name.
CACHE:
MINIO_CACHE_DRIVES: List of mounted drives or directories delimited by ";".
MINIO_CACHE_EXCLUDE: List of cache exclusion patterns delimited by ";".
MINIO_CACHE_EXPIRY: Cache expiry duration in days.
MINIO_CACHE_MAXUSE: Maximum permitted usage of the cache in percentage (0-100).
EXAMPLES:
1. Start minio gateway server for B2 backend.
$ export MINIO_ACCESS_KEY=accountID
$ export MINIO_SECRET_KEY=applicationKey
$ {{.HelpName}}
2. Start minio gateway server for B2 backend with edge caching enabled.
$ export MINIO_ACCESS_KEY=accountID
$ export MINIO_SECRET_KEY=applicationKey
$ export MINIO_CACHE_DRIVES="/mnt/drive1;/mnt/drive2;/mnt/drive3;/mnt/drive4"
$ export MINIO_CACHE_EXCLUDE="bucket1/*;*.png"
$ export MINIO_CACHE_EXPIRY=40
$ export MINIO_CACHE_MAXUSE=80
$ {{.HelpName}}
`
minio.RegisterGatewayCommand(cli.Command{
Name: b2Backend,
Usage: "Backblaze B2",
Action: b2GatewayMain,
CustomHelpTemplate: b2GatewayTemplate,
HideHelpCommand: true,
})
}
// Handler for 'minio gateway b2' command line.
func b2GatewayMain(ctx *cli.Context) {
minio.StartGateway(ctx, &B2{})
}
// B2 implements Minio Gateway
type B2 struct{}
// Name implements Gateway interface.
func (g *B2) Name() string {
return b2Backend
}
// NewGatewayLayer returns b2 gateway layer, implements ObjectLayer interface to
// talk to B2 remote backend.
func (g *B2) NewGatewayLayer(creds auth.Credentials) (minio.ObjectLayer, error) {
ctx := context.Background()
client, err := b2.AuthorizeAccount(ctx, creds.AccessKey, creds.SecretKey, b2.Transport(minio.NewCustomHTTPTransport()))
if err != nil {
return nil, err
}
return &b2Objects{
creds: creds,
b2Client: client,
ctx: ctx,
}, nil
}
// Production - Ready for production use.
func (g *B2) Production() bool {
return true
}
// b2Object implements gateway for Minio and BackBlaze B2 compatible object storage servers.
type b2Objects struct {
minio.GatewayUnsupported
mu sync.Mutex
creds auth.Credentials
b2Client *b2.B2
ctx context.Context
}
// Convert B2 errors to minio object layer errors.
func b2ToObjectError(err error, params ...string) error {
if err == nil {
return nil
}
bucket := ""
object := ""
uploadID := ""
if len(params) >= 1 {
bucket = params[0]
}
if len(params) == 2 {
object = params[1]
}
if len(params) == 3 {
uploadID = params[2]
}
// Following code is a non-exhaustive check to convert
// B2 errors into S3 compatible errors.
//
// For a more complete information - https://www.backblaze.com/b2/docs/
statusCode, code, msg := b2.Code(err)
if statusCode == 0 {
// We don't interpret non B2 errors. B2 errors have statusCode
// to help us convert them to S3 object errors.
return err
}
switch code {
case "duplicate_bucket_name":
err = minio.BucketAlreadyOwnedByYou{Bucket: bucket}
case "bad_request":
if object != "" {
err = minio.ObjectNameInvalid{
Bucket: bucket,
Object: object,
}
} else if bucket != "" {
err = minio.BucketNotFound{Bucket: bucket}
}
case "bad_json":
if object != "" {
err = minio.ObjectNameInvalid{
Bucket: bucket,
Object: object,
}
} else if bucket != "" {
err = minio.BucketNameInvalid{Bucket: bucket}
}
case "bad_bucket_id":
err = minio.BucketNotFound{Bucket: bucket}
case "file_not_present", "not_found":
err = minio.ObjectNotFound{
Bucket: bucket,
Object: object,
}
case "cannot_delete_non_empty_bucket":
err = minio.BucketNotEmpty{Bucket: bucket}
}
// Special interpretation like this is required for Multipart sessions.
if strings.Contains(msg, "No active upload for") && uploadID != "" {
err = minio.InvalidUploadID{UploadID: uploadID}
}
return err
}
// Shutdown saves any gateway metadata to disk
// if necessary and reload upon next restart.
func (l *b2Objects) Shutdown(ctx context.Context) error {
// TODO
return nil
}
// StorageInfo is not relevant to B2 backend.
func (l *b2Objects) StorageInfo(ctx context.Context) (si minio.StorageInfo) {
return si
}
// MakeBucket creates a new container on B2 backend.
func (l *b2Objects) MakeBucketWithLocation(ctx context.Context, bucket, location string) error {
// location is ignored for B2 backend.
// All buckets are set to private by default.
_, err := l.b2Client.CreateBucket(l.ctx, bucket, bucketTypePrivate, nil, nil)
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket)
}
func (l *b2Objects) reAuthorizeAccount(ctx context.Context) error {
client, err := b2.AuthorizeAccount(l.ctx, l.creds.AccessKey, l.creds.SecretKey, b2.Transport(minio.NewCustomHTTPTransport()))
if err != nil {
return err
}
l.mu.Lock()
l.b2Client.Update(client)
l.mu.Unlock()
return nil
}
// listBuckets is a wrapper similar to ListBuckets, which re-authorizes
// the account and updates the B2 client safely. Once successfully
// authorized performs the call again and returns list of buckets.
// For any errors which are not actionable we return an error.
func (l *b2Objects) listBuckets(ctx context.Context, err error) ([]*b2.Bucket, error) {
if err != nil {
if b2.Action(err) != b2.ReAuthenticate {
return nil, err
}
if rerr := l.reAuthorizeAccount(ctx); rerr != nil {
return nil, rerr
}
}
bktList, lerr := l.b2Client.ListBuckets(l.ctx)
if lerr != nil {
return l.listBuckets(ctx, lerr)
}
return bktList, nil
}
// Bucket - is a helper which provides a *Bucket instance
// for performing an API operation. B2 API doesn't
// provide a direct way to access the bucket so we need
// to employ following technique.
func (l *b2Objects) Bucket(ctx context.Context, bucket string) (*b2.Bucket, error) {
bktList, err := l.listBuckets(ctx, nil)
if err != nil {
logger.LogIf(ctx, err)
return nil, b2ToObjectError(err, bucket)
}
for _, bkt := range bktList {
if bkt.Name == bucket {
return bkt, nil
}
}
return nil, minio.BucketNotFound{Bucket: bucket}
}
// GetBucketInfo gets bucket metadata..
func (l *b2Objects) GetBucketInfo(ctx context.Context, bucket string) (bi minio.BucketInfo, err error) {
if _, err = l.Bucket(ctx, bucket); err != nil {
return bi, err
}
return minio.BucketInfo{
Name: bucket,
Created: time.Unix(0, 0),
}, nil
}
// ListBuckets lists all B2 buckets
func (l *b2Objects) ListBuckets(ctx context.Context) ([]minio.BucketInfo, error) {
bktList, err := l.listBuckets(ctx, nil)
if err != nil {
return nil, err
}
var bktInfo []minio.BucketInfo
for _, bkt := range bktList {
bktInfo = append(bktInfo, minio.BucketInfo{
Name: bkt.Name,
Created: time.Unix(0, 0),
})
}
return bktInfo, nil
}
// DeleteBucket deletes a bucket on B2
func (l *b2Objects) DeleteBucket(ctx context.Context, bucket string) error {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return err
}
err = bkt.DeleteBucket(l.ctx)
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket)
}
// ListObjects lists all objects in B2 bucket filtered by prefix, returns upto at max 1000 entries at a time.
func (l *b2Objects) ListObjects(ctx context.Context, bucket string, prefix string, marker string, delimiter string, maxKeys int) (loi minio.ListObjectsInfo, err error) {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return loi, err
}
files, next, lerr := bkt.ListFileNames(l.ctx, maxKeys, marker, prefix, delimiter)
if lerr != nil {
logger.LogIf(ctx, lerr)
return loi, b2ToObjectError(lerr, bucket)
}
loi.IsTruncated = next != ""
loi.NextMarker = next
for _, file := range files {
switch file.Status {
case "folder":
loi.Prefixes = append(loi.Prefixes, file.Name)
case "upload":
loi.Objects = append(loi.Objects, minio.ObjectInfo{
Bucket: bucket,
Name: file.Name,
ModTime: file.Timestamp,
Size: file.Size,
ETag: minio.ToS3ETag(file.Info.ID),
ContentType: file.Info.ContentType,
UserDefined: file.Info.Info,
})
}
}
return loi, nil
}
// ListObjectsV2 lists all objects in B2 bucket filtered by prefix, returns upto max 1000 entries at a time.
func (l *b2Objects) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int,
fetchOwner bool, startAfter string) (loi minio.ListObjectsV2Info, err error) {
// fetchOwner is not supported and unused.
marker := continuationToken
if marker == "" {
// B2's continuation token is an object name to "start at" rather than "start after"
// startAfter plus the lowest character B2 supports is used so that the startAfter
// object isn't included in the results
marker = startAfter + " "
}
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return loi, err
}
files, next, lerr := bkt.ListFileNames(l.ctx, maxKeys, marker, prefix, delimiter)
if lerr != nil {
logger.LogIf(ctx, lerr)
return loi, b2ToObjectError(lerr, bucket)
}
loi.IsTruncated = next != ""
loi.ContinuationToken = continuationToken
loi.NextContinuationToken = next
for _, file := range files {
switch file.Status {
case "folder":
loi.Prefixes = append(loi.Prefixes, file.Name)
case "upload":
loi.Objects = append(loi.Objects, minio.ObjectInfo{
Bucket: bucket,
Name: file.Name,
ModTime: file.Timestamp,
Size: file.Size,
ETag: minio.ToS3ETag(file.Info.ID),
ContentType: file.Info.ContentType,
UserDefined: file.Info.Info,
})
}
}
return loi, nil
}
// GetObjectNInfo - returns object info and locked object ReadCloser
func (l *b2Objects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *minio.HTTPRangeSpec, h http.Header, lockType minio.LockType, opts minio.ObjectOptions) (gr *minio.GetObjectReader, err error) {
var objInfo minio.ObjectInfo
objInfo, err = l.GetObjectInfo(ctx, bucket, object, opts)
if err != nil {
return nil, err
}
var startOffset, length int64
startOffset, length, err = rs.GetOffsetLength(objInfo.Size)
if err != nil {
return nil, err
}
pr, pw := io.Pipe()
go func() {
err := l.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.ETag, opts)
pw.CloseWithError(err)
}()
// Setup cleanup function to cause the above go-routine to
// exit in case of partial read
pipeCloser := func() { pr.Close() }
return minio.NewGetObjectReaderFromReader(pr, objInfo, pipeCloser), nil
}
// GetObject reads an object from B2. Supports additional
// parameters like offset and length which are synonymous with
// HTTP Range requests.
//
// startOffset indicates the starting read location of the object.
// length indicates the total length of the object.
func (l *b2Objects) GetObject(ctx context.Context, bucket string, object string, startOffset int64, length int64, writer io.Writer, etag string, opts minio.ObjectOptions) error {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return err
}
reader, err := bkt.DownloadFileByName(l.ctx, object, startOffset, length)
if err != nil {
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket, object)
}
defer reader.Close()
_, err = io.Copy(writer, reader)
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket, object)
}
// GetObjectInfo reads object info and replies back ObjectInfo
func (l *b2Objects) GetObjectInfo(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return objInfo, err
}
f, err := bkt.DownloadFileByName(l.ctx, object, 0, 1)
if err != nil {
logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object)
}
f.Close()
fi, err := bkt.File(f.ID, object).GetFileInfo(l.ctx)
if err != nil {
logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object)
}
return minio.ObjectInfo{
Bucket: bucket,
Name: object,
ETag: minio.ToS3ETag(fi.ID),
Size: fi.Size,
ModTime: fi.Timestamp,
ContentType: fi.ContentType,
UserDefined: fi.Info,
}, nil
}
// In B2 - You must always include the X-Bz-Content-Sha1 header with
// your upload request. The value you provide can be:
// (1) the 40-character hex checksum of the file,
// (2) the string hex_digits_at_end, or
// (3) the string do_not_verify.
// For more reference - https://www.backblaze.com/b2/docs/uploading.html
//
// In our case we are going to use (2) option
const sha1AtEOF = "hex_digits_at_end"
// With the second option mentioned above, you append the 40-character hex sha1
// to the end of the request body, immediately after the contents of the file
// being uploaded. Note that the content length is the size of the file plus 40
// of the original size of the reader.
//
// newB2Reader implements a B2 compatible reader by wrapping the hash.Reader into
// a new io.Reader which will emit out the sha1 hex digits at io.EOF.
// It also means that your overall content size is now original size + 40 bytes.
// Additionally this reader also verifies Hash encapsulated inside hash.Reader
// at io.EOF if the verification failed we return an error and do not send
// the content to server.
func newB2Reader(r *h2.Reader, size int64) *Reader {
return &Reader{
r: r,
size: size,
sha1Hash: sha1.New(),
}
}
// Reader - is a Reader wraps the hash.Reader which will emit out the sha1
// hex digits at io.EOF. It also means that your overall content size is
// now original size + 40 bytes. Additionally this reader also verifies
// Hash encapsulated inside hash.Reader at io.EOF if the verification
// failed we return an error and do not send the content to server.
type Reader struct {
r *h2.Reader
size int64
sha1Hash hash.Hash
isEOF bool
buf *strings.Reader
}
// Size - Returns the total size of Reader.
func (nb *Reader) Size() int64 { return nb.size + 40 }
func (nb *Reader) Read(p []byte) (int, error) {
if nb.isEOF {
return nb.buf.Read(p)
}
// Read into hash to update the on going checksum.
n, err := io.TeeReader(nb.r, nb.sha1Hash).Read(p)
if err == io.EOF {
// Stream is not corrupted on this end
// now fill in the last 40 bytes of sha1 hex
// so that the server can verify the stream on
// their end.
err = nil
nb.isEOF = true
nb.buf = strings.NewReader(fmt.Sprintf("%x", nb.sha1Hash.Sum(nil)))
}
return n, err
}
// PutObject uploads the single upload to B2 backend by using *b2_upload_file* API, uploads upto 5GiB.
func (l *b2Objects) PutObject(ctx context.Context, bucket string, object string, r *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
data := r.Reader
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return objInfo, err
}
contentType := opts.UserDefined["content-type"]
delete(opts.UserDefined, "content-type")
var u *b2.URL
u, err = bkt.GetUploadURL(l.ctx)
if err != nil {
logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object)
}
hr := newB2Reader(data, data.Size())
var f *b2.File
f, err = u.UploadFile(l.ctx, hr, int(hr.Size()), object, contentType, sha1AtEOF, opts.UserDefined)
if err != nil {
logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object)
}
var fi *b2.FileInfo
fi, err = f.GetFileInfo(l.ctx)
if err != nil {
logger.LogIf(ctx, err)
return objInfo, b2ToObjectError(err, bucket, object)
}
return minio.ObjectInfo{
Bucket: bucket,
Name: object,
ETag: minio.ToS3ETag(fi.ID),
Size: fi.Size,
ModTime: fi.Timestamp,
ContentType: fi.ContentType,
UserDefined: fi.Info,
}, nil
}
// DeleteObject deletes a blob in bucket
func (l *b2Objects) DeleteObject(ctx context.Context, bucket string, object string) error {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return err
}
reader, err := bkt.DownloadFileByName(l.ctx, object, 0, 1)
if err != nil {
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket, object)
}
io.Copy(ioutil.Discard, reader)
reader.Close()
err = bkt.File(reader.ID, object).DeleteFileVersion(l.ctx)
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket, object)
}
// ListMultipartUploads lists all multipart uploads.
func (l *b2Objects) ListMultipartUploads(ctx context.Context, bucket string, prefix string, keyMarker string, uploadIDMarker string,
delimiter string, maxUploads int) (lmi minio.ListMultipartsInfo, err error) {
// keyMarker, prefix, delimiter are all ignored, Backblaze B2 doesn't support any
// of these parameters only equivalent parameter is uploadIDMarker.
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return lmi, err
}
// The maximum number of files to return from this call.
// The default value is 100, and the maximum allowed is 100.
if maxUploads > 100 {
maxUploads = 100
}
largeFiles, nextMarker, err := bkt.ListUnfinishedLargeFiles(l.ctx, uploadIDMarker, maxUploads)
if err != nil {
logger.LogIf(ctx, err)
return lmi, b2ToObjectError(err, bucket)
}
lmi = minio.ListMultipartsInfo{
MaxUploads: maxUploads,
}
if nextMarker != "" {
lmi.IsTruncated = true
lmi.NextUploadIDMarker = nextMarker
}
for _, largeFile := range largeFiles {
lmi.Uploads = append(lmi.Uploads, minio.MultipartInfo{
Object: largeFile.Name,
UploadID: largeFile.ID,
Initiated: largeFile.Timestamp,
})
}
return lmi, nil
}
// NewMultipartUpload upload object in multiple parts, uses B2's LargeFile upload API.
// Large files can range in size from 5MB to 10TB.
// Each large file must consist of at least 2 parts, and all of the parts except the
// last one must be at least 5MB in size. The last part must contain at least one byte.
// For more information - https://www.backblaze.com/b2/docs/large_files.html
func (l *b2Objects) NewMultipartUpload(ctx context.Context, bucket string, object string, opts minio.ObjectOptions) (string, error) {
var uploadID string
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return uploadID, err
}
contentType := opts.UserDefined["content-type"]
delete(opts.UserDefined, "content-type")
lf, err := bkt.StartLargeFile(l.ctx, object, contentType, opts.UserDefined)
if err != nil {
logger.LogIf(ctx, err)
return uploadID, b2ToObjectError(err, bucket, object)
}
return lf.ID, nil
}
// PutObjectPart puts a part of object in bucket, uses B2's LargeFile upload API.
func (l *b2Objects) PutObjectPart(ctx context.Context, bucket string, object string, uploadID string, partID int, r *minio.PutObjReader, opts minio.ObjectOptions) (pi minio.PartInfo, err error) {
data := r.Reader
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return pi, err
}
fc, err := bkt.File(uploadID, object).CompileParts(0, nil).GetUploadPartURL(l.ctx)
if err != nil {
logger.LogIf(ctx, err)
return pi, b2ToObjectError(err, bucket, object, uploadID)
}
hr := newB2Reader(data, data.Size())
sha1, err := fc.UploadPart(l.ctx, hr, sha1AtEOF, int(hr.Size()), partID)
if err != nil {
logger.LogIf(ctx, err)
return pi, b2ToObjectError(err, bucket, object, uploadID)
}
return minio.PartInfo{
PartNumber: partID,
LastModified: minio.UTCNow(),
ETag: minio.ToS3ETag(sha1),
Size: data.Size(),
}, nil
}
// ListObjectParts returns all object parts for specified object in specified bucket, uses B2's LargeFile upload API.
func (l *b2Objects) ListObjectParts(ctx context.Context, bucket string, object string, uploadID string, partNumberMarker int, maxParts int, opts minio.ObjectOptions) (lpi minio.ListPartsInfo, err error) {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return lpi, err
}
lpi = minio.ListPartsInfo{
Bucket: bucket,
Object: object,
UploadID: uploadID,
MaxParts: maxParts,
PartNumberMarker: partNumberMarker,
}
// startPartNumber must be in the range 1 - 10000 for B2.
partNumberMarker++
partsList, next, err := bkt.File(uploadID, object).ListParts(l.ctx, partNumberMarker, maxParts)
if err != nil {
logger.LogIf(ctx, err)
return lpi, b2ToObjectError(err, bucket, object, uploadID)
}
if next != 0 {
lpi.IsTruncated = true
lpi.NextPartNumberMarker = next
}
for _, part := range partsList {
lpi.Parts = append(lpi.Parts, minio.PartInfo{
PartNumber: part.Number,
ETag: minio.ToS3ETag(part.SHA1),
Size: part.Size,
})
}
return lpi, nil
}
// AbortMultipartUpload aborts a on going multipart upload, uses B2's LargeFile upload API.
func (l *b2Objects) AbortMultipartUpload(ctx context.Context, bucket string, object string, uploadID string) error {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return err
}
err = bkt.File(uploadID, object).CompileParts(0, nil).CancelLargeFile(l.ctx)
logger.LogIf(ctx, err)
return b2ToObjectError(err, bucket, object, uploadID)
}
// CompleteMultipartUpload completes ongoing multipart upload and finalizes object, uses B2's LargeFile upload API.
func (l *b2Objects) CompleteMultipartUpload(ctx context.Context, bucket string, object string, uploadID string, uploadedParts []minio.CompletePart, opts minio.ObjectOptions) (oi minio.ObjectInfo, err error) {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return oi, err
}
hashes := make(map[int]string)
for i, uploadedPart := range uploadedParts {
// B2 requires contigous part numbers starting with 1, they do not support
// hand picking part numbers, we return an S3 compatible error instead.
if i+1 != uploadedPart.PartNumber {
logger.LogIf(ctx, minio.InvalidPart{})
return oi, b2ToObjectError(minio.InvalidPart{}, bucket, object, uploadID)
}
// Trim "-1" suffix in ETag as PutObjectPart() treats B2 returned SHA1 as ETag.
hashes[uploadedPart.PartNumber] = strings.TrimSuffix(uploadedPart.ETag, "-1")
}
if _, err = bkt.File(uploadID, object).CompileParts(0, hashes).FinishLargeFile(l.ctx); err != nil {
logger.LogIf(ctx, err)
return oi, b2ToObjectError(err, bucket, object, uploadID)
}
return l.GetObjectInfo(ctx, bucket, object, minio.ObjectOptions{})
}
// SetBucketPolicy - B2 supports 2 types of bucket policies:
// bucketType.AllPublic - bucketTypeReadOnly means that anybody can download the files is the bucket;
// bucketType.AllPrivate - bucketTypePrivate means that you need an authorization token to download them.
// Default is AllPrivate for all buckets.
func (l *b2Objects) SetBucketPolicy(ctx context.Context, bucket string, bucketPolicy *policy.Policy) error {
policyInfo, err := minio.PolicyToBucketAccessPolicy(bucketPolicy)
if err != nil {
// This should not happen.
return b2ToObjectError(err, bucket)
}
var policies []minio.BucketAccessPolicy
for prefix, policy := range miniogopolicy.GetPolicies(policyInfo.Statements, bucket, "") {
policies = append(policies, minio.BucketAccessPolicy{
Prefix: prefix,
Policy: policy,
})
}
prefix := bucket + "/*" // For all objects inside the bucket.
if len(policies) != 1 {
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
if policies[0].Prefix != prefix {
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
if policies[0].Policy != miniogopolicy.BucketPolicyReadOnly {
logger.LogIf(ctx, minio.NotImplemented{})
return minio.NotImplemented{}
}
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return err
}
bkt.Type = bucketTypeReadOnly
_, err = bkt.Update(l.ctx)
logger.LogIf(ctx, err)
return b2ToObjectError(err)
}
// GetBucketPolicy, returns the current bucketType from B2 backend and convert
// it into S3 compatible bucket policy info.
func (l *b2Objects) GetBucketPolicy(ctx context.Context, bucket string) (*policy.Policy, error) {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return nil, err
}
// bkt.Type can also be snapshot, but it is only allowed through B2 browser console,
// just return back as policy not found for all cases.
// CreateBucket always sets the value to allPrivate by default.
if bkt.Type != bucketTypeReadOnly {
return nil, minio.BucketPolicyNotFound{Bucket: bucket}
}
return &policy.Policy{
Version: policy.DefaultVersion,
Statements: []policy.Statement{
policy.NewStatement(
policy.Allow,
policy.NewPrincipal("*"),
policy.NewActionSet(
policy.GetBucketLocationAction,
policy.ListBucketAction,
policy.GetObjectAction,
),
policy.NewResourceSet(
policy.NewResource(bucket, ""),
policy.NewResource(bucket, "*"),
),
condition.NewFunctions(),
),
},
}, nil
}
// DeleteBucketPolicy - resets the bucketType of bucket on B2 to 'allPrivate'.
func (l *b2Objects) DeleteBucketPolicy(ctx context.Context, bucket string) error {
bkt, err := l.Bucket(ctx, bucket)
if err != nil {
return err
}
bkt.Type = bucketTypePrivate
_, err = bkt.Update(l.ctx)
logger.LogIf(ctx, err)
return b2ToObjectError(err)
}
// IsCompressionSupported returns whether compression is applicable for this layer.
func (l *b2Objects) IsCompressionSupported() bool {
return false
}
+119
View File
@@ -0,0 +1,119 @@
/*
* Minio Cloud Storage, (C) 2017 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 b2
import (
"fmt"
"testing"
b2 "github.com/minio/blazer/base"
minio "github.com/minio/minio/cmd"
)
// Test b2 object error.
func TestB2ObjectError(t *testing.T) {
testCases := []struct {
params []string
b2Err error
expectedErr error
}{
{
[]string{}, nil, nil,
},
{
[]string{}, fmt.Errorf("Not *Error"), fmt.Errorf("Not *Error"),
},
{
[]string{}, fmt.Errorf("Non B2 Error"), fmt.Errorf("Non B2 Error"),
},
{
[]string{"bucket"}, b2.Error{
StatusCode: 1,
Code: "duplicate_bucket_name",
}, minio.BucketAlreadyOwnedByYou{
Bucket: "bucket",
},
},
{
[]string{"bucket"}, b2.Error{
StatusCode: 1,
Code: "bad_request",
}, minio.BucketNotFound{
Bucket: "bucket",
},
},
{
[]string{"bucket", "object"}, b2.Error{
StatusCode: 1,
Code: "bad_request",
}, minio.ObjectNameInvalid{
Bucket: "bucket",
Object: "object",
},
},
{
[]string{"bucket"}, b2.Error{
StatusCode: 1,
Code: "bad_bucket_id",
}, minio.BucketNotFound{Bucket: "bucket"},
},
{
[]string{"bucket", "object"}, b2.Error{
StatusCode: 1,
Code: "file_not_present",
}, minio.ObjectNotFound{
Bucket: "bucket",
Object: "object",
},
},
{
[]string{"bucket", "object"}, b2.Error{
StatusCode: 1,
Code: "not_found",
}, minio.ObjectNotFound{
Bucket: "bucket",
Object: "object",
},
},
{
[]string{"bucket"}, b2.Error{
StatusCode: 1,
Code: "cannot_delete_non_empty_bucket",
}, minio.BucketNotEmpty{
Bucket: "bucket",
},
},
{
[]string{"bucket", "object", "uploadID"}, b2.Error{
StatusCode: 1,
Message: "No active upload for",
}, minio.InvalidUploadID{
UploadID: "uploadID",
},
},
}
for i, testCase := range testCases {
actualErr := b2ToObjectError(testCase.b2Err, testCase.params...)
if actualErr != nil {
if actualErr.Error() != testCase.expectedErr.Error() {
t.Errorf("Test %d: Expected %s, got %s", i+1, testCase.expectedErr, actualErr)
}
}
}
}
+4
View File
@@ -23,5 +23,9 @@ import (
_ "github.com/minio/minio/cmd/gateway/nas" _ "github.com/minio/minio/cmd/gateway/nas"
_ "github.com/minio/minio/cmd/gateway/oss" _ "github.com/minio/minio/cmd/gateway/oss"
_ "github.com/minio/minio/cmd/gateway/s3" _ "github.com/minio/minio/cmd/gateway/s3"
// B2 is specifically kept here to avoid re-ordering by goimports,
// please ask on github.com/minio/minio/issues before changing this.
_ "github.com/minio/minio/cmd/gateway/b2"
// Add your gateway here. // Add your gateway here.
) )
+4 -6
View File
@@ -324,8 +324,6 @@ func gcsToObjectError(err error, params ...string) error {
break break
} }
err = minio.BucketNotEmpty{Bucket: bucket} err = minio.BucketNotEmpty{Bucket: bucket}
default:
err = fmt.Errorf("Unsupported error reason: %s", reason)
} }
return err return err
@@ -877,7 +875,7 @@ func (l *gcsGateway) GetObjectInfo(ctx context.Context, bucket string, object st
} }
// PutObject - Create a new object with the incoming data, // PutObject - Create a new object with the incoming data,
func (l *gcsGateway) PutObject(ctx context.Context, bucket string, key string, r *minio.PutObjReader, metadata map[string]string, opts minio.ObjectOptions) (minio.ObjectInfo, error) { func (l *gcsGateway) PutObject(ctx context.Context, bucket string, key string, r *minio.PutObjReader, opts minio.ObjectOptions) (minio.ObjectInfo, error) {
data := r.Reader data := r.Reader
// if we want to mimic S3 behavior exactly, we need to verify if bucket exists first, // if we want to mimic S3 behavior exactly, we need to verify if bucket exists first,
@@ -895,7 +893,7 @@ func (l *gcsGateway) PutObject(ctx context.Context, bucket string, key string, r
if data.Size() < int64(w.ChunkSize) { if data.Size() < int64(w.ChunkSize) {
w.ChunkSize = 0 w.ChunkSize = 0
} }
applyMetadataToGCSAttrs(metadata, &w.ObjectAttrs) applyMetadataToGCSAttrs(opts.UserDefined, &w.ObjectAttrs)
if _, err := io.Copy(w, data); err != nil { if _, err := io.Copy(w, data); err != nil {
// Close the object writer upon error. // Close the object writer upon error.
@@ -947,7 +945,7 @@ func (l *gcsGateway) DeleteObject(ctx context.Context, bucket string, object str
} }
// NewMultipartUpload - upload object in multiple parts // NewMultipartUpload - upload object in multiple parts
func (l *gcsGateway) NewMultipartUpload(ctx context.Context, bucket string, key string, metadata map[string]string, o minio.ObjectOptions) (uploadID string, err error) { func (l *gcsGateway) NewMultipartUpload(ctx context.Context, bucket string, key string, o minio.ObjectOptions) (uploadID string, err error) {
// generate new uploadid // generate new uploadid
uploadID = minio.MustGetUUID() uploadID = minio.MustGetUUID()
@@ -957,7 +955,7 @@ func (l *gcsGateway) NewMultipartUpload(ctx context.Context, bucket string, key
w := l.client.Bucket(bucket).Object(meta).NewWriter(ctx) w := l.client.Bucket(bucket).Object(meta).NewWriter(ctx)
defer w.Close() defer w.Close()
applyMetadataToGCSAttrs(metadata, &w.ObjectAttrs) applyMetadataToGCSAttrs(o.UserDefined, &w.ObjectAttrs)
if err = json.NewEncoder(w).Encode(gcsMultipartMetaV1{ if err = json.NewEncoder(w).Encode(gcsMultipartMetaV1{
gcsMinioMultipartMetaCurrentVersion, gcsMinioMultipartMetaCurrentVersion,
-9
View File
@@ -375,15 +375,6 @@ func TestGCSToObjectError(t *testing.T) {
Object: "object", Object: "object",
}, },
}, },
{
[]string{"bucket", "object"},
&googleapi.Error{
Errors: []googleapi.ErrorItem{{
Reason: "unknown",
}},
},
fmt.Errorf("Unsupported error reason: unknown"),
},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
+6 -5
View File
@@ -31,6 +31,7 @@ import (
"github.com/minio/cli" "github.com/minio/cli"
miniogopolicy "github.com/minio/minio-go/pkg/policy" miniogopolicy "github.com/minio/minio-go/pkg/policy"
minio "github.com/minio/minio/cmd" minio "github.com/minio/minio/cmd"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/pkg/auth" "github.com/minio/minio/pkg/auth"
"github.com/minio/minio/pkg/hash" "github.com/minio/minio/pkg/hash"
@@ -655,10 +656,10 @@ func ossPutObject(ctx context.Context, client *oss.Client, bucket, object string
} }
// PutObject creates a new object with the incoming data. // PutObject creates a new object with the incoming data.
func (l *ossObjects) PutObject(ctx context.Context, bucket, object string, r *minio.PutObjReader, metadata map[string]string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) { func (l *ossObjects) PutObject(ctx context.Context, bucket, object string, r *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
data := r.Reader data := r.Reader
return ossPutObject(ctx, l.Client, bucket, object, data, metadata) return ossPutObject(ctx, l.Client, bucket, object, data, opts.UserDefined)
} }
// CopyObject copies an object from source bucket to a destination bucket. // CopyObject copies an object from source bucket to a destination bucket.
@@ -752,7 +753,7 @@ func (l *ossObjects) ListMultipartUploads(ctx context.Context, bucket, prefix, k
} }
// NewMultipartUpload upload object in multiple parts. // NewMultipartUpload upload object in multiple parts.
func (l *ossObjects) NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, o minio.ObjectOptions) (uploadID string, err error) { func (l *ossObjects) NewMultipartUpload(ctx context.Context, bucket, object string, o minio.ObjectOptions) (uploadID string, err error) {
bkt, err := l.Client.Bucket(bucket) bkt, err := l.Client.Bucket(bucket)
if err != nil { if err != nil {
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
@@ -760,7 +761,7 @@ func (l *ossObjects) NewMultipartUpload(ctx context.Context, bucket, object stri
} }
// Build OSS metadata // Build OSS metadata
opts, err := appendS3MetaToOSSOptions(ctx, nil, metadata) opts, err := appendS3MetaToOSSOptions(ctx, nil, o.UserDefined)
if err != nil { if err != nil {
return uploadID, ossToObjectError(err, bucket, object) return uploadID, ossToObjectError(err, bucket, object)
} }
@@ -844,7 +845,7 @@ func ossListObjectParts(client *oss.Client, bucket, object, uploadID string, par
} }
// always drain output (response body) // always drain output (response body)
defer minio.CloseResponse(resp.Body) defer xhttp.DrainBody(resp.Body)
err = xml.NewDecoder(resp.Body).Decode(&lupr) err = xml.NewDecoder(resp.Body).Decode(&lupr)
if err != nil { if err != nil {
+21 -21
View File
@@ -236,7 +236,7 @@ func (l *s3EncObjects) writeGWMetadata(ctx context.Context, bucket, metaFileName
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return err return err
} }
_, err = l.s3Objects.PutObject(ctx, bucket, metaFileName, reader, map[string]string{}, o) _, err = l.s3Objects.PutObject(ctx, bucket, metaFileName, reader, o)
return err return err
} }
@@ -373,7 +373,8 @@ func (l *s3EncObjects) CopyObject(ctx context.Context, srcBucket string, srcObje
return gwMeta.ToObjectInfo(dstBucket, dstObject), nil return gwMeta.ToObjectInfo(dstBucket, dstObject), nil
} }
} }
return l.PutObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, srcInfo.UserDefined, d) dstOpts := minio.ObjectOptions{ServerSideEncryption: d.ServerSideEncryption, UserDefined: srcInfo.UserDefined}
return l.PutObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, dstOpts)
} }
// DeleteObject deletes a blob in bucket // DeleteObject deletes a blob in bucket
@@ -406,23 +407,24 @@ func (l *s3EncObjects) ListMultipartUploads(ctx context.Context, bucket string,
} }
// NewMultipartUpload uploads object in multiple parts // NewMultipartUpload uploads object in multiple parts
func (l *s3EncObjects) NewMultipartUpload(ctx context.Context, bucket string, object string, metadata map[string]string, o minio.ObjectOptions) (uploadID string, err error) { func (l *s3EncObjects) NewMultipartUpload(ctx context.Context, bucket string, object string, o minio.ObjectOptions) (uploadID string, err error) {
var opts minio.ObjectOptions var sseOpts encrypt.ServerSide
if o.ServerSideEncryption != nil &&
((minio.GlobalGatewaySSE.SSEC() && o.ServerSideEncryption.Type() == encrypt.SSEC) ||
(minio.GlobalGatewaySSE.SSES3() && o.ServerSideEncryption.Type() == encrypt.S3)) {
opts = o
}
if o.ServerSideEncryption == nil { if o.ServerSideEncryption == nil {
return l.s3Objects.NewMultipartUpload(ctx, bucket, object, metadata, opts) return l.s3Objects.NewMultipartUpload(ctx, bucket, object, minio.ObjectOptions{UserDefined: o.UserDefined})
} }
uploadID, err = l.s3Objects.NewMultipartUpload(ctx, bucket, getGWContentPath(object), map[string]string{}, opts) // Decide if sse options needed to be passed to backend
if (minio.GlobalGatewaySSE.SSEC() && o.ServerSideEncryption.Type() == encrypt.SSEC) ||
(minio.GlobalGatewaySSE.SSES3() && o.ServerSideEncryption.Type() == encrypt.S3) {
sseOpts = o.ServerSideEncryption
}
uploadID, err = l.s3Objects.NewMultipartUpload(ctx, bucket, getGWContentPath(object), minio.ObjectOptions{ServerSideEncryption: sseOpts})
if err != nil { if err != nil {
return return
} }
// Create uploadID and write a temporary dare.meta object under object/uploadID prefix // Create uploadID and write a temporary dare.meta object under object/uploadID prefix
gwmeta := newGWMetaV1() gwmeta := newGWMetaV1()
gwmeta.Meta = metadata gwmeta.Meta = o.UserDefined
gwmeta.Stat.ModTime = time.Now().UTC() gwmeta.Stat.ModTime = time.Now().UTC()
err = l.writeGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID), gwmeta, minio.ObjectOptions{}) err = l.writeGWMetadata(ctx, bucket, getTmpDareMetaPath(object, uploadID), gwmeta, minio.ObjectOptions{})
if err != nil { if err != nil {
@@ -432,30 +434,28 @@ func (l *s3EncObjects) NewMultipartUpload(ctx context.Context, bucket string, ob
} }
// PutObject creates a new object with the incoming data, // PutObject creates a new object with the incoming data,
func (l *s3EncObjects) PutObject(ctx context.Context, bucket string, object string, data *minio.PutObjReader, metadata map[string]string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) { func (l *s3EncObjects) PutObject(ctx context.Context, bucket string, object string, data *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
var s3Opts minio.ObjectOptions var sseOpts encrypt.ServerSide
// Decide if sse options needed to be passed to backend
if opts.ServerSideEncryption != nil && if opts.ServerSideEncryption != nil &&
((minio.GlobalGatewaySSE.SSEC() && opts.ServerSideEncryption.Type() == encrypt.SSEC) || ((minio.GlobalGatewaySSE.SSEC() && opts.ServerSideEncryption.Type() == encrypt.SSEC) ||
(minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3)) { (minio.GlobalGatewaySSE.SSES3() && opts.ServerSideEncryption.Type() == encrypt.S3)) {
s3Opts = opts sseOpts = opts.ServerSideEncryption
} }
if opts.ServerSideEncryption == nil { if opts.ServerSideEncryption == nil {
defer l.deleteGWMetadata(ctx, bucket, getDareMetaPath(object)) defer l.deleteGWMetadata(ctx, bucket, getDareMetaPath(object))
defer l.DeleteObject(ctx, bucket, getGWContentPath(object)) defer l.DeleteObject(ctx, bucket, getGWContentPath(object))
return l.s3Objects.PutObject(ctx, bucket, object, data, metadata, s3Opts) return l.s3Objects.PutObject(ctx, bucket, object, data, minio.ObjectOptions{UserDefined: opts.UserDefined})
} }
oi, err := l.s3Objects.PutObject(ctx, bucket, getGWContentPath(object), data, map[string]string{}, s3Opts) oi, err := l.s3Objects.PutObject(ctx, bucket, getGWContentPath(object), data, minio.ObjectOptions{ServerSideEncryption: sseOpts})
if err != nil { if err != nil {
return objInfo, minio.ErrorRespToObjectError(err) return objInfo, minio.ErrorRespToObjectError(err)
} }
gwMeta := newGWMetaV1() gwMeta := newGWMetaV1()
gwMeta.Meta = make(map[string]string) gwMeta.Meta = make(map[string]string)
for k, v := range oi.UserDefined { for k, v := range opts.UserDefined {
gwMeta.Meta[k] = v
}
for k, v := range metadata {
gwMeta.Meta[k] = v gwMeta.Meta[k] = v
} }
encMD5 := data.MD5CurrentHexString() encMD5 := data.MD5CurrentHexString()
+5 -5
View File
@@ -448,15 +448,15 @@ func (l *s3Objects) GetObjectInfo(ctx context.Context, bucket string, object str
} }
// PutObject creates a new object with the incoming data, // PutObject creates a new object with the incoming data,
func (l *s3Objects) PutObject(ctx context.Context, bucket string, object string, r *minio.PutObjReader, metadata map[string]string, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) { func (l *s3Objects) PutObject(ctx context.Context, bucket string, object string, r *minio.PutObjReader, opts minio.ObjectOptions) (objInfo minio.ObjectInfo, err error) {
data := r.Reader data := r.Reader
oi, err := l.Client.PutObject(bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), minio.ToMinioClientMetadata(metadata), opts.ServerSideEncryption) oi, err := l.Client.PutObject(bucket, object, data, data.Size(), data.MD5Base64String(), data.SHA256HexString(), minio.ToMinioClientMetadata(opts.UserDefined), opts.ServerSideEncryption)
if err != nil { if err != nil {
return objInfo, minio.ErrorRespToObjectError(err, bucket, object) return objInfo, minio.ErrorRespToObjectError(err, bucket, object)
} }
// On success, populate the key & metadata so they are present in the notification // On success, populate the key & metadata so they are present in the notification
oi.Key = object oi.Key = object
oi.Metadata = minio.ToMinioClientObjectInfoMetadata(metadata) oi.Metadata = minio.ToMinioClientObjectInfoMetadata(opts.UserDefined)
return minio.FromMinioClientObjectInfo(bucket, oi), nil return minio.FromMinioClientObjectInfo(bucket, oi), nil
} }
@@ -508,9 +508,9 @@ func (l *s3Objects) ListMultipartUploads(ctx context.Context, bucket string, pre
} }
// NewMultipartUpload upload object in multiple parts // NewMultipartUpload upload object in multiple parts
func (l *s3Objects) NewMultipartUpload(ctx context.Context, bucket string, object string, metadata map[string]string, o minio.ObjectOptions) (uploadID string, err error) { func (l *s3Objects) NewMultipartUpload(ctx context.Context, bucket string, object string, o minio.ObjectOptions) (uploadID string, err error) {
// Create PutObject options // Create PutObject options
opts := miniogo.PutObjectOptions{UserMetadata: metadata, ServerSideEncryption: o.ServerSideEncryption} opts := miniogo.PutObjectOptions{UserMetadata: o.UserDefined, ServerSideEncryption: o.ServerSideEncryption}
uploadID, err = l.Client.NewMultipartUpload(bucket, object, opts) uploadID, err = l.Client.NewMultipartUpload(bucket, object, opts)
if err != nil { if err != nil {
return uploadID, minio.ErrorRespToObjectError(err, bucket, object) return uploadID, minio.ErrorRespToObjectError(err, bucket, object)
+50 -62
View File
@@ -90,7 +90,7 @@ func setRequestHeaderSizeLimitHandler(h http.Handler) http.Handler {
// of the user-defined metadata to 2 KB. // of the user-defined metadata to 2 KB.
func (h requestHeaderSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h requestHeaderSizeLimitHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if isHTTPHeaderSizeTooLarge(r.Header) { if isHTTPHeaderSizeTooLarge(r.Header) {
writeErrorResponse(w, ErrMetadataTooLarge, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMetadataTooLarge), r.URL, guessIsBrowserReq(r))
return return
} }
h.Handler.ServeHTTP(w, r) h.Handler.ServeHTTP(w, r)
@@ -133,7 +133,7 @@ func filterReservedMetadata(h http.Handler) http.Handler {
// would be treated as metadata. // would be treated as metadata.
func (h reservedMetadataHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h reservedMetadataHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if containsReservedMetadata(r.Header) { if containsReservedMetadata(r.Header) {
writeErrorResponse(w, ErrUnsupportedMetadata, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrUnsupportedMetadata), r.URL, guessIsBrowserReq(r))
return return
} }
h.Handler.ServeHTTP(w, r) h.Handler.ServeHTTP(w, r)
@@ -300,7 +300,7 @@ func (h minioReservedBucketHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
// buckets // buckets
bucketName, _ := urlPath2BucketObjectName(r.URL.Path) bucketName, _ := urlPath2BucketObjectName(r.URL.Path)
if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) { if isMinioReservedBucket(bucketName) || isMinioMetaBucket(bucketName) {
writeErrorResponse(w, ErrAllAccessDisabled, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrAllAccessDisabled), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -358,19 +358,19 @@ func (h timeValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
aType := getRequestAuthType(r) aType := getRequestAuthType(r)
if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned { if aType == authTypeSigned || aType == authTypeSignedV2 || aType == authTypeStreamingSigned {
// Verify if date headers are set, if not reject the request // Verify if date headers are set, if not reject the request
amzDate, apiErr := parseAmzDateHeader(r) amzDate, errCode := parseAmzDateHeader(r)
if apiErr != ErrNone { if errCode != ErrNone {
// All our internal APIs are sensitive towards Date // All our internal APIs are sensitive towards Date
// header, for all requests where Date header is not // header, for all requests where Date header is not
// present we will reject such clients. // present we will reject such clients.
writeErrorResponse(w, apiErr, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(errCode), r.URL, guessIsBrowserReq(r))
return return
} }
// Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past // Verify if the request date header is shifted by less than globalMaxSkewTime parameter in the past
// or in the future, reject request otherwise. // or in the future, reject request otherwise.
curTime := UTCNow() curTime := UTCNow()
if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime { if curTime.Sub(amzDate) > globalMaxSkewTime || amzDate.Sub(curTime) > globalMaxSkewTime {
writeErrorResponse(w, ErrRequestTimeTooSkewed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrRequestTimeTooSkewed), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -381,37 +381,9 @@ type resourceHandler struct {
handler http.Handler handler http.Handler
} }
// List of default allowable HTTP methods.
var defaultAllowableHTTPMethods = []string{
http.MethodGet,
http.MethodPut,
http.MethodHead,
http.MethodPost,
http.MethodDelete,
http.MethodOptions,
}
// setCorsHandler handler for CORS (Cross Origin Resource Sharing) // setCorsHandler handler for CORS (Cross Origin Resource Sharing)
func setCorsHandler(h http.Handler) http.Handler { func setCorsHandler(h http.Handler) http.Handler {
commonS3Headers := []string{ c := cors.AllowAll()
"Date",
"ETag",
"Server",
"Connection",
"Accept-Ranges",
"Content-Range",
"Content-Encoding",
"Content-Length",
"Content-Type",
"x-amz-request-id",
}
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: defaultAllowableHTTPMethods,
AllowedHeaders: []string{"*"},
ExposedHeaders: commonS3Headers,
AllowCredentials: true,
})
return c.Handler(h) return c.Handler(h)
} }
@@ -426,8 +398,24 @@ func setIgnoreResourcesHandler(h http.Handler) http.Handler {
// Checks requests for not implemented Bucket resources // Checks requests for not implemented Bucket resources
func ignoreNotImplementedBucketResources(req *http.Request) bool { func ignoreNotImplementedBucketResources(req *http.Request) bool {
for name := range req.URL.Query() { for name := range req.URL.Query() {
// Enable GetBucketACL dummy call specifically. // Enable GetBucketACL, GetBucketCors, GetBucketWebsite,
if name == "acl" && req.Method == http.MethodGet { // GetBucketAcccelerate, GetBucketRequestPayment,
// GetBucketLogging, GetBucketLifecycle,
// GetBucketReplication, GetBucketTagging,
// GetBucketVersioning, DeleteBucketTagging,
// and DeleteBucketWebsite dummy calls specifically.
if ((name == "acl" ||
name == "cors" ||
name == "website" ||
name == "accelerate" ||
name == "requestPayment" ||
name == "logging" ||
name == "lifecycle" ||
name == "replication" ||
name == "tagging" ||
name == "versioning") && req.Method == http.MethodGet) ||
((name == "tagging" ||
name == "website") && req.Method == http.MethodDelete) {
return false return false
} }
@@ -441,8 +429,8 @@ func ignoreNotImplementedBucketResources(req *http.Request) bool {
// Checks requests for not implemented Object resources // Checks requests for not implemented Object resources
func ignoreNotImplementedObjectResources(req *http.Request) bool { func ignoreNotImplementedObjectResources(req *http.Request) bool {
for name := range req.URL.Query() { for name := range req.URL.Query() {
// Enable GetObjectACL dummy call specifically. // Enable GetObjectACL and GetObjectTagging dummy calls specifically.
if name == "acl" && req.Method == http.MethodGet { if (name == "acl" || name == "tagging") && req.Method == http.MethodGet {
return false return false
} }
if notimplementedObjectResourceNames[name] { if notimplementedObjectResourceNames[name] {
@@ -454,28 +442,28 @@ func ignoreNotImplementedObjectResources(req *http.Request) bool {
// List of not implemented bucket queries // List of not implemented bucket queries
var notimplementedBucketResourceNames = map[string]bool{ var notimplementedBucketResourceNames = map[string]bool{
"accelerate": true,
"acl": true, "acl": true,
"cors": true, "cors": true,
"inventory": true,
"lifecycle": true, "lifecycle": true,
"logging": true, "logging": true,
"replication": true,
"tagging": true,
"versions": true,
"requestPayment": true,
"versioning": true,
"website": true,
"inventory": true,
"metrics": true, "metrics": true,
"accelerate": true, "replication": true,
"requestPayment": true,
"tagging": true,
"versioning": true,
"versions": true,
"website": true,
} }
// List of not implemented object queries // List of not implemented object queries
var notimplementedObjectResourceNames = map[string]bool{ var notimplementedObjectResourceNames = map[string]bool{
"torrent": true,
"acl": true, "acl": true,
"policy": true, "policy": true,
"tagging": true,
"restore": true, "restore": true,
"tagging": true,
"torrent": true,
} }
// Resource handler ServeHTTP() wrapper // Resource handler ServeHTTP() wrapper
@@ -485,14 +473,14 @@ func (h resourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// If bucketName is present and not objectName check for bucket level resource queries. // If bucketName is present and not objectName check for bucket level resource queries.
if bucketName != "" && objectName == "" { if bucketName != "" && objectName == "" {
if ignoreNotImplementedBucketResources(r) { if ignoreNotImplementedBucketResources(r) {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
} }
// If bucketName and objectName are present check for its resource queries. // If bucketName and objectName are present check for its resource queries.
if bucketName != "" && objectName != "" { if bucketName != "" && objectName != "" {
if ignoreNotImplementedObjectResources(r) { if ignoreNotImplementedObjectResources(r) {
writeErrorResponse(w, ErrNotImplemented, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -596,14 +584,14 @@ func hasBadPathComponent(path string) bool {
func (h pathValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h pathValidityHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check for bad components in URL path. // Check for bad components in URL path.
if hasBadPathComponent(r.URL.Path) { if hasBadPathComponent(r.URL.Path) {
writeErrorResponse(w, ErrInvalidResourceName, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidResourceName), r.URL, guessIsBrowserReq(r))
return return
} }
// Check for bad components in URL query values. // Check for bad components in URL query values.
for _, vv := range r.URL.Query() { for _, vv := range r.URL.Query() {
for _, v := range vv { for _, v := range vv {
if hasBadPathComponent(v) { if hasBadPathComponent(v) {
writeErrorResponse(w, ErrInvalidResourceName, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInvalidResourceName), r.URL, guessIsBrowserReq(r))
return return
} }
} }
@@ -647,9 +635,9 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
sr, err := globalDNSConfig.Get(bucket) sr, err := globalDNSConfig.Get(bucket)
if err != nil { if err != nil {
if err == dns.ErrNoEntriesFound { if err == dns.ErrNoEntriesFound {
writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNoSuchBucket), r.URL, guessIsBrowserReq(r))
} else { } else {
writeErrorResponse(w, toAPIErrorCode(context.Background(), err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(context.Background(), err), r.URL, guessIsBrowserReq(r))
} }
return return
} }
@@ -691,9 +679,9 @@ func (f bucketForwardingHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
sr, err := globalDNSConfig.Get(bucket) sr, err := globalDNSConfig.Get(bucket)
if err != nil { if err != nil {
if err == dns.ErrNoEntriesFound { if err == dns.ErrNoEntriesFound {
writeErrorResponse(w, ErrNoSuchBucket, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrNoSuchBucket), r.URL, guessIsBrowserReq(r))
} else { } else {
writeErrorResponse(w, toAPIErrorCode(context.Background(), err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(context.Background(), err), r.URL, guessIsBrowserReq(r))
} }
return return
} }
@@ -753,7 +741,7 @@ func (l rateLimit) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// potential pileup on the server. // potential pileup on the server.
if err := l.Wait(ctx); err != nil { if err := l.Wait(ctx); err != nil {
// Send an S3 compatible error, SlowDown. // Send an S3 compatible error, SlowDown.
writeErrorResponse(w, ErrSlowDown, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrSlowDown), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -807,7 +795,7 @@ type criticalErrorHandler struct{ handler http.Handler }
func (h criticalErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h criticalErrorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
if err := recover(); err == logger.ErrCritical { // handle if err := recover(); err == logger.ErrCritical { // handle
writeErrorResponse(w, ErrInternalError, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInternalError), r.URL, guessIsBrowserReq(r))
} else if err != nil { } else if err != nil {
panic(err) // forward other panic calls panic(err) // forward other panic calls
} }
@@ -824,9 +812,9 @@ func (h sseTLSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Deny SSE-C requests if not made over TLS // Deny SSE-C requests if not made over TLS
if !globalIsSSL && (crypto.SSEC.IsRequested(r.Header) || crypto.SSECopy.IsRequested(r.Header)) { if !globalIsSSL && (crypto.SSEC.IsRequested(r.Header) || crypto.SSECopy.IsRequested(r.Header)) {
if r.Method == http.MethodHead { if r.Method == http.MethodHead {
writeErrorResponseHeadersOnly(w, ErrInsecureSSECustomerRequest) writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInsecureSSECustomerRequest))
} else { } else {
writeErrorResponse(w, ErrInsecureSSECustomerRequest, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrInsecureSSECustomerRequest), r.URL, guessIsBrowserReq(r))
} }
return return
} }
+3 -3
View File
@@ -40,7 +40,7 @@ func parseLocationConstraint(r *http.Request) (location string, s3Error APIError
// be created at default region. // be created at default region.
locationConstraint := createBucketLocationConfiguration{} locationConstraint := createBucketLocationConfiguration{}
err := xmlDecoder(r.Body, &locationConstraint, r.ContentLength) err := xmlDecoder(r.Body, &locationConstraint, r.ContentLength)
if err != nil && err != io.EOF { if err != nil && r.ContentLength != 0 {
logger.LogIf(context.Background(), err) logger.LogIf(context.Background(), err)
// Treat all other failures as XML parsing errors. // Treat all other failures as XML parsing errors.
return "", ErrMalformedXML return "", ErrMalformedXML
@@ -366,12 +366,12 @@ func getResource(path string, host string, domain string) (string, error) {
// If none of the http routes match respond with MethodNotAllowed, in JSON // If none of the http routes match respond with MethodNotAllowed, in JSON
func notFoundHandlerJSON(w http.ResponseWriter, r *http.Request) { func notFoundHandlerJSON(w http.ResponseWriter, r *http.Request) {
writeErrorResponseJSON(w, ErrMethodNotAllowed, r.URL) writeErrorResponseJSON(w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
return return
} }
// If none of the http routes match respond with MethodNotAllowed // If none of the http routes match respond with MethodNotAllowed
func notFoundHandler(w http.ResponseWriter, r *http.Request) { func notFoundHandler(w http.ResponseWriter, r *http.Request) {
writeErrorResponse(w, ErrMethodNotAllowed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL, guessIsBrowserReq(r))
return return
} }
+20 -18
View File
@@ -39,48 +39,50 @@ func TestIsValidLocationContraint(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Test with corrupted XML // Corrupted XML
malformedReq := &http.Request{ malformedReq := &http.Request{
Body: ioutil.NopCloser(bytes.NewBuffer([]byte("<>"))), Body: ioutil.NopCloser(bytes.NewBuffer([]byte("<>"))),
ContentLength: int64(len("<>")), ContentLength: int64(len("<>")),
} }
if _, err := parseLocationConstraint(malformedReq); err != ErrMalformedXML {
t.Fatal("Unexpected error: ", err) // Not an XML
badRequest := &http.Request{
Body: ioutil.NopCloser(bytes.NewReader([]byte("garbage"))),
ContentLength: int64(len("garbage")),
} }
// generates the input request with XML bucket configuration set to the request body. // generates the input request with XML bucket configuration set to the request body.
createExpectedRequest := func(req *http.Request, location string) (*http.Request, error) { createExpectedRequest := func(req *http.Request, location string) *http.Request {
createBucketConfig := createBucketLocationConfiguration{} createBucketConfig := createBucketLocationConfiguration{}
createBucketConfig.Location = location createBucketConfig.Location = location
var createBucketConfigBytes []byte createBucketConfigBytes, _ := xml.Marshal(createBucketConfig)
createBucketConfigBytes, e := xml.Marshal(createBucketConfig)
if e != nil {
return nil, e
}
createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes) createBucketConfigBuffer := bytes.NewBuffer(createBucketConfigBytes)
req.Body = ioutil.NopCloser(createBucketConfigBuffer) req.Body = ioutil.NopCloser(createBucketConfigBuffer)
req.ContentLength = int64(createBucketConfigBuffer.Len()) req.ContentLength = int64(createBucketConfigBuffer.Len())
return req, nil return req
} }
testCases := []struct { testCases := []struct {
locationForInputRequest string request *http.Request
serverConfigRegion string serverConfigRegion string
expectedCode APIErrorCode expectedCode APIErrorCode
}{ }{
// Test case - 1. // Test case - 1.
{globalMinioDefaultRegion, globalMinioDefaultRegion, ErrNone}, {createExpectedRequest(&http.Request{}, "eu-central-1"), globalMinioDefaultRegion, ErrNone},
// Test case - 2. // Test case - 2.
// In case of empty request body ErrNone is returned. // In case of empty request body ErrNone is returned.
{"", globalMinioDefaultRegion, ErrNone}, {createExpectedRequest(&http.Request{}, ""), globalMinioDefaultRegion, ErrNone},
// Test case - 3
// In case of garbage request body ErrMalformedXML is returned.
{badRequest, globalMinioDefaultRegion, ErrMalformedXML},
// Test case - 4
// In case of invalid XML request body ErrMalformedXML is returned.
{malformedReq, globalMinioDefaultRegion, ErrMalformedXML},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
inputRequest, e := createExpectedRequest(&http.Request{}, testCase.locationForInputRequest)
if e != nil {
t.Fatalf("Test %d: Failed to Marshal bucket configuration", i+1)
}
globalServerConfig.SetRegion(testCase.serverConfigRegion) globalServerConfig.SetRegion(testCase.serverConfigRegion)
_, actualCode := parseLocationConstraint(inputRequest) _, actualCode := parseLocationConstraint(testCase.request)
if testCase.expectedCode != actualCode { if testCase.expectedCode != actualCode {
t.Errorf("Test %d: Expected the APIErrCode to be %d, but instead found %d", i+1, testCase.expectedCode, actualCode) t.Errorf("Test %d: Expected the APIErrCode to be %d, but instead found %d", i+1, testCase.expectedCode, actualCode)
} }
+50
View File
@@ -0,0 +1,50 @@
/*
* Minio Cloud Storage, (C) 2019 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 http
import (
"io"
"io/ioutil"
"sync"
)
var b512pool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 512)
return &buf
},
}
// DrainBody close non nil response with any response Body.
// convenient wrapper to drain any remaining data on response body.
//
// Subsequently this allows golang http RoundTripper
// to re-use the same connection for future requests.
func DrainBody(respBody io.ReadCloser) {
// Callers should close resp.Body when done reading from it.
// If resp.Body is not closed, the Client's underlying RoundTripper
// (typically Transport) may not be able to re-use a persistent TCP
// connection to the server for a subsequent "keep-alive" request.
if respBody != nil {
// Drain any remaining Body and then close the connection.
// Without this closing connection would disallow re-using
// the same connection for future uses.
// - http://stackoverflow.com/a/17961593/4465767
defer respBody.Close()
io.Copy(ioutil.Discard, respBody)
}
}
+6 -8
View File
@@ -1,5 +1,5 @@
/* /*
* Minio Cloud Storage, (C) 2018 Minio, Inc. * Minio Cloud Storage, (C) 2018, 2019 Minio, Inc.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -20,9 +20,9 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
"io"
"io/ioutil"
gohttp "net/http" gohttp "net/http"
xhttp "github.com/minio/minio/cmd/http"
) )
// Target implements logger.Target and sends the json // Target implements logger.Target and sends the json
@@ -56,11 +56,9 @@ func (h *Target) startHTTPLogger() {
if err != nil { if err != nil {
continue continue
} }
if resp.Body != nil {
buf := make([]byte, 512) // Drain any response.
io.CopyBuffer(ioutil.Discard, resp.Body, buf) xhttp.DrainBody(resp.Body)
resp.Body.Close()
}
} }
}() }()
} }
+1 -1
View File
@@ -93,7 +93,7 @@ func testDeleteObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
for _, object := range testCase.objectToUploads { for _, object := range testCase.objectToUploads {
md5Bytes := md5.Sum([]byte(object.content)) md5Bytes := md5.Sum([]byte(object.content))
_, err = obj.PutObject(context.Background(), testCase.bucketName, object.name, mustGetPutObjReader(t, bytes.NewBufferString(object.content), _, err = obj.PutObject(context.Background(), testCase.bucketName, object.name, mustGetPutObjReader(t, bytes.NewBufferString(object.content),
int64(len(object.content)), hex.EncodeToString(md5Bytes[:]), ""), nil, ObjectOptions{}) int64(len(object.content)), hex.EncodeToString(md5Bytes[:]), ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
+3 -3
View File
@@ -75,7 +75,7 @@ func testGetObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
// iterate through the above set of inputs and upkoad the object. // iterate through the above set of inputs and upkoad the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), input.metaData, ObjectOptions{}) _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -220,7 +220,7 @@ func testGetObjectPermissionDenied(obj ObjectLayer, instanceType string, disks [
// iterate through the above set of inputs and upkoad the object. // iterate through the above set of inputs and upkoad the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), input.metaData, ObjectOptions{}) _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -333,7 +333,7 @@ func testGetObjectDiskNotFound(obj ObjectLayer, instanceType string, disks []str
// iterate through the above set of inputs and upkoad the object. // iterate through the above set of inputs and upkoad the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), input.metaData, ObjectOptions{}) _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
+2 -2
View File
@@ -35,13 +35,13 @@ func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
opts := ObjectOptions{} opts := ObjectOptions{}
_, err = obj.PutObject(context.Background(), "test-getobjectinfo", "Asia/asiapics.jpg", mustGetPutObjReader(t, bytes.NewBufferString("asiapics"), int64(len("asiapics")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "test-getobjectinfo", "Asia/asiapics.jpg", mustGetPutObjReader(t, bytes.NewBufferString("asiapics"), int64(len("asiapics")), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
// Put an empty directory // Put an empty directory
_, err = obj.PutObject(context.Background(), "test-getobjectinfo", "Asia/empty-dir/", mustGetPutObjReader(t, bytes.NewBufferString(""), int64(len("")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "test-getobjectinfo", "Asia/empty-dir/", mustGetPutObjReader(t, bytes.NewBufferString(""), int64(len("")), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
+5 -4
View File
@@ -29,6 +29,7 @@ import (
// ObjectOptions represents object options for ObjectLayer operations // ObjectOptions represents object options for ObjectLayer operations
type ObjectOptions struct { type ObjectOptions struct {
ServerSideEncryption encrypt.ServerSide ServerSideEncryption encrypt.ServerSide
UserDefined map[string]string
} }
// LockType represents required locking for ObjectLayer operations // LockType represents required locking for ObjectLayer operations
@@ -65,13 +66,13 @@ type ObjectLayer interface {
GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (reader *GetObjectReader, err error) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, lockType LockType, opts ObjectOptions) (reader *GetObjectReader, err error)
GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error) GetObject(ctx context.Context, bucket, object string, startOffset int64, length int64, writer io.Writer, etag string, opts ObjectOptions) (err error)
GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (objInfo ObjectInfo, err error)
PutObject(ctx context.Context, bucket, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) PutObject(ctx context.Context, bucket, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error)
CopyObject(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error) CopyObject(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (objInfo ObjectInfo, err error)
DeleteObject(ctx context.Context, bucket, object string) error DeleteObject(ctx context.Context, bucket, object string) error
// Multipart operations. // Multipart operations.
ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (result ListMultipartsInfo, err error)
NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error)
CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int, CopyObjectPart(ctx context.Context, srcBucket, srcObject, destBucket, destObject string, uploadID string, partID int,
startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (info PartInfo, err error) startOffset int64, length int64, srcInfo ObjectInfo, srcOpts, dstOpts ObjectOptions) (info PartInfo, err error)
PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, data *PutObjReader, opts ObjectOptions) (info PartInfo, err error)
@@ -82,8 +83,8 @@ type ObjectLayer interface {
// Healing operations. // Healing operations.
ReloadFormat(ctx context.Context, dryRun bool) error ReloadFormat(ctx context.Context, dryRun bool) error
HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error) HealFormat(ctx context.Context, dryRun bool) (madmin.HealResultItem, error)
HealBucket(ctx context.Context, bucket string, dryRun bool) ([]madmin.HealResultItem, error) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (madmin.HealResultItem, error)
HealObject(ctx context.Context, bucket, object string, dryRun bool) (madmin.HealResultItem, error) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool) (madmin.HealResultItem, error)
ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error) ListBucketsHeal(ctx context.Context) (buckets []BucketInfo, err error)
ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) ListObjectsHeal(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error)
+2 -2
View File
@@ -71,7 +71,7 @@ func testListObjects(obj ObjectLayer, instanceType string, t TestErrHandler) {
for _, object := range testObjects { for _, object := range testObjects {
md5Bytes := md5.Sum([]byte(object.content)) md5Bytes := md5.Sum([]byte(object.content))
_, err = obj.PutObject(context.Background(), testBuckets[0], object.name, mustGetPutObjReader(t, bytes.NewBufferString(object.content), _, err = obj.PutObject(context.Background(), testBuckets[0], object.name, mustGetPutObjReader(t, bytes.NewBufferString(object.content),
int64(len(object.content)), hex.EncodeToString(md5Bytes[:]), ""), object.meta, ObjectOptions{}) int64(len(object.content)), hex.EncodeToString(md5Bytes[:]), ""), ObjectOptions{UserDefined: object.meta})
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
@@ -625,7 +625,7 @@ func BenchmarkListObjects(b *testing.B) {
// Insert objects to be listed and benchmarked later. // Insert objects to be listed and benchmarked later.
for i := 0; i < 20000; i++ { for i := 0; i < 20000; i++ {
key := "obj" + strconv.Itoa(i) key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject(context.Background(), bucket, key, mustGetPutObjReader(b, bytes.NewBufferString(key), int64(len(key)), "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucket, key, mustGetPutObjReader(b, bytes.NewBufferString(key), int64(len(key)), "", ""), ObjectOptions{})
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }
+15 -15
View File
@@ -41,14 +41,14 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestEr
bucket := "minio-bucket" bucket := "minio-bucket"
object := "minio-object" object := "minio-object"
opts := ObjectOptions{} opts := ObjectOptions{}
_, err := obj.NewMultipartUpload(context.Background(), "--", object, nil, opts) _, err := obj.NewMultipartUpload(context.Background(), "--", object, opts)
if err == nil { if err == nil {
t.Fatalf("%s: Expected to fail since bucket name is invalid.", instanceType) t.Fatalf("%s: Expected to fail since bucket name is invalid.", instanceType)
} }
errMsg := "Bucket not found: minio-bucket" errMsg := "Bucket not found: minio-bucket"
// opearation expected to fail since the bucket on which NewMultipartUpload is being initiated doesn't exist. // opearation expected to fail since the bucket on which NewMultipartUpload is being initiated doesn't exist.
_, err = obj.NewMultipartUpload(context.Background(), bucket, object, nil, opts) _, err = obj.NewMultipartUpload(context.Background(), bucket, object, opts)
if err == nil { if err == nil {
t.Fatalf("%s: Expected to fail since the NewMultipartUpload is intialized on a non-existent bucket.", instanceType) t.Fatalf("%s: Expected to fail since the NewMultipartUpload is intialized on a non-existent bucket.", instanceType)
} }
@@ -63,12 +63,12 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestEr
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
_, err = obj.NewMultipartUpload(context.Background(), bucket, "\\", nil, opts) _, err = obj.NewMultipartUpload(context.Background(), bucket, "\\", opts)
if err == nil { if err == nil {
t.Fatalf("%s: Expected to fail since object name is invalid.", instanceType) t.Fatalf("%s: Expected to fail since object name is invalid.", instanceType)
} }
uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts)
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
@@ -102,7 +102,7 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts)
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
@@ -148,7 +148,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestE
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
_, err = obj.NewMultipartUpload(context.Background(), bucket, object, nil, ObjectOptions{}) _, err = obj.NewMultipartUpload(context.Background(), bucket, object, ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
@@ -184,7 +184,7 @@ func testPutObjectPartDiskNotFound(obj ObjectLayer, instanceType string, disks [
} }
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], nil, ObjectOptions{}) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], ObjectOptions{})
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -264,7 +264,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -400,7 +400,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -420,7 +420,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
// Initiate Multipart Upload on bucketNames[1] for the same object 3 times. // Initiate Multipart Upload on bucketNames[1] for the same object 3 times.
// Used to test the listing for the case of multiple uploadID's for a given object. // Used to test the listing for the case of multiple uploadID's for a given object.
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketNames[1], objectNames[0], nil, opts) uploadID, err = obj.NewMultipartUpload(context.Background(), bucketNames[1], objectNames[0], opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -442,7 +442,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
// Used to test the listing for the case of multiple objects for a given bucket. // Used to test the listing for the case of multiple objects for a given bucket.
for i := 0; i < 6; i++ { for i := 0; i < 6; i++ {
var uploadID string var uploadID string
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketNames[2], objectNames[i], nil, opts) uploadID, err = obj.NewMultipartUpload(context.Background(), bucketNames[2], objectNames[i], opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -1275,7 +1275,7 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks
} }
opts := ObjectOptions{} opts := ObjectOptions{}
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -1519,7 +1519,7 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler)
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -1764,9 +1764,8 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T
// Failed to create newbucket, abort. // Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err) t.Fatalf("%s : %s", instanceType, err)
} }
opts := ObjectOptions{}
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], map[string]string{"X-Amz-Meta-Id": "id"}, opts) uploadID, err = obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], ObjectOptions{UserDefined: map[string]string{"X-Amz-Meta-Id": "id"}})
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err) t.Fatalf("%s : %s", instanceType, err)
@@ -1799,6 +1798,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T
{bucketNames[0], objectNames[0], uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(string(validPart)))}, {bucketNames[0], objectNames[0], uploadIDs[0], 6, string(validPart), validPartMD5, int64(len(string(validPart)))},
} }
sha256sum := "" sha256sum := ""
var opts ObjectOptions
// Iterating over creatPartCases to generate multipart chunks. // Iterating over creatPartCases to generate multipart chunks.
for _, part := range parts { for _, part := range parts {
_, err = obj.PutObjectPart(context.Background(), part.bucketName, part.objName, part.uploadID, part.PartID, mustGetPutObjReader(t, bytes.NewBufferString(part.inputReaderData), part.intputDataSize, part.inputMd5, sha256sum), opts) _, err = obj.PutObjectPart(context.Background(), part.bucketName, part.objName, part.uploadID, part.PartID, mustGetPutObjReader(t, bytes.NewBufferString(part.inputReaderData), part.intputDataSize, part.inputMd5, sha256sum), opts)
+5 -5
View File
@@ -172,7 +172,7 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl
} }
for i, testCase := range testCases { for i, testCase := range testCases {
objInfo, actualErr := obj.PutObject(context.Background(), testCase.bucketName, testCase.objName, mustGetPutObjReader(t, bytes.NewReader(testCase.inputData), testCase.intputDataSize, testCase.inputMeta["etag"], testCase.inputSHA256), testCase.inputMeta, ObjectOptions{}) objInfo, actualErr := obj.PutObject(context.Background(), testCase.bucketName, testCase.objName, mustGetPutObjReader(t, bytes.NewReader(testCase.inputData), testCase.intputDataSize, testCase.inputMeta["etag"], testCase.inputSHA256), ObjectOptions{UserDefined: testCase.inputMeta})
if actualErr != nil && testCase.expectedError == nil { if actualErr != nil && testCase.expectedError == nil {
t.Errorf("Test %d: %s: Expected to pass, but failed with: error %s.", i+1, instanceType, actualErr.Error()) t.Errorf("Test %d: %s: Expected to pass, but failed with: error %s.", i+1, instanceType, actualErr.Error())
} }
@@ -245,7 +245,7 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di
sha256sum := "" sha256sum := ""
for i, testCase := range testCases { for i, testCase := range testCases {
objInfo, actualErr := obj.PutObject(context.Background(), testCase.bucketName, testCase.objName, mustGetPutObjReader(t, bytes.NewReader(testCase.inputData), testCase.intputDataSize, testCase.inputMeta["etag"], sha256sum), testCase.inputMeta, ObjectOptions{}) objInfo, actualErr := obj.PutObject(context.Background(), testCase.bucketName, testCase.objName, mustGetPutObjReader(t, bytes.NewReader(testCase.inputData), testCase.intputDataSize, testCase.inputMeta["etag"], sha256sum), ObjectOptions{UserDefined: testCase.inputMeta})
if actualErr != nil && testCase.shouldPass { if actualErr != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s.", i+1, instanceType, actualErr.Error()) t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s.", i+1, instanceType, actualErr.Error())
} }
@@ -294,7 +294,7 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di
InsufficientWriteQuorum{}, InsufficientWriteQuorum{},
} }
_, actualErr := obj.PutObject(context.Background(), testCase.bucketName, testCase.objName, mustGetPutObjReader(t, bytes.NewReader(testCase.inputData), testCase.intputDataSize, testCase.inputMeta["etag"], sha256sum), testCase.inputMeta, ObjectOptions{}) _, actualErr := obj.PutObject(context.Background(), testCase.bucketName, testCase.objName, mustGetPutObjReader(t, bytes.NewReader(testCase.inputData), testCase.intputDataSize, testCase.inputMeta["etag"], sha256sum), ObjectOptions{UserDefined: testCase.inputMeta})
if actualErr != nil && testCase.shouldPass { if actualErr != nil && testCase.shouldPass {
t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s.", len(testCases)+1, instanceType, actualErr.Error()) t.Errorf("Test %d: %s: Expected to pass, but failed with: <ERROR> %s.", len(testCases)+1, instanceType, actualErr.Error())
} }
@@ -326,7 +326,7 @@ func testObjectAPIPutObjectStaleFiles(obj ObjectLayer, instanceType string, disk
data := []byte("hello, world") data := []byte("hello, world")
// Create object. // Create object.
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{})
if err != nil { if err != nil {
// Failed to create object, abort. // Failed to create object, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
@@ -359,7 +359,7 @@ func testObjectAPIMultipartPutObjectStaleFiles(obj ObjectLayer, instanceType str
} }
opts := ObjectOptions{} opts := ObjectOptions{}
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
+6 -6
View File
@@ -75,7 +75,7 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf
if !ifModifiedSince(objInfo.ModTime, givenTime) { if !ifModifiedSince(objInfo.ModTime, givenTime) {
// If the object is not modified since the specified time. // If the object is not modified since the specified time.
writeHeaders() writeHeaders()
writeErrorResponse(w, ErrPreconditionFailed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
return true return true
} }
} }
@@ -89,7 +89,7 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf
if ifModifiedSince(objInfo.ModTime, givenTime) { if ifModifiedSince(objInfo.ModTime, givenTime) {
// If the object is modified since the specified time. // If the object is modified since the specified time.
writeHeaders() writeHeaders()
writeErrorResponse(w, ErrPreconditionFailed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
return true return true
} }
} }
@@ -102,7 +102,7 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf
if objInfo.ETag != "" && !isETagEqual(objInfo.ETag, ifMatchETagHeader) { if objInfo.ETag != "" && !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
// If the object ETag does not match with the specified ETag. // If the object ETag does not match with the specified ETag.
writeHeaders() writeHeaders()
writeErrorResponse(w, ErrPreconditionFailed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
return true return true
} }
} }
@@ -114,7 +114,7 @@ func checkCopyObjectPreconditions(w http.ResponseWriter, r *http.Request, objInf
if objInfo.ETag != "" && isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) { if objInfo.ETag != "" && isETagEqual(objInfo.ETag, ifNoneMatchETagHeader) {
// If the object ETag matches with the specified ETag. // If the object ETag matches with the specified ETag.
writeHeaders() writeHeaders()
writeErrorResponse(w, ErrPreconditionFailed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
return true return true
} }
} }
@@ -174,7 +174,7 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, objInfo ObjectIn
if ifModifiedSince(objInfo.ModTime, givenTime) { if ifModifiedSince(objInfo.ModTime, givenTime) {
// If the object is modified since the specified time. // If the object is modified since the specified time.
writeHeaders() writeHeaders()
writeErrorResponse(w, ErrPreconditionFailed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
return true return true
} }
} }
@@ -187,7 +187,7 @@ func checkPreconditions(w http.ResponseWriter, r *http.Request, objInfo ObjectIn
if !isETagEqual(objInfo.ETag, ifMatchETagHeader) { if !isETagEqual(objInfo.ETag, ifMatchETagHeader) {
// If the object ETag does not match with the specified ETag. // If the object ETag does not match with the specified ETag.
writeHeaders() writeHeaders()
writeErrorResponse(w, ErrPreconditionFailed, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, errorCodes.ToAPIErr(ErrPreconditionFailed), r.URL, guessIsBrowserReq(r))
return true return true
} }
} }
+246 -215
View File
File diff suppressed because it is too large Load Diff
+16 -116
View File
@@ -85,7 +85,7 @@ func testAPIHeadObjectHandler(obj ObjectLayer, instanceType, bucketName string,
// iterate through the above set of inputs and upload the object. // iterate through the above set of inputs and upload the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err := obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), input.metaData, ObjectOptions{}) _, err := obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -357,7 +357,7 @@ func testAPIGetObjectHandler(obj ObjectLayer, instanceType, bucketName string, a
// iterate through the above set of inputs and upload the object. // iterate through the above set of inputs and upload the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err := obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), input.metaData, ObjectOptions{}) _, err := obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -1393,7 +1393,7 @@ func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketNam
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName,
mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), input.metaData, opts) mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -1406,7 +1406,7 @@ func testAPICopyObjectPartHandlerSanity(obj ObjectLayer, instanceType, bucketNam
// PutObjectPart API HTTP Handler has to be tested in isolation, // PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered, // that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer. // That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("Minio %s : <ERROR> %s", instanceType, err) t.Fatalf("Minio %s : <ERROR> %s", instanceType, err)
@@ -1503,7 +1503,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
// iterate through the above set of inputs and upload the object. // iterate through the above set of inputs and upload the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), input.metaData, opts) _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -1516,7 +1516,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
// PutObjectPart API HTTP Handler has to be tested in isolation, // PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered, // that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer. // That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("Minio %s : <ERROR> %s", instanceType, err) t.Fatalf("Minio %s : <ERROR> %s", instanceType, err)
@@ -1738,7 +1738,6 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
for i, testCase := range testCases { for i, testCase := range testCases {
var req *http.Request var req *http.Request
var reqV2 *http.Request
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler. // initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
if !testCase.invalidPartNumber || !testCase.maximumPartNumber { if !testCase.invalidPartNumber || !testCase.maximumPartNumber {
@@ -1782,37 +1781,6 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
t.Fatalf("Test %d: %s: Expected only one entry returned %d entries", i+1, instanceType, len(results.Parts)) t.Fatalf("Test %d: %s: Expected only one entry returned %d entries", i+1, instanceType, len(results.Parts))
} }
} }
// Verify response of the V2 signed HTTP request.
// initialize HTTP NewRecorder, this records any mutations to response writer inside the handler.
recV2 := httptest.NewRecorder()
reqV2, err = newTestRequest("PUT", getCopyObjectPartURL("", testCase.bucketName, testObject, testCase.uploadID, "1"), 0, nil)
if err != nil {
t.Fatalf("Test %d: Failed to create HTTP request for copy Object: <ERROR> %v", i+1, err)
}
// "X-Amz-Copy-Source" header contains the information about the source bucket and the object to copied.
if testCase.copySourceHeader != "" {
reqV2.Header.Set("X-Amz-Copy-Source", testCase.copySourceHeader)
}
if testCase.copySourceVersionID != "" {
reqV2.Header.Set("X-Amz-Copy-Source-Version-Id", testCase.copySourceVersionID)
}
if testCase.copySourceRange != "" {
reqV2.Header.Set("X-Amz-Copy-Source-Range", testCase.copySourceRange)
}
err = signRequestV2(reqV2, testCase.accessKey, testCase.secretKey)
if err != nil {
t.Fatalf("Failed to V2 Sign the HTTP request: %v.", err)
}
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
// Call the ServeHTTP to execute the handler.
apiRouter.ServeHTTP(recV2, reqV2)
if recV2.Code != testCase.expectedRespStatus {
t.Errorf("Test %d: %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, recV2.Code)
}
} }
// HTTP request for testing when `ObjectLayer` is set to `nil`. // HTTP request for testing when `ObjectLayer` is set to `nil`.
@@ -1895,7 +1863,7 @@ func testAPICopyObjectHandler(obj ObjectLayer, instanceType, bucketName string,
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
var objInfo ObjectInfo var objInfo ObjectInfo
objInfo, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), input.metaData, opts) objInfo, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -2535,7 +2503,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
// initiate new multipart uploadID. // initiate new multipart uploadID.
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, nil, opts) uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("Minio %s : <ERROR> %s", instanceType, err) t.Fatalf("Minio %s : <ERROR> %s", instanceType, err)
@@ -2672,7 +2640,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(toAPIErrorCode(ctx, InvalidPart{})), expectedContent: encodeResponse(getAPIErrorResponse(toAPIError(ctx, InvalidPart{}),
getGetObjectURL("", bucketName, objectName), "")), getGetObjectURL("", bucketName, objectName), "")),
expectedRespStatus: http.StatusBadRequest, expectedRespStatus: http.StatusBadRequest,
}, },
@@ -2702,7 +2670,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(toAPIErrorCode(ctx, InvalidUploadID{UploadID: "abc"})), expectedContent: encodeResponse(getAPIErrorResponse(toAPIError(ctx, InvalidUploadID{UploadID: "abc"}),
getGetObjectURL("", bucketName, objectName), "")), getGetObjectURL("", bucketName, objectName), "")),
expectedRespStatus: http.StatusNotFound, expectedRespStatus: http.StatusNotFound,
}, },
@@ -2717,7 +2685,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
expectedContent: encodeResponse(completeMultipartAPIError{int64(4), int64(5242880), 1, "e2fc714c4727ee9395f324cd2e7f331f", expectedContent: encodeResponse(completeMultipartAPIError{int64(4), int64(5242880), 1, "e2fc714c4727ee9395f324cd2e7f331f",
getAPIErrorResponse(getAPIError(toAPIErrorCode(ctx, PartTooSmall{PartNumber: 1})), getAPIErrorResponse(toAPIError(ctx, PartTooSmall{PartNumber: 1}),
getGetObjectURL("", bucketName, objectName), "")}), getGetObjectURL("", bucketName, objectName), "")}),
expectedRespStatus: http.StatusBadRequest, expectedRespStatus: http.StatusBadRequest,
}, },
@@ -2731,7 +2699,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
expectedContent: encodeResponse(getAPIErrorResponse(getAPIError(toAPIErrorCode(ctx, InvalidPart{})), expectedContent: encodeResponse(getAPIErrorResponse(toAPIError(ctx, InvalidPart{}),
getGetObjectURL("", bucketName, objectName), "")), getGetObjectURL("", bucketName, objectName), "")),
expectedRespStatus: http.StatusBadRequest, expectedRespStatus: http.StatusBadRequest,
}, },
@@ -2886,7 +2854,7 @@ func testAPIAbortMultipartHandler(obj ObjectLayer, instanceType, bucketName stri
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
// initiate new multipart uploadID. // initiate new multipart uploadID.
uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, nil, opts) uploadID, err = obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("Minio %s : <ERROR> %s", instanceType, err) t.Fatalf("Minio %s : <ERROR> %s", instanceType, err)
@@ -3038,7 +3006,6 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
credentials auth.Credentials, t *testing.T) { credentials auth.Credentials, t *testing.T) {
var err error var err error
var opts ObjectOptions
objectName := "test-object" objectName := "test-object"
// Object used for anonymous API request test. // Object used for anonymous API request test.
anonObjectName := "test-anon-obj" anonObjectName := "test-anon-obj"
@@ -3066,7 +3033,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
// iterate through the above set of inputs and upload the object. // iterate through the above set of inputs and upload the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), input.metaData, opts) _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData[""], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -3189,73 +3156,6 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq) ExecObjectLayerAPINilTest(t, nilBucket, nilObject, instanceType, apiRouter, nilReq)
} }
// TestAPIPutObjectPartHandlerPreSign - Tests validate the response of PutObjectPart HTTP handler
// when the request signature type is PreSign.
func TestAPIPutObjectPartHandlerPreSign(t *testing.T) {
defer DetectTestLeak(t)()
ExecObjectLayerAPITest(t, testAPIPutObjectPartHandlerPreSign, []string{"NewMultipart", "PutObjectPart"})
}
func testAPIPutObjectPartHandlerPreSign(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
credentials auth.Credentials, t *testing.T) {
testObject := "testobject"
rec := httptest.NewRecorder()
req, err := newTestSignedRequestV4("POST", getNewMultipartURL("", bucketName, "testobject"),
0, nil, credentials.AccessKey, credentials.SecretKey, nil)
if err != nil {
t.Fatalf("[%s] - Failed to create a signed request to initiate multipart upload for %s/%s: <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
// Get uploadID of the mulitpart upload initiated.
var mpartResp InitiateMultipartUploadResponse
mpartRespBytes, err := ioutil.ReadAll(rec.Result().Body)
if err != nil {
t.Fatalf("[%s] Failed to read NewMultipartUpload response <ERROR> %v", instanceType, err)
}
err = xml.Unmarshal(mpartRespBytes, &mpartResp)
if err != nil {
t.Fatalf("[%s] Failed to unmarshal NewMultipartUpload response <ERROR> %v", instanceType, err)
}
rec = httptest.NewRecorder()
req, err = newTestRequest("PUT", getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
int64(len("hello")), bytes.NewReader([]byte("hello")))
if err != nil {
t.Fatalf("[%s] - Failed to create an unsigned request to put object part for %s/%s <ERROR> %v",
instanceType, bucketName, testObject, err)
}
err = preSignV2(req, credentials.AccessKey, credentials.SecretKey, int64(10*60*60))
if err != nil {
t.Fatalf("[%s] - Failed to presign an unsigned request to put object part for %s/%s <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d", 1, instanceType, rec.Code)
}
rec = httptest.NewRecorder()
req, err = newTestRequest("PUT", getPutObjectPartURL("", bucketName, testObject, mpartResp.UploadID, "1"),
int64(len("hello")), bytes.NewReader([]byte("hello")))
if err != nil {
t.Fatalf("[%s] - Failed to create an unsigned request to put object part for %s/%s <ERROR> %v",
instanceType, bucketName, testObject, err)
}
err = preSignV4(req, credentials.AccessKey, credentials.SecretKey, int64(10*60*60))
if err != nil {
t.Fatalf("[%s] - Failed to presign an unsigned request to put object part for %s/%s <ERROR> %v",
instanceType, bucketName, testObject, err)
}
apiRouter.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("Test %d %s expected to succeed but failed with HTTP status code %d", 1, instanceType, rec.Code)
}
}
// TestAPIPutObjectPartHandlerStreaming - Tests validate the response of PutObjectPart HTTP handler // TestAPIPutObjectPartHandlerStreaming - Tests validate the response of PutObjectPart HTTP handler
// when the request signature type is `streaming signature`. // when the request signature type is `streaming signature`.
func TestAPIPutObjectPartHandlerStreaming(t *testing.T) { func TestAPIPutObjectPartHandlerStreaming(t *testing.T) {
@@ -3360,7 +3260,7 @@ func testAPIPutObjectPartHandler(obj ObjectLayer, instanceType, bucketName strin
// PutObjectPart API HTTP Handler has to be tested in isolation, // PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered, // that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer. // That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("Minio %s : <ERROR> %s", instanceType, err) t.Fatalf("Minio %s : <ERROR> %s", instanceType, err)
@@ -3763,7 +3663,7 @@ func testAPIListObjectPartsHandler(obj ObjectLayer, instanceType, bucketName str
// PutObjectPart API HTTP Handler has to be tested in isolation, // PutObjectPart API HTTP Handler has to be tested in isolation,
// that is without any other handler being registered, // that is without any other handler being registered,
// That's why NewMultipartUpload is initiated using ObjectLayer. // That's why NewMultipartUpload is initiated using ObjectLayer.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, testObject, opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("Minio %s : <ERROR> %s", instanceType, err) t.Fatalf("Minio %s : <ERROR> %s", instanceType, err)
+18 -18
View File
@@ -95,7 +95,7 @@ func testMultipartObjectCreation(obj ObjectLayer, instanceType string, t TestErr
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
uploadID, err := obj.NewMultipartUpload(context.Background(), "bucket", "key", nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), "bucket", "key", opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -139,7 +139,7 @@ func testMultipartObjectAbort(obj ObjectLayer, instanceType string, t TestErrHan
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
uploadID, err := obj.NewMultipartUpload(context.Background(), "bucket", "key", nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), "bucket", "key", opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -180,8 +180,8 @@ func (s *ObjectLayerAPISuite) TestMultipleObjectCreation(t *testing.T) {
// Tests validate object creation. // Tests validate object creation.
func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrHandler) { func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrHandler) {
objects := make(map[string][]byte) objects := make(map[string][]byte)
err := obj.MakeBucketWithLocation(context.Background(), "bucket", "")
var opts ObjectOptions var opts ObjectOptions
err := obj.MakeBucketWithLocation(context.Background(), "bucket", "")
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -199,7 +199,7 @@ func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrH
metadata := make(map[string]string) metadata := make(map[string]string)
metadata["etag"] = expectedETaghex metadata["etag"] = expectedETaghex
var objInfo ObjectInfo var objInfo ObjectInfo
objInfo, err = obj.PutObject(context.Background(), "bucket", key, mustGetPutObjReader(t, bytes.NewBufferString(randomString), int64(len(randomString)), metadata["etag"], ""), metadata, opts) objInfo, err = obj.PutObject(context.Background(), "bucket", key, mustGetPutObjReader(t, bytes.NewBufferString(randomString), int64(len(randomString)), metadata["etag"], ""), ObjectOptions{UserDefined: metadata})
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -253,7 +253,7 @@ func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) {
// check before paging occurs. // check before paging occurs.
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
key := "obj" + strconv.Itoa(i) key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject(context.Background(), "bucket", key, mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", key, mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -273,7 +273,7 @@ func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) {
// check after paging occurs pages work. // check after paging occurs pages work.
for i := 6; i <= 10; i++ { for i := 6; i <= 10; i++ {
key := "obj" + strconv.Itoa(i) key := "obj" + strconv.Itoa(i)
_, err = obj.PutObject(context.Background(), "bucket", key, mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", key, mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -290,11 +290,11 @@ func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) {
} }
// check paging with prefix at end returns less objects. // check paging with prefix at end returns less objects.
{ {
_, err = obj.PutObject(context.Background(), "bucket", "newPrefix", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "newPrefix", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
_, err = obj.PutObject(context.Background(), "bucket", "newPrefix2", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "newPrefix2", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -332,11 +332,11 @@ func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) {
// check delimited results with delimiter and prefix. // check delimited results with delimiter and prefix.
{ {
_, err = obj.PutObject(context.Background(), "bucket", "this/is/delimited", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "this/is/delimited", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
_, err = obj.PutObject(context.Background(), "bucket", "this/is/also/a/delimited/file", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "this/is/also/a/delimited/file", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -448,14 +448,14 @@ func testObjectOverwriteWorks(obj ObjectLayer, instanceType string, t TestErrHan
var opts ObjectOptions var opts ObjectOptions
uploadContent := "The list of parts was not in ascending order. The parts list must be specified in order by part number." uploadContent := "The list of parts was not in ascending order. The parts list must be specified in order by part number."
length := int64(len(uploadContent)) length := int64(len(uploadContent))
_, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), length, "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), length, "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
uploadContent = "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed." uploadContent = "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."
length = int64(len(uploadContent)) length = int64(len(uploadContent))
_, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), length, "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), length, "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -478,7 +478,7 @@ func (s *ObjectLayerAPISuite) TestNonExistantBucketOperations(t *testing.T) {
// Tests validate that bucket operation on non-existent bucket fails. // Tests validate that bucket operation on non-existent bucket fails.
func testNonExistantBucketOperations(obj ObjectLayer, instanceType string, t TestErrHandler) { func testNonExistantBucketOperations(obj ObjectLayer, instanceType string, t TestErrHandler) {
var opts ObjectOptions var opts ObjectOptions
_, err := obj.PutObject(context.Background(), "bucket1", "object", mustGetPutObjReader(t, bytes.NewBufferString("one"), int64(len("one")), "", ""), nil, opts) _, err := obj.PutObject(context.Background(), "bucket1", "object", mustGetPutObjReader(t, bytes.NewBufferString("one"), int64(len("one")), "", ""), opts)
if err == nil { if err == nil {
t.Fatal("Expected error but found nil") t.Fatal("Expected error but found nil")
} }
@@ -526,7 +526,7 @@ func testPutObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
var bytesBuffer1 bytes.Buffer var bytesBuffer1 bytes.Buffer
var opts ObjectOptions var opts ObjectOptions
_, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, readerEOF, length, "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, readerEOF, length, "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -539,7 +539,7 @@ func testPutObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
} }
var bytesBuffer2 bytes.Buffer var bytesBuffer2 bytes.Buffer
_, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, readerNoEOF, length, "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "object", mustGetPutObjReader(t, readerNoEOF, length, "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -568,7 +568,7 @@ func testPutObjectInSubdir(obj ObjectLayer, instanceType string, t TestErrHandle
uploadContent := `The specified multipart upload does not exist. The upload ID might be invalid, or the multipart uploadContent := `The specified multipart upload does not exist. The upload ID might be invalid, or the multipart
upload might have been aborted or completed.` upload might have been aborted or completed.`
length := int64(len(uploadContent)) length := int64(len(uploadContent))
_, err = obj.PutObject(context.Background(), "bucket", "dir1/dir2/object", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), length, "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "dir1/dir2/object", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), length, "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
@@ -741,7 +741,7 @@ func testGetDirectoryReturnsObjectNotFound(obj ObjectLayer, instanceType string,
content := "One or more of the specified parts could not be found. The part might not have been uploaded, or the specified entity tag might not have matched the part's entity tag." content := "One or more of the specified parts could not be found. The part might not have been uploaded, or the specified entity tag might not have matched the part's entity tag."
length := int64(len(content)) length := int64(len(content))
var opts ObjectOptions var opts ObjectOptions
_, err = obj.PutObject(context.Background(), bucketName, "dir1/dir3/object", mustGetPutObjReader(t, bytes.NewBufferString(content), length, "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucketName, "dir1/dir3/object", mustGetPutObjReader(t, bytes.NewBufferString(content), length, "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
@@ -783,7 +783,7 @@ func testContentType(obj ObjectLayer, instanceType string, t TestErrHandler) {
var opts ObjectOptions var opts ObjectOptions
uploadContent := "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed." uploadContent := "The specified multipart upload does not exist. The upload ID might be invalid, or the multipart upload might have been aborted or completed."
// Test empty. // Test empty.
_, err = obj.PutObject(context.Background(), "bucket", "minio.png", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), "bucket", "minio.png", mustGetPutObjReader(t, bytes.NewBufferString(uploadContent), int64(len(uploadContent)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err) t.Fatalf("%s: <ERROR> %s", instanceType, err)
} }
+3 -2
View File
@@ -1105,8 +1105,9 @@ func (s *posix) WriteAll(volume, path string, buf []byte) (err error) {
return errFaultyDisk return errFaultyDisk
} }
// Create file if not found // Create file if not found. Note that it is created with os.O_EXCL flag as the file
w, err := s.openFile(volume, path, os.O_CREATE|os.O_SYNC|os.O_WRONLY) // always is supposed to be created in the tmp directory with a unique file name.
w, err := s.openFile(volume, path, os.O_CREATE|os.O_SYNC|os.O_WRONLY|os.O_EXCL)
if err != nil { if err != nil {
return err return err
} }
+2 -2
View File
@@ -59,9 +59,9 @@ func (c *Client) Call(method string, values url.Values, body io.Reader, length i
} }
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
defer xhttp.DrainBody(resp.Body)
// Limit the ReadAll(), just in case, because of a bug, the server responds with large data. // Limit the ReadAll(), just in case, because of a bug, the server responds with large data.
r := io.LimitReader(resp.Body, 1024) b, err := ioutil.ReadAll(io.LimitReader(resp.Body, 4096))
b, err := ioutil.ReadAll(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+1 -25
View File
@@ -22,8 +22,6 @@ import (
"encoding/gob" "encoding/gob"
"errors" "errors"
"fmt" "fmt"
"io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"reflect" "reflect"
@@ -42,28 +40,6 @@ type Client struct {
serviceURL *xnet.URL serviceURL *xnet.URL
} }
// closeResponse close non nil response with any response Body.
// convenient wrapper to drain any remaining data on response body.
//
// Subsequently this allows golang http RoundTripper
// to re-use the same connection for future requests.
func closeResponse(body io.ReadCloser) {
// Callers should close resp.Body when done reading from it.
// If resp.Body is not closed, the Client's underlying RoundTripper
// (typically Transport) may not be able to re-use a persistent TCP
// connection to the server for a subsequent "keep-alive" request.
if body != nil {
// Drain any remaining Body and then close the connection.
// Without this closing connection would disallow re-using
// the same connection for future uses.
// - http://stackoverflow.com/a/17961593/4465767
bufp := b512pool.Get().(*[]byte)
defer b512pool.Put(bufp)
io.CopyBuffer(ioutil.Discard, body, *bufp)
body.Close()
}
}
// Call - calls service method on RPC server. // Call - calls service method on RPC server.
func (client *Client) Call(serviceMethod string, args, reply interface{}) error { func (client *Client) Call(serviceMethod string, args, reply interface{}) error {
replyKind := reflect.TypeOf(reply).Kind() replyKind := reflect.TypeOf(reply).Kind()
@@ -93,7 +69,7 @@ func (client *Client) Call(serviceMethod string, args, reply interface{}) error
if err != nil { if err != nil {
return err return err
} }
defer closeResponse(response.Body) defer xhttp.DrainBody(response.Body)
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
return fmt.Errorf("%v rpc call failed with error code %v", serviceMethod, response.StatusCode) return fmt.Errorf("%v rpc call failed with error code %v", serviceMethod, response.StatusCode)
+1 -1
View File
@@ -222,7 +222,7 @@ func TestParseCredentialHeader(t *testing.T) {
actualCredential, actualErrCode := parseCredentialHeader(testCase.inputCredentialStr, "us-west-1") actualCredential, actualErrCode := parseCredentialHeader(testCase.inputCredentialStr, "us-west-1")
// validating the credential fields. // validating the credential fields.
if testCase.expectedErrCode != actualErrCode { if testCase.expectedErrCode != actualErrCode {
t.Fatalf("Test %d: Expected the APIErrCode to be %s, got %s", i+1, errorCodeResponse[testCase.expectedErrCode].Code, errorCodeResponse[actualErrCode].Code) t.Fatalf("Test %d: Expected the APIErrCode to be %s, got %s", i+1, errorCodes[testCase.expectedErrCode].Code, errorCodes[actualErrCode].Code)
} }
if actualErrCode == ErrNone { if actualErrCode == ErrNone {
validateCredentialfields(t, i+1, testCase.expectedCredentials, actualCredential) validateCredentialfields(t, i+1, testCase.expectedCredentials, actualCredential)
+1 -1
View File
@@ -31,7 +31,7 @@ func niceError(code APIErrorCode) string {
return "ErrNone" return "ErrNone"
} }
return fmt.Sprintf("%s (%s)", errorCodeResponse[code].Code, errorCodeResponse[code].Description) return fmt.Sprintf("%s (%s)", errorCodes[code].Code, errorCodes[code].Description)
} }
func TestDoesPolicySignatureMatch(t *testing.T) { func TestDoesPolicySignatureMatch(t *testing.T) {
+7 -7
View File
@@ -189,7 +189,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
// Object for test case 1 - No StorageClass defined, no MetaData in PutObject // Object for test case 1 - No StorageClass defined, no MetaData in PutObject
object1 := "object1" object1 := "object1"
_, err = obj.PutObject(context.Background(), bucket, object1, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucket, object1, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), opts)
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -200,7 +200,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
object2 := "object2" object2 := "object2"
metadata2 := make(map[string]string) metadata2 := make(map[string]string)
metadata2["x-amz-storage-class"] = reducedRedundancyStorageClass metadata2["x-amz-storage-class"] = reducedRedundancyStorageClass
_, err = obj.PutObject(context.Background(), bucket, object2, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), metadata2, opts) _, err = obj.PutObject(context.Background(), bucket, object2, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{UserDefined: metadata2})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -211,7 +211,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
object3 := "object3" object3 := "object3"
metadata3 := make(map[string]string) metadata3 := make(map[string]string)
metadata3["x-amz-storage-class"] = standardStorageClass metadata3["x-amz-storage-class"] = standardStorageClass
_, err = obj.PutObject(context.Background(), bucket, object3, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), metadata3, opts) _, err = obj.PutObject(context.Background(), bucket, object3, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{UserDefined: metadata3})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -227,7 +227,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
Scheme: "EC", Scheme: "EC",
} }
_, err = obj.PutObject(context.Background(), bucket, object4, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), metadata4, opts) _, err = obj.PutObject(context.Background(), bucket, object4, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{UserDefined: metadata4})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -245,7 +245,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
Scheme: "EC", Scheme: "EC",
} }
_, err = obj.PutObject(context.Background(), bucket, object5, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), metadata5, opts) _, err = obj.PutObject(context.Background(), bucket, object5, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{UserDefined: metadata5})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -263,7 +263,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
Scheme: "EC", Scheme: "EC",
} }
_, err = obj.PutObject(context.Background(), bucket, object6, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), metadata6, opts) _, err = obj.PutObject(context.Background(), bucket, object6, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{UserDefined: metadata6})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -281,7 +281,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
Scheme: "EC", Scheme: "EC",
} }
_, err = obj.PutObject(context.Background(), bucket, object7, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), metadata7, opts) _, err = obj.PutObject(context.Background(), bucket, object7, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{UserDefined: metadata7})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
+15 -14
View File
@@ -32,6 +32,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
"github.com/minio/minio/cmd/rest" "github.com/minio/minio/cmd/rest"
xnet "github.com/minio/minio/pkg/net" xnet "github.com/minio/minio/pkg/net"
@@ -164,7 +165,7 @@ func (client *storageRESTClient) DiskInfo() (info DiskInfo, err error) {
if err != nil { if err != nil {
return return
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&info) err = gob.NewDecoder(respBody).Decode(&info)
return info, err return info, err
} }
@@ -174,7 +175,7 @@ func (client *storageRESTClient) MakeVol(volume string) (err error) {
values := make(url.Values) values := make(url.Values)
values.Set(storageRESTVolume, volume) values.Set(storageRESTVolume, volume)
respBody, err := client.call(storageRESTMethodMakeVol, values, nil, -1) respBody, err := client.call(storageRESTMethodMakeVol, values, nil, -1)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
@@ -184,7 +185,7 @@ func (client *storageRESTClient) ListVols() (volinfo []VolInfo, err error) {
if err != nil { if err != nil {
return return
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&volinfo) err = gob.NewDecoder(respBody).Decode(&volinfo)
return volinfo, err return volinfo, err
} }
@@ -197,7 +198,7 @@ func (client *storageRESTClient) StatVol(volume string) (volInfo VolInfo, err er
if err != nil { if err != nil {
return return
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&volInfo) err = gob.NewDecoder(respBody).Decode(&volInfo)
return volInfo, err return volInfo, err
} }
@@ -207,7 +208,7 @@ func (client *storageRESTClient) DeleteVol(volume string) (err error) {
values := make(url.Values) values := make(url.Values)
values.Set(storageRESTVolume, volume) values.Set(storageRESTVolume, volume)
respBody, err := client.call(storageRESTMethodDeleteVol, values, nil, -1) respBody, err := client.call(storageRESTMethodDeleteVol, values, nil, -1)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
@@ -218,7 +219,7 @@ func (client *storageRESTClient) AppendFile(volume, path string, buffer []byte)
values.Set(storageRESTFilePath, path) values.Set(storageRESTFilePath, path)
reader := bytes.NewBuffer(buffer) reader := bytes.NewBuffer(buffer)
respBody, err := client.call(storageRESTMethodAppendFile, values, reader, -1) respBody, err := client.call(storageRESTMethodAppendFile, values, reader, -1)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
@@ -228,7 +229,7 @@ func (client *storageRESTClient) CreateFile(volume, path string, length int64, r
values.Set(storageRESTFilePath, path) values.Set(storageRESTFilePath, path)
values.Set(storageRESTLength, strconv.Itoa(int(length))) values.Set(storageRESTLength, strconv.Itoa(int(length)))
respBody, err := client.call(storageRESTMethodCreateFile, values, r, length) respBody, err := client.call(storageRESTMethodCreateFile, values, r, length)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
@@ -239,7 +240,7 @@ func (client *storageRESTClient) WriteAll(volume, path string, buffer []byte) er
values.Set(storageRESTFilePath, path) values.Set(storageRESTFilePath, path)
reader := bytes.NewBuffer(buffer) reader := bytes.NewBuffer(buffer)
respBody, err := client.call(storageRESTMethodWriteAll, values, reader, -1) respBody, err := client.call(storageRESTMethodWriteAll, values, reader, -1)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
@@ -252,7 +253,7 @@ func (client *storageRESTClient) StatFile(volume, path string) (info FileInfo, e
if err != nil { if err != nil {
return info, err return info, err
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&info) err = gob.NewDecoder(respBody).Decode(&info)
return info, err return info, err
} }
@@ -266,7 +267,7 @@ func (client *storageRESTClient) ReadAll(volume, path string) ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return ioutil.ReadAll(respBody) return ioutil.ReadAll(respBody)
} }
@@ -302,7 +303,7 @@ func (client *storageRESTClient) ReadFile(volume, path string, offset int64, buf
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
n, err := io.ReadFull(respBody, buffer) n, err := io.ReadFull(respBody, buffer)
return int64(n), err return int64(n), err
} }
@@ -317,7 +318,7 @@ func (client *storageRESTClient) ListDir(volume, dirPath string, count int) (ent
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer CloseResponse(respBody) defer http.DrainBody(respBody)
err = gob.NewDecoder(respBody).Decode(&entries) err = gob.NewDecoder(respBody).Decode(&entries)
return entries, err return entries, err
} }
@@ -328,7 +329,7 @@ func (client *storageRESTClient) DeleteFile(volume, path string) error {
values.Set(storageRESTVolume, volume) values.Set(storageRESTVolume, volume)
values.Set(storageRESTFilePath, path) values.Set(storageRESTFilePath, path)
respBody, err := client.call(storageRESTMethodDeleteFile, values, nil, -1) respBody, err := client.call(storageRESTMethodDeleteFile, values, nil, -1)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
@@ -340,7 +341,7 @@ func (client *storageRESTClient) RenameFile(srcVolume, srcPath, dstVolume, dstPa
values.Set(storageRESTDstVolume, dstVolume) values.Set(storageRESTDstVolume, dstVolume)
values.Set(storageRESTDstPath, dstPath) values.Set(storageRESTDstPath, dstPath)
respBody, err := client.call(storageRESTMethodRenameFile, values, nil, -1) respBody, err := client.call(storageRESTMethodRenameFile, values, nil, -1)
defer CloseResponse(respBody) defer http.DrainBody(respBody)
return err return err
} }
+1 -1
View File
@@ -16,7 +16,7 @@
package cmd package cmd
const storageRESTVersion = "v3" const storageRESTVersion = "v4"
const storageRESTPath = minioReservedBucketPath + "/storage/" + storageRESTVersion + "/" const storageRESTPath = minioReservedBucketPath + "/storage/" + storageRESTVersion + "/"
const ( const (
+10
View File
@@ -43,8 +43,18 @@ func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error)
w.Write([]byte(err.Error())) w.Write([]byte(err.Error()))
} }
// Authenticates storage client's requests.
func storageServerRequestAuthenticate(r *http.Request) error {
_, _, err := webRequestAuthenticate(r)
return err
}
// IsValid - To authenticate and verify the time difference. // IsValid - To authenticate and verify the time difference.
func (s *storageRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool { func (s *storageRESTServer) IsValid(w http.ResponseWriter, r *http.Request) bool {
if err := storageServerRequestAuthenticate(r); err != nil {
w.WriteHeader(http.StatusForbidden)
return false
}
requestTimeStr := r.Header.Get("X-Minio-Time") requestTimeStr := r.Header.Get("X-Minio-Time")
requestTime, err := time.Parse(time.RFC3339, requestTimeStr) requestTime, err := time.Parse(time.RFC3339, requestTimeStr)
if err != nil { if err != nil {
+136 -154
View File
@@ -17,6 +17,7 @@
package cmd package cmd
import ( import (
"fmt"
"net/http" "net/http"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@@ -28,6 +29,10 @@ import (
const ( const (
// STS API version. // STS API version.
stsAPIVersion = "2011-06-15" stsAPIVersion = "2011-06-15"
// STS API action constants
clientGrants = "AssumeRoleWithClientGrants"
webIdentity = "AssumeRoleWithWebIdentity"
) )
// stsAPIHandlers implements and provides http handlers for AWS STS API. // stsAPIHandlers implements and provides http handlers for AWS STS API.
@@ -41,20 +46,147 @@ func registerSTSRouter(router *mux.Router) {
// STS Router // STS Router
stsRouter := router.NewRoute().PathPrefix("/").Subrouter() stsRouter := router.NewRoute().PathPrefix("/").Subrouter()
// Assume roles with JWT handler, handles both ClientGrants and WebIdentity.
stsRouter.Methods("POST").HeadersRegexp("Content-Type", "application/x-www-form-urlencoded*").
HandlerFunc(httpTraceAll(sts.AssumeRoleWithJWT))
// AssumeRoleWithClientGrants // AssumeRoleWithClientGrants
stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)). stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithClientGrants)).
Queries("Action", "AssumeRoleWithClientGrants"). Queries("Action", clientGrants).
Queries("Version", stsAPIVersion). Queries("Version", stsAPIVersion).
Queries("Token", "{Token:.*}") Queries("Token", "{Token:.*}")
// AssumeRoleWithWebIdentity // AssumeRoleWithWebIdentity
stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)). stsRouter.Methods("POST").HandlerFunc(httpTraceAll(sts.AssumeRoleWithWebIdentity)).
Queries("Action", "AssumeRoleWithWebIdentity"). Queries("Action", webIdentity).
Queries("Version", stsAPIVersion). Queries("Version", stsAPIVersion).
Queries("WebIdentityToken", "{Token:.*}") Queries("WebIdentityToken", "{Token:.*}")
} }
func (sts *stsAPIHandlers) AssumeRoleWithJWT(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AssumeRoleInternalFunction")
// Parse the incoming form data.
if err := r.ParseForm(); err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
if r.Form.Get("Version") != stsAPIVersion {
logger.LogIf(ctx, fmt.Errorf("Invalid STS API version %s, expecting %s", r.Form.Get("Version"), stsAPIVersion))
writeSTSErrorResponse(w, ErrSTSMissingParameter)
return
}
action := r.Form.Get("Action")
switch action {
case clientGrants, webIdentity:
default:
logger.LogIf(ctx, fmt.Errorf("Unsupported action %s", action))
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
ctx = newContext(r, w, action)
defer logger.AuditLog(w, r, action, nil)
if globalIAMValidators == nil {
writeSTSErrorResponse(w, ErrSTSNotInitialized)
return
}
v, err := globalIAMValidators.Get("jwt")
if err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
token := r.Form.Get("Token")
if token == "" {
token = r.Form.Get("WebIdentityToken")
}
m, err := v.Validate(token, r.Form.Get("DurationSeconds"))
if err != nil {
switch err {
case validator.ErrTokenExpired:
switch action {
case clientGrants:
writeSTSErrorResponse(w, ErrSTSClientGrantsExpiredToken)
case webIdentity:
writeSTSErrorResponse(w, ErrSTSWebIdentityExpiredToken)
}
return
case validator.ErrInvalidDuration:
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
secret := globalServerConfig.GetCredential().SecretKey
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
if err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInternalError)
return
}
// JWT has requested a custom claim with policy value set.
// This is a Minio STS API specific value, this value should
// be set and configured on your identity provider as part of
// JWT custom claims.
var policyName string
if v, ok := m["policy"]; ok {
policyName, _ = v.(string)
}
var subFromToken string
if v, ok := m["sub"]; ok {
subFromToken, _ = v.(string)
}
// Set the newly generated credentials.
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInternalError)
return
}
// Notify all other Minio peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
var encodedSuccessResponse []byte
switch action {
case clientGrants:
encodedSuccessResponse = encodeResponse(&AssumeRoleWithClientGrantsResponse{
Result: ClientGrantsResult{
Credentials: cred,
SubjectFromToken: subFromToken,
},
})
case webIdentity:
encodedSuccessResponse = encodeResponse(&AssumeRoleWithWebIdentityResponse{
Result: WebIdentityResult{
Credentials: cred,
SubjectFromWebIdentityToken: subFromToken,
},
})
}
writeSuccessResponseXML(w, encodedSuccessResponse)
}
// AssumeRoleWithWebIdentity - implementation of AWS STS API supporting OAuth2.0 // AssumeRoleWithWebIdentity - implementation of AWS STS API supporting OAuth2.0
// users from web identity provider such as Facebook, Google, or any OpenID // users from web identity provider such as Facebook, Google, or any OpenID
// Connect-compatible identity provider. // Connect-compatible identity provider.
@@ -62,82 +194,7 @@ func registerSTSRouter(router *mux.Router) {
// Eg:- // Eg:-
// $ curl https://minio:9000/?Action=AssumeRoleWithWebIdentity&WebIdentityToken=<jwt> // $ curl https://minio:9000/?Action=AssumeRoleWithWebIdentity&WebIdentityToken=<jwt>
func (sts *stsAPIHandlers) AssumeRoleWithWebIdentity(w http.ResponseWriter, r *http.Request) { func (sts *stsAPIHandlers) AssumeRoleWithWebIdentity(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AssumeRoleWithWebIdentity") sts.AssumeRoleWithJWT(w, r)
defer logger.AuditLog(w, r, "AssumeRoleWithWebIdentity", nil)
if globalIAMValidators == nil {
writeSTSErrorResponse(w, ErrSTSNotInitialized)
return
}
// NOTE: this API only accepts JWT tokens.
v, err := globalIAMValidators.Get("jwt")
if err != nil {
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
vars := mux.Vars(r)
m, err := v.Validate(vars["Token"], r.URL.Query().Get("DurationSeconds"))
if err != nil {
switch err {
case validator.ErrTokenExpired:
writeSTSErrorResponse(w, ErrSTSWebIdentityExpiredToken)
case validator.ErrInvalidDuration:
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
default:
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
}
return
}
secret := globalServerConfig.GetCredential().SecretKey
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
if err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInternalError)
return
}
// JWT has requested a custom claim with policy value set.
// This is a Minio STS API specific value, this value should
// be set and configured on your identity provider as part of
// JWT custom claims.
var policyName string
if v, ok := m["policy"]; ok {
policyName, _ = v.(string)
}
var subFromToken string
if v, ok := m["sub"]; ok {
subFromToken, _ = v.(string)
}
// Set the newly generated credentials.
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInternalError)
return
}
// Notify all other Minio peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
encodedSuccessResponse := encodeResponse(&AssumeRoleWithWebIdentityResponse{
Result: WebIdentityResult{
Credentials: cred,
SubjectFromWebIdentityToken: subFromToken,
},
})
writeSuccessResponseXML(w, encodedSuccessResponse)
} }
// AssumeRoleWithClientGrants - implementation of AWS STS extension API supporting // AssumeRoleWithClientGrants - implementation of AWS STS extension API supporting
@@ -146,80 +203,5 @@ func (sts *stsAPIHandlers) AssumeRoleWithWebIdentity(w http.ResponseWriter, r *h
// Eg:- // Eg:-
// $ curl https://minio:9000/?Action=AssumeRoleWithClientGrants&Token=<jwt> // $ curl https://minio:9000/?Action=AssumeRoleWithClientGrants&Token=<jwt>
func (sts *stsAPIHandlers) AssumeRoleWithClientGrants(w http.ResponseWriter, r *http.Request) { func (sts *stsAPIHandlers) AssumeRoleWithClientGrants(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AssumeRoleWithClientGrants") sts.AssumeRoleWithJWT(w, r)
defer logger.AuditLog(w, r, "AssumeRoleWithClientGrants", nil)
if globalIAMValidators == nil {
writeSTSErrorResponse(w, ErrSTSNotInitialized)
return
}
// NOTE: this API only accepts JWT tokens.
v, err := globalIAMValidators.Get("jwt")
if err != nil {
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
return
}
vars := mux.Vars(r)
m, err := v.Validate(vars["Token"], r.URL.Query().Get("DurationSeconds"))
if err != nil {
switch err {
case validator.ErrTokenExpired:
writeSTSErrorResponse(w, ErrSTSClientGrantsExpiredToken)
case validator.ErrInvalidDuration:
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
default:
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInvalidParameterValue)
}
return
}
secret := globalServerConfig.GetCredential().SecretKey
cred, err := auth.GetNewCredentialsWithMetadata(m, secret)
if err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInternalError)
return
}
// JWT has requested a custom claim with policy value set.
// This is a Minio STS API specific value, this value should
// be set and configured on your identity provider as part of
// JWT custom claims.
var policyName string
if v, ok := m["policy"]; ok {
policyName, _ = v.(string)
}
var subFromToken string
if v, ok := m["sub"]; ok {
subFromToken, _ = v.(string)
}
// Set the newly generated credentials.
if err = globalIAMSys.SetTempUser(cred.AccessKey, cred, policyName); err != nil {
logger.LogIf(ctx, err)
writeSTSErrorResponse(w, ErrSTSInternalError)
return
}
// Notify all other Minio peers to reload temp users
for _, nerr := range globalNotificationSys.LoadUsers() {
if nerr.Err != nil {
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
logger.LogIf(ctx, nerr.Err)
}
}
encodedSuccessResponse := encodeResponse(&AssumeRoleWithClientGrantsResponse{
Result: ClientGrantsResult{
Credentials: cred,
SubjectFromToken: subFromToken,
},
})
writeSuccessResponseXML(w, encodedSuccessResponse)
} }
+2 -2
View File
@@ -2357,8 +2357,8 @@ func TestToErrIsNil(t *testing.T) {
t.Errorf("Test expected to return nil, failed instead got a non-nil value %s", toStorageErr(nil)) t.Errorf("Test expected to return nil, failed instead got a non-nil value %s", toStorageErr(nil))
} }
ctx := context.Background() ctx := context.Background()
if toAPIErrorCode(ctx, nil) != ErrNone { if toAPIError(ctx, nil) != noError {
t.Errorf("Test expected error code to be ErrNone, failed instead provided %d", toAPIErrorCode(ctx, nil)) t.Errorf("Test expected error code to be ErrNone, failed instead provided %s", toAPIError(ctx, nil).Code)
} }
} }
+3 -2
View File
@@ -32,6 +32,7 @@ import (
"github.com/inconshreveable/go-update" "github.com/inconshreveable/go-update"
"github.com/minio/cli" "github.com/minio/cli"
xhttp "github.com/minio/minio/cmd/http"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier. _ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
"github.com/segmentio/go-prompt" "github.com/segmentio/go-prompt"
@@ -318,7 +319,7 @@ func downloadReleaseURL(releaseChecksumURL string, timeout time.Duration, mode s
if resp == nil { if resp == nil {
return content, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL) return content, fmt.Errorf("No response from server to download URL %s", releaseChecksumURL)
} }
defer CloseResponse(resp.Body) defer xhttp.DrainBody(resp.Body)
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return content, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status) return content, fmt.Errorf("Error downloading URL %s. Response: %v", releaseChecksumURL, resp.Status)
@@ -465,7 +466,7 @@ func doUpdate(sha256Hex string, latestReleaseTime time.Time, ok bool) (updateSta
if err != nil { if err != nil {
return updateStatusMsg, err return updateStatusMsg, err
} }
defer CloseResponse(resp.Body) defer xhttp.DrainBody(resp.Body)
// FIXME: add support for gpg verification as well. // FIXME: add support for gpg verification as well.
if err = update.Apply(resp.Body, if err = update.Apply(resp.Body,
-30
View File
@@ -34,7 +34,6 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings" "strings"
"sync"
"time" "time"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@@ -467,35 +466,6 @@ func isNetworkOrHostDown(err error) bool {
return false return false
} }
var b512pool = sync.Pool{
New: func() interface{} {
buf := make([]byte, 512)
return &buf
},
}
// CloseResponse close non nil response with any response Body.
// convenient wrapper to drain any remaining data on response body.
//
// Subsequently this allows golang http RoundTripper
// to re-use the same connection for future requests.
func CloseResponse(respBody io.ReadCloser) {
// Callers should close resp.Body when done reading from it.
// If resp.Body is not closed, the Client's underlying RoundTripper
// (typically Transport) may not be able to re-use a persistent TCP
// connection to the server for a subsequent "keep-alive" request.
if respBody != nil {
// Drain any remaining Body and then close the connection.
// Without this closing connection would disallow re-using
// the same connection for future uses.
// - http://stackoverflow.com/a/17961593/4465767
bufp := b512pool.Get().(*[]byte)
defer b512pool.Put(bufp)
io.CopyBuffer(ioutil.Discard, respBody, *bufp)
respBody.Close()
}
}
// Used for registering with rest handlers (have a look at registerStorageRESTHandlers for usage example) // Used for registering with rest handlers (have a look at registerStorageRESTHandlers for usage example)
// If it is passed ["aaaa", "bbbb"], it returns ["aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"] // If it is passed ["aaaa", "bbbb"], it returns ["aaaa", "{aaaa:.*}", "bbbb", "{bbbb:.*}"]
func restQueries(keys ...string) []string { func restQueries(keys ...string) []string {
+6 -6
View File
@@ -894,7 +894,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
// Extract incoming metadata if any. // Extract incoming metadata if any.
metadata, err := extractMetadata(context.Background(), r) metadata, err := extractMetadata(context.Background(), r)
if err != nil { if err != nil {
writeErrorResponse(w, ErrInternalError, r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
@@ -941,9 +941,9 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
pReader = NewPutObjReader(hashReader, nil, nil) pReader = NewPutObjReader(hashReader, nil, nil)
// get gateway encryption options // get gateway encryption options
var opts ObjectOptions var opts ObjectOptions
opts, err = putEncryptionOpts(ctx, r, bucket, object, nil) opts, err = putOpts(ctx, r, bucket, object, metadata)
if err != nil { if err != nil {
writeErrorResponseHeadersOnly(w, toAPIErrorCode(ctx, err)) writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
return return
} }
if objectAPI.IsEncryptionSupported() { if objectAPI.IsEncryptionSupported() {
@@ -952,13 +952,13 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
var objectEncryptionKey []byte var objectEncryptionKey []byte
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata) reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
info := ObjectInfo{Size: size} info := ObjectInfo{Size: size}
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", size) // do not try to verify encrypted content hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", size) // do not try to verify encrypted content
if err != nil { if err != nil {
writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) writeErrorResponse(w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
return return
} }
pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey) pReader = NewPutObjReader(rawReader, hashReader, objectEncryptionKey)
@@ -980,7 +980,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
if !hasServerSideEncryptionHeader(r.Header) && web.CacheAPI() != nil { if !hasServerSideEncryptionHeader(r.Header) && web.CacheAPI() != nil {
putObject = web.CacheAPI().PutObject putObject = web.CacheAPI().PutObject
} }
objInfo, err := putObject(context.Background(), bucket, object, pReader, metadata, opts) objInfo, err := putObject(context.Background(), bucket, object, pReader, opts)
if err != nil { if err != nil {
writeWebErrorResponse(w, err) writeWebErrorResponse(w, err)
return return
+9 -9
View File
@@ -349,7 +349,7 @@ func testDeleteBucketWebHandler(obj ObjectLayer, instanceType string, t TestErrH
for _, test := range testCases { for _, test := range testCases {
if test.initWithObject { if test.initWithObject {
data := bytes.NewBufferString("hello") data := bytes.NewBufferString("hello")
_, err = obj.PutObject(context.Background(), test.bucketName, "object", mustGetPutObjReader(t, data, int64(data.Len()), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), test.bucketName, "object", mustGetPutObjReader(t, data, int64(data.Len()), "", ""), opts)
// _, err = obj.PutObject(test.bucketName, "object", int64(data.Len()), data, nil, "") // _, err = obj.PutObject(test.bucketName, "object", int64(data.Len()), data, nil, "")
if err != nil { if err != nil {
t.Fatalf("could not put object to %s, %s", test.bucketName, err.Error()) t.Fatalf("could not put object to %s, %s", test.bucketName, err.Error())
@@ -485,7 +485,7 @@ func testListObjectsWebHandler(obj ObjectLayer, instanceType string, t TestErrHa
data := bytes.Repeat([]byte("a"), objectSize) data := bytes.Repeat([]byte("a"), objectSize)
metadata := map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"} metadata := map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), metadata, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), ObjectOptions{UserDefined: metadata})
if err != nil { if err != nil {
t.Fatalf("Was not able to upload an object, %v", err) t.Fatalf("Was not able to upload an object, %v", err)
@@ -589,14 +589,14 @@ func testRemoveObjectWebHandler(obj ObjectLayer, instanceType string, t TestErrH
data := bytes.Repeat([]byte("a"), objectSize) data := bytes.Repeat([]byte("a"), objectSize)
metadata := map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"} metadata := map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), metadata, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), ObjectOptions{UserDefined: metadata})
if err != nil { if err != nil {
t.Fatalf("Was not able to upload an object, %v", err) t.Fatalf("Was not able to upload an object, %v", err)
} }
objectName = "a/object" objectName = "a/object"
metadata = map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"} metadata = map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), metadata, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), ObjectOptions{UserDefined: metadata})
if err != nil { if err != nil {
t.Fatalf("Was not able to upload an object, %v", err) t.Fatalf("Was not able to upload an object, %v", err)
} }
@@ -987,7 +987,7 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl
content := []byte("temporary file's content") content := []byte("temporary file's content")
metadata := map[string]string{"etag": "01ce59706106fe5e02e7f55fffda7f34"} metadata := map[string]string{"etag": "01ce59706106fe5e02e7f55fffda7f34"}
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(content), int64(len(content)), metadata["etag"], ""), metadata, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(content), int64(len(content)), metadata["etag"], ""), ObjectOptions{UserDefined: metadata})
if err != nil { if err != nil {
t.Fatalf("Was not able to upload an object, %v", err) t.Fatalf("Was not able to upload an object, %v", err)
} }
@@ -1090,9 +1090,9 @@ func testWebHandlerDownloadZip(obj ObjectLayer, instanceType string, t TestErrHa
t.Fatalf("%s : %s", instanceType, err) t.Fatalf("%s : %s", instanceType, err)
} }
obj.PutObject(context.Background(), bucket, "a/one", mustGetPutObjReader(t, strings.NewReader(fileOne), int64(len(fileOne)), "", ""), nil, opts) obj.PutObject(context.Background(), bucket, "a/one", mustGetPutObjReader(t, strings.NewReader(fileOne), int64(len(fileOne)), "", ""), opts)
obj.PutObject(context.Background(), bucket, "a/b/two", mustGetPutObjReader(t, strings.NewReader(fileTwo), int64(len(fileTwo)), "", ""), nil, opts) obj.PutObject(context.Background(), bucket, "a/b/two", mustGetPutObjReader(t, strings.NewReader(fileTwo), int64(len(fileTwo)), "", ""), opts)
obj.PutObject(context.Background(), bucket, "a/c/three", mustGetPutObjReader(t, strings.NewReader(fileThree), int64(len(fileThree)), "", ""), nil, opts) obj.PutObject(context.Background(), bucket, "a/c/three", mustGetPutObjReader(t, strings.NewReader(fileThree), int64(len(fileThree)), "", ""), opts)
test := func(token string) (int, []byte) { test := func(token string) (int, []byte) {
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
@@ -1177,7 +1177,7 @@ func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrH
data := bytes.Repeat([]byte("a"), objectSize) data := bytes.Repeat([]byte("a"), objectSize)
metadata := map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"} metadata := map[string]string{"etag": "c9a34cfc85d982698c6ac89f76071abd"}
_, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), metadata, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), metadata["etag"], ""), ObjectOptions{UserDefined: metadata})
if err != nil { if err != nil {
t.Fatalf("Was not able to upload an object, %v", err) t.Fatalf("Was not able to upload an object, %v", err)
} }
+86 -41
View File
@@ -602,8 +602,8 @@ func (s *xlSets) GetObject(ctx context.Context, bucket, object string, startOffs
} }
// PutObject - writes an object to hashedSet based on the object name. // PutObject - writes an object to hashedSet based on the object name.
func (s *xlSets) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) { func (s *xlSets) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
return s.getHashedSet(object).PutObject(ctx, bucket, object, data, metadata, opts) return s.getHashedSet(object).PutObject(ctx, bucket, object, data, opts)
} }
// GetObjectInfo - reads object metadata from the hashedSet based on the object name. // GetObjectInfo - reads object metadata from the hashedSet based on the object name.
@@ -634,7 +634,8 @@ func (s *xlSets) CopyObject(ctx context.Context, srcBucket, srcObject, destBucke
} }
defer objectDWLock.Unlock() defer objectDWLock.Unlock()
} }
return destSet.putObject(ctx, destBucket, destObject, srcInfo.PutObjReader, srcInfo.UserDefined, dstOpts) putOpts := ObjectOptions{ServerSideEncryption: dstOpts.ServerSideEncryption, UserDefined: srcInfo.UserDefined}
return destSet.putObject(ctx, destBucket, destObject, srcInfo.PutObjReader, putOpts)
} }
// Returns function "listDir" of the type listDirFunc. // Returns function "listDir" of the type listDirFunc.
@@ -822,8 +823,8 @@ func (s *xlSets) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMa
} }
// Initiate a new multipart upload on a hashedSet based on object name. // Initiate a new multipart upload on a hashedSet based on object name.
func (s *xlSets) NewMultipartUpload(ctx context.Context, bucket, object string, metadata map[string]string, opts ObjectOptions) (uploadID string, err error) { func (s *xlSets) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (uploadID string, err error) {
return s.getHashedSet(object).NewMultipartUpload(ctx, bucket, object, metadata, opts) return s.getHashedSet(object).NewMultipartUpload(ctx, bucket, object, opts)
} }
// Copies a part of an object from source hashedSet to destination hashedSet. // Copies a part of an object from source hashedSet to destination hashedSet.
@@ -1007,6 +1008,65 @@ func (s *xlSets) ReloadFormat(ctx context.Context, dryRun bool) (err error) {
return nil return nil
} }
// If it is a single node XL and all disks are root disks, it is most likely a test setup, else it is a production setup.
// On a test setup we allow creation of format.json on root disks to help with dev/testing.
func isTestSetup(infos []DiskInfo, errs []error) bool {
if globalIsDistXL {
return false
}
rootDiskCount := 0
for i := range errs {
if errs[i] != nil {
// On error it is safer to assume that this is not a test setup.
return false
}
if infos[i].RootDisk {
rootDiskCount++
}
}
// It is a test setup if all disks are root disks.
return rootDiskCount == len(infos)
}
func getAllDiskInfos(storageDisks []StorageAPI) ([]DiskInfo, []error) {
infos := make([]DiskInfo, len(storageDisks))
errs := make([]error, len(storageDisks))
var wg sync.WaitGroup
for i := range storageDisks {
if storageDisks[i] == nil {
errs[i] = errDiskNotFound
continue
}
wg.Add(1)
go func(i int) {
defer wg.Done()
infos[i], errs[i] = storageDisks[i].DiskInfo()
}(i)
}
wg.Wait()
return infos, errs
}
// Mark root disks as down so as not to heal them.
func markRootDisksAsDown(storageDisks []StorageAPI) {
infos, errs := getAllDiskInfos(storageDisks)
if isTestSetup(infos, errs) {
// Allow healing of disks for test setups to help with testing.
return
}
for i := range storageDisks {
if errs[i] != nil {
storageDisks[i] = nil
continue
}
if infos[i].RootDisk {
// We should not heal on root disk. i.e in a situation where the minio-administrator has unmounted a
// defective drive we should not heal a path on the root disk.
storageDisks[i] = nil
}
}
}
// HealFormat - heals missing `format.json` on fresh unformatted disks. // HealFormat - heals missing `format.json` on fresh unformatted disks.
// TODO: In future support corrupted disks missing format.json but has erasure // TODO: In future support corrupted disks missing format.json but has erasure
// coded data in it. // coded data in it.
@@ -1029,17 +1089,7 @@ func (s *xlSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.HealRe
} }
}(storageDisks) }(storageDisks)
for i, disk := range storageDisks { markRootDisksAsDown(storageDisks)
info, err := disk.DiskInfo()
if err != nil {
storageDisks[i] = nil
}
if info.RootDisk {
// We should not heal on root disk. i.e in a situation where the minio-administrator has unmounted a
// defective drive we should not heal a path on the root disk.
storageDisks[i] = nil
}
}
formats, sErrs := loadFormatXLAll(storageDisks) formats, sErrs := loadFormatXLAll(storageDisks)
if err = checkFormatXLValues(formats); err != nil { if err = checkFormatXLValues(formats); err != nil {
@@ -1182,15 +1232,15 @@ func (s *xlSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.HealRe
} }
// HealBucket - heals inconsistent buckets and bucket metadata on all sets. // HealBucket - heals inconsistent buckets and bucket metadata on all sets.
func (s *xlSets) HealBucket(ctx context.Context, bucket string, dryRun bool) (results []madmin.HealResultItem, err error) { func (s *xlSets) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (result madmin.HealResultItem, err error) {
bucketLock := globalNSMutex.NewNSLock(bucket, "") bucketLock := globalNSMutex.NewNSLock(bucket, "")
if err := bucketLock.GetLock(globalHealingTimeout); err != nil { if err := bucketLock.GetLock(globalHealingTimeout); err != nil {
return nil, err return result, err
} }
defer bucketLock.Unlock() defer bucketLock.Unlock()
// Initialize heal result info // Initialize heal result info
res := madmin.HealResultItem{ result = madmin.HealResultItem{
Type: madmin.HealItemBucket, Type: madmin.HealItemBucket,
Bucket: bucket, Bucket: bucket,
DiskCount: s.setCount * s.drivesPerSet, DiskCount: s.setCount * s.drivesPerSet,
@@ -1198,25 +1248,22 @@ func (s *xlSets) HealBucket(ctx context.Context, bucket string, dryRun bool) (re
} }
for _, s := range s.sets { for _, s := range s.sets {
var setResults []madmin.HealResultItem var healResult madmin.HealResultItem
setResults, _ = s.HealBucket(ctx, bucket, dryRun) healResult, err = s.HealBucket(ctx, bucket, dryRun, remove)
for _, setResult := range setResults { if err != nil {
if setResult.Type == madmin.HealItemBucket { return result, err
for _, v := range setResult.Before.Drives {
res.Before.Drives = append(res.Before.Drives, v)
} }
for _, v := range setResult.After.Drives { for _, v := range healResult.Before.Drives {
res.After.Drives = append(res.After.Drives, v) result.Before.Drives = append(result.Before.Drives, v)
} }
continue for _, v := range healResult.After.Drives {
} result.After.Drives = append(result.After.Drives, v)
results = append(results, setResult)
} }
} }
for _, endpoint := range s.endpoints { for _, endpoint := range s.endpoints {
var foundBefore bool var foundBefore bool
for _, v := range res.Before.Drives { for _, v := range result.Before.Drives {
if endpoint.IsLocal { if endpoint.IsLocal {
if v.Endpoint == endpoint.Path { if v.Endpoint == endpoint.Path {
foundBefore = true foundBefore = true
@@ -1228,14 +1275,14 @@ func (s *xlSets) HealBucket(ctx context.Context, bucket string, dryRun bool) (re
} }
} }
if !foundBefore { if !foundBefore {
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{ result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
UUID: "", UUID: "",
Endpoint: endpoint.String(), Endpoint: endpoint.String(),
State: madmin.DriveStateOffline, State: madmin.DriveStateOffline,
}) })
} }
var foundAfter bool var foundAfter bool
for _, v := range res.After.Drives { for _, v := range result.After.Drives {
if endpoint.IsLocal { if endpoint.IsLocal {
if v.Endpoint == endpoint.Path { if v.Endpoint == endpoint.Path {
foundAfter = true foundAfter = true
@@ -1247,7 +1294,7 @@ func (s *xlSets) HealBucket(ctx context.Context, bucket string, dryRun bool) (re
} }
} }
if !foundAfter { if !foundAfter {
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{ result.After.Drives = append(result.After.Drives, madmin.HealDriveInfo{
UUID: "", UUID: "",
Endpoint: endpoint.String(), Endpoint: endpoint.String(),
State: madmin.DriveStateOffline, State: madmin.DriveStateOffline,
@@ -1256,19 +1303,17 @@ func (s *xlSets) HealBucket(ctx context.Context, bucket string, dryRun bool) (re
} }
// Check if we had quorum to write, if not return an appropriate error. // Check if we had quorum to write, if not return an appropriate error.
_, afterDriveOnline := res.GetOnlineCounts() _, afterDriveOnline := result.GetOnlineCounts()
if afterDriveOnline < ((s.setCount*s.drivesPerSet)/2)+1 { if afterDriveOnline < ((s.setCount*s.drivesPerSet)/2)+1 {
return nil, toObjectErr(errXLWriteQuorum, bucket) return result, toObjectErr(errXLWriteQuorum, bucket)
} }
results = append(results, res) return result, nil
return results, nil
} }
// HealObject - heals inconsistent object on a hashedSet based on object name. // HealObject - heals inconsistent object on a hashedSet based on object name.
func (s *xlSets) HealObject(ctx context.Context, bucket, object string, dryRun bool) (madmin.HealResultItem, error) { func (s *xlSets) HealObject(ctx context.Context, bucket, object string, dryRun, remove bool) (madmin.HealResultItem, error) {
return s.getHashedSet(object).HealObject(ctx, bucket, object, dryRun) return s.getHashedSet(object).HealObject(ctx, bucket, object, dryRun, remove)
} }
// Lists all buckets which need healing. // Lists all buckets which need healing.
+33 -7
View File
@@ -218,6 +218,30 @@ func (xl xlObjects) ListBuckets(ctx context.Context) ([]BucketInfo, error) {
return bucketInfos, nil return bucketInfos, nil
} }
// Dangling buckets should be handled appropriately, in this following situation
// we actually have quorum error to be `nil` but we have some disks where
// the bucket delete returned `errVolumeNotEmpty` but this is not correct
// can only happen if there are dangling objects in a bucket. Under such
// a situation we simply attempt a full delete of the bucket including
// the dangling objects. All of this happens under a lock and there
// is no way a user can create buckets and sneak in objects into namespace,
// so it is safer to do.
func deleteDanglingBucket(ctx context.Context, storageDisks []StorageAPI, dErrs []error, bucket string) {
for index, err := range dErrs {
if err == errVolumeNotEmpty {
// Attempt to delete bucket again.
if derr := storageDisks[index].DeleteVol(bucket); derr == errVolumeNotEmpty {
_ = cleanupDir(ctx, storageDisks[index], bucket, "")
_ = storageDisks[index].DeleteVol(bucket)
// Cleanup all the previously incomplete multiparts.
_ = cleanupDir(ctx, storageDisks[index], minioMetaMultipartBucket, bucket)
}
}
}
}
// DeleteBucket - deletes a bucket. // DeleteBucket - deletes a bucket.
func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string) error { func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string) error {
bucketLock := xl.nsMutex.NewNSLock(bucket, "") bucketLock := xl.nsMutex.NewNSLock(bucket, "")
@@ -231,7 +255,8 @@ func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string) error {
var dErrs = make([]error, len(xl.getDisks())) var dErrs = make([]error, len(xl.getDisks()))
// Remove a volume entry on all underlying storage disks. // Remove a volume entry on all underlying storage disks.
for index, disk := range xl.getDisks() { storageDisks := xl.getDisks()
for index, disk := range storageDisks {
if disk == nil { if disk == nil {
dErrs[index] = errDiskNotFound dErrs[index] = errDiskNotFound
continue continue
@@ -242,18 +267,14 @@ func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string) error {
defer wg.Done() defer wg.Done()
// Attempt to delete bucket. // Attempt to delete bucket.
err := disk.DeleteVol(bucket) err := disk.DeleteVol(bucket)
if err != nil { if err != nil {
dErrs[index] = err dErrs[index] = err
return return
} }
// Cleanup all the previously incomplete multiparts. // Cleanup all the previously incomplete multiparts.
err = cleanupDir(ctx, disk, minioMetaMultipartBucket, bucket) err = cleanupDir(ctx, disk, minioMetaMultipartBucket, bucket)
if err != nil && err != errVolumeNotFound {
if err != nil {
if err == errVolumeNotFound {
return
}
dErrs[index] = err dErrs[index] = err
} }
}(index, disk) }(index, disk)
@@ -271,6 +292,11 @@ func (xl xlObjects) DeleteBucket(ctx context.Context, bucket string) error {
return toObjectErr(err, bucket) return toObjectErr(err, bucket)
} }
// If we reduce quorum to nil, means we have deleted buckets properly
// on some servers in quorum, we should look for volumeNotEmpty errors
// and delete those buckets as well.
deleteDanglingBucket(ctx, storageDisks, dErrs, bucket)
return nil return nil
} }
+32 -11
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"context" "context"
"path" "path"
"sync"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
) )
@@ -75,26 +76,46 @@ func (xl xlObjects) isObject(bucket, prefix string) (ok bool) {
// isObjectDir returns if the specified path represents an empty directory. // isObjectDir returns if the specified path represents an empty directory.
func (xl xlObjects) isObjectDir(bucket, prefix string) (ok bool) { func (xl xlObjects) isObjectDir(bucket, prefix string) (ok bool) {
for _, disk := range xl.getLoadBalancedDisks() { var objectDirs = make([]bool, len(xl.getDisks()))
var errs = make([]error, len(xl.getDisks()))
var wg sync.WaitGroup
for index, disk := range xl.getDisks() {
if disk == nil { if disk == nil {
continue continue
} }
wg.Add(1)
go func(index int, disk StorageAPI) {
defer wg.Done()
// Check if 'prefix' is an object on this 'disk', else continue the check the next disk // Check if 'prefix' is an object on this 'disk', else continue the check the next disk
ctnts, err := disk.ListDir(bucket, prefix, 1) entries, err := disk.ListDir(bucket, prefix, 1)
if err == nil { if err != nil {
if len(ctnts) == 0 { errs[index] = err
return true return
} }
return false objectDirs[index] = len(entries) == 0
} }(index, disk)
// Ignore for file not found, disk not found or faulty disk.
if IsErrIgnored(err, xlTreeWalkIgnoredErrs...) {
continue
} }
wg.Wait()
readQuorum := len(xl.getDisks()) / 2
err := reduceReadQuorumErrs(context.Background(), errs, xlTreeWalkIgnoredErrs, readQuorum)
if err != nil {
reqInfo := &logger.ReqInfo{BucketName: bucket} reqInfo := &logger.ReqInfo{BucketName: bucket}
reqInfo.AppendTags("prefix", prefix) reqInfo.AppendTags("prefix", prefix)
ctx := logger.SetReqInfo(context.Background(), reqInfo) ctx := logger.SetReqInfo(context.Background(), reqInfo)
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
} // Exhausted all disks - return false.
return false return false
} }
for _, ok := range objectDirs {
if !ok {
// We perhaps found some entries in a prefix.
return false
}
}
// Return true if and only if the all directories are empty.
return true
}
+1 -1
View File
@@ -43,7 +43,7 @@ func TestXLParentDirIsObject(t *testing.T) {
} }
objectContent := "12345" objectContent := "12345"
objInfo, err := obj.PutObject(context.Background(), bucketName, objectName, objInfo, err := obj.PutObject(context.Background(), bucketName, objectName,
mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), nil, ObjectOptions{}) mustGetPutObjReader(t, bytes.NewReader([]byte(objectContent)), int64(len(objectContent)), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+2 -2
View File
@@ -179,7 +179,7 @@ func TestListOnlineDisks(t *testing.T) {
t.Fatalf("Failed to make a bucket %v", err) t.Fatalf("Failed to make a bucket %v", err)
} }
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
@@ -273,7 +273,7 @@ func TestDisksWithAllParts(t *testing.T) {
t.Fatalf("Failed to make a bucket %v", err) t.Fatalf("Failed to make a bucket %v", err)
} }
_, err = obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(ctx, bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), int64(len(data)), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("Failed to putObject %v", err) t.Fatalf("Failed to putObject %v", err)
} }
+50 -60
View File
@@ -20,7 +20,6 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"path"
"sync" "sync"
"time" "time"
@@ -41,8 +40,8 @@ func (xl xlObjects) HealFormat(ctx context.Context, dryRun bool) (madmin.HealRes
// Heals a bucket if it doesn't exist on one of the disks, additionally // Heals a bucket if it doesn't exist on one of the disks, additionally
// also heals the missing entries for bucket metadata files // also heals the missing entries for bucket metadata files
// `policy.json, notification.xml, listeners.json`. // `policy.json, notification.xml, listeners.json`.
func (xl xlObjects) HealBucket(ctx context.Context, bucket string, dryRun bool) ( func (xl xlObjects) HealBucket(ctx context.Context, bucket string, dryRun, remove bool) (
results []madmin.HealResultItem, err error) { result madmin.HealResultItem, err error) {
storageDisks := xl.getDisks() storageDisks := xl.getDisks()
@@ -50,17 +49,7 @@ func (xl xlObjects) HealBucket(ctx context.Context, bucket string, dryRun bool)
writeQuorum := len(storageDisks)/2 + 1 writeQuorum := len(storageDisks)/2 + 1
// Heal bucket. // Heal bucket.
var result madmin.HealResultItem return healBucket(ctx, storageDisks, bucket, writeQuorum, dryRun)
result, err = healBucket(ctx, storageDisks, bucket, writeQuorum, dryRun)
if err != nil {
return nil, err
}
results = append(results, result)
// Proceed to heal bucket metadata.
metaResults, err := healBucketMetadata(xl, bucket, dryRun)
results = append(results, metaResults...)
return results, err
} }
// Heal bucket - create buckets on disks where it does not exist. // Heal bucket - create buckets on disks where it does not exist.
@@ -157,51 +146,6 @@ func healBucket(ctx context.Context, storageDisks []StorageAPI, bucket string, w
return res, reducedErr return res, reducedErr
} }
// Heals all the metadata associated for a given bucket, this function
// heals `policy.json`, `notification.xml` and `listeners.json`.
func healBucketMetadata(xl xlObjects, bucket string, dryRun bool) (
results []madmin.HealResultItem, err error) {
healBucketMetaFn := func(metaPath string) error {
reqInfo := &logger.ReqInfo{BucketName: bucket}
ctx := logger.SetReqInfo(context.Background(), reqInfo)
result, healErr := xl.HealObject(ctx, minioMetaBucket, metaPath, dryRun)
// If object is not found, skip the file.
if isErrObjectNotFound(healErr) {
return nil
}
if healErr != nil {
return healErr
}
result.Type = madmin.HealItemBucketMetadata
results = append(results, result)
return nil
}
// Heal `policy.json` for missing entries, ignores if
// `policy.json` is not found.
policyPath := pathJoin(bucketConfigPrefix, bucket, bucketPolicyConfig)
err = healBucketMetaFn(policyPath)
if err != nil {
return results, err
}
// Heal `notification.xml` for missing entries, ignores if
// `notification.xml` is not found.
nConfigPath := path.Join(bucketConfigPrefix, bucket,
bucketNotificationConfig)
err = healBucketMetaFn(nConfigPath)
if err != nil {
return results, err
}
// Heal `listeners.json` for missing entries, ignores if
// `listeners.json` is not found.
lConfigPath := path.Join(bucketConfigPrefix, bucket, bucketListenerConfig)
err = healBucketMetaFn(lConfigPath)
return results, err
}
// listAllBuckets lists all buckets from all disks. It also // listAllBuckets lists all buckets from all disks. It also
// returns the occurrence of each buckets in all disks // returns the occurrence of each buckets in all disks
func listAllBuckets(storageDisks []StorageAPI) (buckets map[string]VolInfo, func listAllBuckets(storageDisks []StorageAPI) (buckets map[string]VolInfo,
@@ -639,12 +583,45 @@ func defaultHealResult(storageDisks []StorageAPI, errs []error, bucket, object s
return result return result
} }
// Object is considered dangling/corrupted if any only
// if total disks - a combination of corrupted and missing
// files is lesser than number of data blocks.
func (xl xlObjects) isObjectDangling(metaArr []xlMetaV1, errs []error) (validMeta xlMetaV1, ok bool) {
// We can consider an object data not reliable
// when xl.json is not found in read quorum disks.
// or when xl.json is not readable in read quorum disks.
var notFoundXLJSON, corruptedXLJSON int
for _, readErr := range errs {
if readErr == errFileNotFound {
notFoundXLJSON++
} else if readErr == errCorruptedFormat {
corruptedXLJSON++
}
}
for _, m := range metaArr {
if !m.IsValid() {
continue
}
validMeta = m
break
}
// We couldn't find any valid meta we are indeed corrupted, return true right away.
if validMeta.Erasure.DataBlocks == 0 {
return validMeta, true
}
// We have valid meta, now verify if we have enough files with data blocks.
return validMeta, (len(xl.getDisks()) - corruptedXLJSON - notFoundXLJSON) < validMeta.Erasure.DataBlocks
}
// HealObject - heal the given object. // HealObject - heal the given object.
// //
// FIXME: If an object object was deleted and one disk was down, // FIXME: If an object object was deleted and one disk was down,
// and later the disk comes back up again, heal on the object // and later the disk comes back up again, heal on the object
// should delete it. // should delete it.
func (xl xlObjects) HealObject(ctx context.Context, bucket, object string, dryRun bool) (hr madmin.HealResultItem, err error) { func (xl xlObjects) HealObject(ctx context.Context, bucket, object string, dryRun bool, remove bool) (hr madmin.HealResultItem, err error) {
// Create context that also contains information about the object and bucket. // Create context that also contains information about the object and bucket.
// The top level handler might not have this information. // The top level handler might not have this information.
reqInfo := logger.GetReqInfo(ctx) reqInfo := logger.GetReqInfo(ctx)
@@ -667,6 +644,19 @@ func (xl xlObjects) HealObject(ctx context.Context, bucket, object string, dryRu
// Read metadata files from all the disks // Read metadata files from all the disks
partsMetadata, errs := readAllXLMetadata(healCtx, storageDisks, bucket, object) partsMetadata, errs := readAllXLMetadata(healCtx, storageDisks, bucket, object)
// Check if the object is dangling, if yes and user requested
// remove we simply delete it from namespace.
if m, ok := xl.isObjectDangling(partsMetadata, errs); ok {
writeQuorum := m.Erasure.DataBlocks + 1
if m.Erasure.DataBlocks == 0 {
writeQuorum = len(xl.getDisks())/2 + 1
}
if !dryRun && remove {
err = xl.deleteObject(healCtx, bucket, object, writeQuorum, false)
}
return defaultHealResult(storageDisks, errs, bucket, object), err
}
latestXLMeta, err := getLatestXLMeta(healCtx, partsMetadata, errs) latestXLMeta, err := getLatestXLMeta(healCtx, partsMetadata, errs)
if err != nil { if err != nil {
return defaultHealResult(storageDisks, errs, bucket, object), toObjectErr(err, bucket, object) return defaultHealResult(storageDisks, errs, bucket, object), toObjectErr(err, bucket, object)
+3 -3
View File
@@ -84,7 +84,7 @@ func TestHealObjectXL(t *testing.T) {
// Create an object with multiple parts uploaded in decreasing // Create an object with multiple parts uploaded in decreasing
// part number. // part number.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucket, object, opts)
if err != nil { if err != nil {
t.Fatalf("Failed to create a multipart upload - %v", err) t.Fatalf("Failed to create a multipart upload - %v", err)
} }
@@ -114,7 +114,7 @@ func TestHealObjectXL(t *testing.T) {
t.Fatalf("Failed to delete a file - %v", err) t.Fatalf("Failed to delete a file - %v", err)
} }
_, err = obj.HealObject(context.Background(), bucket, object, false) _, err = obj.HealObject(context.Background(), bucket, object, false, false)
if err != nil { if err != nil {
t.Fatalf("Failed to heal object - %v", err) t.Fatalf("Failed to heal object - %v", err)
} }
@@ -130,7 +130,7 @@ func TestHealObjectXL(t *testing.T) {
} }
// Try healing now, expect to receive errDiskNotFound. // Try healing now, expect to receive errDiskNotFound.
_, err = obj.HealObject(context.Background(), bucket, object, false) _, err = obj.HealObject(context.Background(), bucket, object, false, false)
// since majority of xl.jsons are not available, object quorum can't be read properly and error will be errXLReadQuorum // since majority of xl.jsons are not available, object quorum can't be read properly and error will be errXLReadQuorum
if _, ok := err.(InsufficientReadQuorum); !ok { if _, ok := err.(InsufficientReadQuorum); !ok {
t.Errorf("Expected %v but received %v", InsufficientReadQuorum{}, err) t.Errorf("Expected %v but received %v", InsufficientReadQuorum{}, err)
+2 -2
View File
@@ -69,7 +69,7 @@ func testXLReadStat(obj ObjectLayer, instanceType string, disks []string, t *tes
// iterate through the above set of inputs and upkoad the object. // iterate through the above set of inputs and upkoad the object.
for i, input := range putObjectInputs { for i, input := range putObjectInputs {
// uploading the object. // uploading the object.
_, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), input.metaData, ObjectOptions{}) _, err = obj.PutObject(context.Background(), input.bucketName, input.objectName, mustGetPutObjReader(t, bytes.NewBuffer(input.textData), input.contentLength, input.metaData["etag"], ""), ObjectOptions{UserDefined: input.metaData})
// if object upload fails stop the test. // if object upload fails stop the test.
if err != nil { if err != nil {
t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err) t.Fatalf("Put Object case %d: Error uploading object: <ERROR> %v", i+1, err)
@@ -122,7 +122,7 @@ func testXLReadMetaParts(obj ObjectLayer, instanceType string, disks []string, t
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
} }
// Initiate Multipart Upload on the above created bucket. // Initiate Multipart Upload on the above created bucket.
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketNames[0], objectNames[0], opts)
if err != nil { if err != nil {
// Failed to create NewMultipartUpload, abort. // Failed to create NewMultipartUpload, abort.
t.Fatalf("%s : %s", instanceType, err.Error()) t.Fatalf("%s : %s", instanceType, err.Error())
+27 -5
View File
@@ -241,15 +241,15 @@ func (xl xlObjects) newMultipartUpload(ctx context.Context, bucket string, objec
// subsequent request each UUID is unique. // subsequent request each UUID is unique.
// //
// Implements S3 compatible initiate multipart API. // Implements S3 compatible initiate multipart API.
func (xl xlObjects) NewMultipartUpload(ctx context.Context, bucket, object string, meta map[string]string, opts ObjectOptions) (string, error) { func (xl xlObjects) NewMultipartUpload(ctx context.Context, bucket, object string, opts ObjectOptions) (string, error) {
if err := checkNewMultipartArgs(ctx, bucket, object, xl); err != nil { if err := checkNewMultipartArgs(ctx, bucket, object, xl); err != nil {
return "", err return "", err
} }
// No metadata is set, allocate a new one. // No metadata is set, allocate a new one.
if meta == nil { if opts.UserDefined == nil {
meta = make(map[string]string) opts.UserDefined = make(map[string]string)
} }
return xl.newMultipartUpload(ctx, bucket, object, meta) return xl.newMultipartUpload(ctx, bucket, object, opts.UserDefined)
} }
// CopyObjectPart - reads incoming stream and internally erasure codes // CopyObjectPart - reads incoming stream and internally erasure codes
@@ -505,11 +505,33 @@ func (xl xlObjects) ListObjectParts(ctx context.Context, bucket, object, uploadI
uploadIDPath := xl.getUploadIDDir(bucket, object, uploadID) uploadIDPath := xl.getUploadIDDir(bucket, object, uploadID)
xlParts, xlMeta, err := xl.readXLMetaParts(ctx, minioMetaMultipartBucket, uploadIDPath) storageDisks := xl.getDisks()
// Read metadata associated with the object from all disks.
partsMetadata, errs := readAllXLMetadata(ctx, storageDisks, minioMetaMultipartBucket, uploadIDPath)
// get Quorum for this object
_, writeQuorum, err := objectQuorumFromMeta(ctx, xl, partsMetadata, errs)
if err != nil { if err != nil {
return result, toObjectErr(err, minioMetaMultipartBucket, uploadIDPath) return result, toObjectErr(err, minioMetaMultipartBucket, uploadIDPath)
} }
reducedErr := reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
if reducedErr == errXLWriteQuorum {
return result, toObjectErr(err, minioMetaMultipartBucket, uploadIDPath)
}
_, modTime := listOnlineDisks(storageDisks, partsMetadata, errs)
// Pick one from the first valid metadata.
xlValidMeta, err := pickValidXLMeta(ctx, partsMetadata, modTime, writeQuorum)
if err != nil {
return result, err
}
var xlMeta = xlValidMeta.Meta
var xlParts = xlValidMeta.Parts
// Populate the result stub. // Populate the result stub.
result.Bucket = bucket result.Bucket = bucket
result.Object = object result.Object = object
+1 -1
View File
@@ -43,7 +43,7 @@ func TestXLCleanupStaleMultipartUploads(t *testing.T) {
var opts ObjectOptions var opts ObjectOptions
obj.MakeBucketWithLocation(context.Background(), bucketName, "") obj.MakeBucketWithLocation(context.Background(), bucketName, "")
uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, nil, opts) uploadID, err := obj.NewMultipartUpload(context.Background(), bucketName, objectName, opts)
if err != nil { if err != nil {
t.Fatal("Unexpected err: ", err) t.Fatal("Unexpected err: ", err)
} }
+14 -126
View File
@@ -23,7 +23,6 @@ import (
"net/http" "net/http"
"path" "path"
"strconv" "strconv"
"strings"
"sync" "sync"
"github.com/minio/minio/cmd/logger" "github.com/minio/minio/cmd/logger"
@@ -132,8 +131,8 @@ func (xl xlObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBuc
logger.LogIf(ctx, err) logger.LogIf(ctx, err)
return oi, toObjectErr(err, dstBucket, dstObject) return oi, toObjectErr(err, dstBucket, dstObject)
} }
putOpts := ObjectOptions{UserDefined: srcInfo.UserDefined, ServerSideEncryption: dstOpts.ServerSideEncryption}
objInfo, err := xl.putObject(ctx, dstBucket, dstObject, NewPutObjReader(hashReader, nil, nil), srcInfo.UserDefined, dstOpts) objInfo, err := xl.putObject(ctx, dstBucket, dstObject, NewPutObjReader(hashReader, nil, nil), putOpts)
if err != nil { if err != nil {
return oi, toObjectErr(err, dstBucket, dstObject) return oi, toObjectErr(err, dstBucket, dstObject)
} }
@@ -430,100 +429,6 @@ func (xl xlObjects) GetObjectInfo(ctx context.Context, bucket, object string, op
return info, nil return info, nil
} }
func (xl xlObjects) isObjectCorrupted(metaArr []xlMetaV1, errs []error) (validMeta xlMetaV1, ok bool) {
// We can consider an object data not reliable
// when xl.json is not found in read quorum disks.
var notFoundXLJSON, corruptedXLJSON int
for _, readErr := range errs {
if readErr == errFileNotFound {
notFoundXLJSON++
}
}
for _, readErr := range errs {
if readErr == errCorruptedFormat {
corruptedXLJSON++
}
}
for _, m := range metaArr {
if !m.IsValid() {
continue
}
validMeta = m
break
}
// Return if the object is indeed corrupted.
return validMeta, len(xl.getDisks())-notFoundXLJSON < validMeta.Erasure.DataBlocks || len(xl.getDisks()) == corruptedXLJSON
}
const xlCorruptedSuffix = ".CORRUPTED"
// Renames the corrupted object and makes it visible.
func (xl xlObjects) renameCorruptedObject(ctx context.Context, bucket, object string, validMeta xlMetaV1, disks []StorageAPI, errs []error) {
// if errs returned are corrupted
if validMeta.Erasure.DataBlocks == 0 {
validMeta = newXLMetaV1(object, len(disks)/2, len(disks)/2)
}
writeQuorum := validMeta.Erasure.DataBlocks + 1
// Move all existing objects into corrupted suffix.
oldObj := mustGetUUID()
rename(ctx, disks, bucket, object, minioMetaTmpBucket, oldObj, true, writeQuorum, []error{errFileNotFound})
// Delete temporary object in the event of failure.
// If PutObject succeeded there would be no temporary
// object to delete.
defer xl.deleteObject(ctx, minioMetaTmpBucket, oldObj, writeQuorum, false)
tempObj := mustGetUUID()
// Get all the disks which do not have the file.
var cdisks = make([]StorageAPI, len(disks))
for i, merr := range errs {
if merr == errFileNotFound || merr == errCorruptedFormat {
cdisks[i] = disks[i]
}
}
for _, disk := range cdisks {
if disk == nil {
continue
}
// Write empty part file on missing disks.
disk.AppendFile(minioMetaTmpBucket, pathJoin(tempObj, "part.1"), []byte{})
// Write algorithm hash for empty part file.
var algorithm = DefaultBitrotAlgorithm
h := algorithm.New()
h.Write([]byte{})
// Update the checksums and part info.
validMeta.Erasure.Checksums = []ChecksumInfo{
{
Name: "part.1",
Algorithm: algorithm,
Hash: h.Sum(nil),
},
}
validMeta.Parts = []ObjectPartInfo{
{
Number: 1,
Name: "part.1",
},
}
// Write the `xl.json` with the newly calculated metadata.
writeXLMetadata(ctx, disk, minioMetaTmpBucket, tempObj, validMeta)
}
// Finally rename all the parts into their respective locations.
rename(ctx, cdisks, minioMetaTmpBucket, tempObj, bucket, object+xlCorruptedSuffix, true, writeQuorum, []error{errFileNotFound})
}
// getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo. // getObjectInfo - wrapper for reading object metadata and constructs ObjectInfo.
func (xl xlObjects) getObjectInfo(ctx context.Context, bucket, object string) (objInfo ObjectInfo, err error) { func (xl xlObjects) getObjectInfo(ctx context.Context, bucket, object string) (objInfo ObjectInfo, err error) {
disks := xl.getDisks() disks := xl.getDisks()
@@ -531,28 +436,11 @@ func (xl xlObjects) getObjectInfo(ctx context.Context, bucket, object string) (o
// Read metadata associated with the object from all disks. // Read metadata associated with the object from all disks.
metaArr, errs := readAllXLMetadata(ctx, disks, bucket, object) metaArr, errs := readAllXLMetadata(ctx, disks, bucket, object)
var readQuorum int readQuorum, _, err := objectQuorumFromMeta(ctx, xl, metaArr, errs)
// Having read quorum means we have xl.json in at least N/2 disks.
if !strings.HasSuffix(object, xlCorruptedSuffix) {
if validMeta, ok := xl.isObjectCorrupted(metaArr, errs); ok {
xl.renameCorruptedObject(ctx, bucket, object, validMeta, disks, errs)
// Return err file not found since we renamed now the corrupted object
return objInfo, errFileNotFound
}
// Not a corrupted object, attempt to get readquorum properly.
readQuorum, _, err = objectQuorumFromMeta(ctx, xl, metaArr, errs)
if err != nil { if err != nil {
return objInfo, err return objInfo, err
} }
} else {
// If this is a corrupted object, change read quorum to N/2 disks
// so it will be visible to users, so they can delete it.
readQuorum = len(xl.getDisks()) / 2
}
// List all the file commit ids from parts metadata. // List all the file commit ids from parts metadata.
modTimes := listObjectModtimes(metaArr, errs) modTimes := listObjectModtimes(metaArr, errs)
@@ -643,7 +531,7 @@ func rename(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry, dstBuc
// until EOF, erasure codes the data across all disk and additionally // until EOF, erasure codes the data across all disk and additionally
// writes `xl.json` which carries the necessary metadata for future // writes `xl.json` which carries the necessary metadata for future
// object operations. // object operations.
func (xl xlObjects) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) { func (xl xlObjects) PutObject(ctx context.Context, bucket string, object string, data *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
// Validate put object input args. // Validate put object input args.
if err = checkPutObjectArgs(ctx, bucket, object, xl, data.Size()); err != nil { if err = checkPutObjectArgs(ctx, bucket, object, xl, data.Size()); err != nil {
return ObjectInfo{}, err return ObjectInfo{}, err
@@ -655,22 +543,22 @@ func (xl xlObjects) PutObject(ctx context.Context, bucket string, object string,
return objInfo, err return objInfo, err
} }
defer objectLock.Unlock() defer objectLock.Unlock()
return xl.putObject(ctx, bucket, object, data, metadata, opts) return xl.putObject(ctx, bucket, object, data, opts)
} }
// putObject wrapper for xl PutObject // putObject wrapper for xl PutObject
func (xl xlObjects) putObject(ctx context.Context, bucket string, object string, r *PutObjReader, metadata map[string]string, opts ObjectOptions) (objInfo ObjectInfo, err error) { func (xl xlObjects) putObject(ctx context.Context, bucket string, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
data := r.Reader data := r.Reader
uniqueID := mustGetUUID() uniqueID := mustGetUUID()
tempObj := uniqueID tempObj := uniqueID
// No metadata is set, allocate a new one. // No metadata is set, allocate a new one.
if metadata == nil { if opts.UserDefined == nil {
metadata = make(map[string]string) opts.UserDefined = make(map[string]string)
} }
// Get parity and data drive count based on storage class metadata // Get parity and data drive count based on storage class metadata
dataDrives, parityDrives := getRedundancyCount(metadata[amzStorageClass], len(xl.getDisks())) dataDrives, parityDrives := getRedundancyCount(opts.UserDefined[amzStorageClass], len(xl.getDisks()))
// we now know the number of blocks this object needs for data and parity. // we now know the number of blocks this object needs for data and parity.
// writeQuorum is dataBlocks + 1 // writeQuorum is dataBlocks + 1
@@ -702,7 +590,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string,
return ObjectInfo{}, toObjectErr(err, bucket, object) return ObjectInfo{}, toObjectErr(err, bucket, object)
} }
return dirObjectInfo(bucket, object, data.Size(), metadata), nil return dirObjectInfo(bucket, object, data.Size(), opts.UserDefined), nil
} }
// Validate put object input args. // Validate put object input args.
@@ -845,11 +733,11 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string,
// Save additional erasureMetadata. // Save additional erasureMetadata.
modTime := UTCNow() modTime := UTCNow()
metadata["etag"] = r.MD5CurrentHexString() opts.UserDefined["etag"] = r.MD5CurrentHexString()
// Guess content-type from the extension if possible. // Guess content-type from the extension if possible.
if metadata["content-type"] == "" { if opts.UserDefined["content-type"] == "" {
metadata["content-type"] = mimedb.TypeByExtension(path.Ext(object)) opts.UserDefined["content-type"] = mimedb.TypeByExtension(path.Ext(object))
} }
if xl.isObject(bucket, object) { if xl.isObject(bucket, object) {
@@ -876,7 +764,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string,
// Fill all the necessary metadata. // Fill all the necessary metadata.
// Update `xl.json` content on each disks. // Update `xl.json` content on each disks.
for index := range partsMetadata { for index := range partsMetadata {
partsMetadata[index].Meta = metadata partsMetadata[index].Meta = opts.UserDefined
partsMetadata[index].Stat.Size = sizeWritten partsMetadata[index].Stat.Size = sizeWritten
partsMetadata[index].Stat.ModTime = modTime partsMetadata[index].Stat.ModTime = modTime
} }
+16 -11
View File
@@ -49,7 +49,7 @@ func TestRepeatPutObjectPart(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
uploadID, err := objLayer.NewMultipartUpload(context.Background(), "bucket1", "mpartObj1", nil, opts) uploadID, err := objLayer.NewMultipartUpload(context.Background(), "bucket1", "mpartObj1", opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -94,7 +94,7 @@ func TestXLDeleteObjectBasic(t *testing.T) {
} }
// Create object "dir/obj" under bucket "bucket" for Test 7 to pass // Create object "dir/obj" under bucket "bucket" for Test 7 to pass
_, err = xl.PutObject(context.Background(), "bucket", "dir/obj", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, ObjectOptions{}) _, err = xl.PutObject(context.Background(), "bucket", "dir/obj", mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatalf("XL Object upload failed: <ERROR> %s", err) t.Fatalf("XL Object upload failed: <ERROR> %s", err)
} }
@@ -131,7 +131,7 @@ func TestXLDeleteObjectDiskNotFound(t *testing.T) {
object := "object" object := "object"
opts := ObjectOptions{} opts := ObjectOptions{}
// Create object "obj" under bucket "bucket". // Create object "obj" under bucket "bucket".
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -146,7 +146,7 @@ func TestXLDeleteObjectDiskNotFound(t *testing.T) {
} }
// Create "obj" under "bucket". // Create "obj" under "bucket".
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -182,7 +182,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
object := "object" object := "object"
opts := ObjectOptions{} opts := ObjectOptions{}
// Create "object" under "bucket". // Create "object" under "bucket".
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -233,7 +233,7 @@ func TestPutObjectNoQuorum(t *testing.T) {
object := "object" object := "object"
opts := ObjectOptions{} opts := ObjectOptions{}
// Create "object" under "bucket". // Create "object" under "bucket".
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), opts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -256,7 +256,7 @@ func TestPutObjectNoQuorum(t *testing.T) {
} }
} }
// Upload new content to same object "object" // Upload new content to same object "object"
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), nil, opts) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), opts)
if err != toObjectErr(errXLWriteQuorum, bucket, object) { if err != toObjectErr(errXLWriteQuorum, bucket, object) {
t.Errorf("Expected putObject to fail with %v, but failed with %v", toObjectErr(errXLWriteQuorum, bucket, object), err) t.Errorf("Expected putObject to fail with %v, but failed with %v", toObjectErr(errXLWriteQuorum, bucket, object), err)
} }
@@ -290,7 +290,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), length, "", ""), nil, ObjectOptions{}) _, err = obj.PutObject(context.Background(), bucket, object, mustGetPutObjReader(t, bytes.NewReader(data), length, "", ""), ObjectOptions{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -308,7 +308,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = xl.HealObject(context.Background(), bucket, object, false) _, err = xl.HealObject(context.Background(), bucket, object, false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -323,6 +323,11 @@ func TestHealing(t *testing.T) {
t.Fatal("HealObject failed") t.Fatal("HealObject failed")
} }
err = os.RemoveAll(path.Join(fsDirs[0], bucket, object, "xl.json"))
if err != nil {
t.Fatal(err)
}
// Write xl.json with different modtime to simulate the case where a disk had // Write xl.json with different modtime to simulate the case where a disk had
// gone down when an object was replaced by a new object. // gone down when an object was replaced by a new object.
xlMetaOutDated := xlMetaPreHeal xlMetaOutDated := xlMetaPreHeal
@@ -332,7 +337,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, err = xl.HealObject(context.Background(), bucket, object, false) _, err = xl.HealObject(context.Background(), bucket, object, false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -354,7 +359,7 @@ func TestHealing(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// This would create the bucket. // This would create the bucket.
_, err = xl.HealBucket(context.Background(), bucket, false) _, err = xl.HealBucket(context.Background(), bucket, false, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+1 -1
View File
@@ -970,7 +970,7 @@ We will enable bucket event notification to trigger whenever a JPEG image is upl
``` ```
mc mb myminio/images mc mb myminio/images
mc mb myminio/images-thumbnail mc mb myminio/images-thumbnail
mc event add myminio/images arn:minio:sqs::1:webhook --events put --suffix .jpg mc event add myminio/images arn:minio:sqs::1:webhook --event put --suffix .jpg
``` ```
Check if event notification is successfully configured by Check if event notification is successfully configured by
+11
View File
@@ -167,6 +167,17 @@ export MINIO_DRIVE_SYNC=on
minio server /data minio server /data
``` ```
### HTTP Trace
By default, Minio disables the feature to log HTTP trace. You may enable this feature by setting `MINIO_HTTP_TRACE` environment variable.
Example:
```sh
export MINIO_HTTP_TRACE=/var/log/minio.log
minio server /data
```
## Explore Further ## Explore Further
* [Minio Quickstart Guide](https://docs.minio.io/docs/minio-quickstart-guide) * [Minio Quickstart Guide](https://docs.minio.io/docs/minio-quickstart-guide)
+2
View File
@@ -5,3 +5,5 @@ Minio Gateway adds Amazon S3 compatibility to third party cloud storage provider
- [S3](https://github.com/minio/minio/blob/master/docs/gateway/s3.md) - [S3](https://github.com/minio/minio/blob/master/docs/gateway/s3.md)
- [Google Cloud Storage](https://github.com/minio/minio/blob/master/docs/gateway/gcs.md) - [Google Cloud Storage](https://github.com/minio/minio/blob/master/docs/gateway/gcs.md)
- [Alibaba Cloud Storage](https://github.com/minio/minio/blob/master/docs/gateway/oss.md) - [Alibaba Cloud Storage](https://github.com/minio/minio/blob/master/docs/gateway/oss.md)
- [Backblaze B2](https://github.com/minio/minio/blob/master/docs/gateway/b2.md)
+58
View File
@@ -0,0 +1,58 @@
# Minio B2 Gateway [![Slack](https://slack.minio.io/slack?type=svg)](https://slack.minio.io)
Minio Gateway adds Amazon S3 compatibility to Backblaze B2 Cloud Storage.
## Run Minio Gateway for Backblaze B2 Cloud Storage
Please follow this [guide](https://www.backblaze.com/b2/docs/quick_account.html) to create an account on backblaze.com to obtain your access credentials for B2 Cloud storage.
### Using Docker
```
docker run -p 9000:9000 --name b2-s3 \
-e "MINIO_ACCESS_KEY=b2_account_id" \
-e "MINIO_SECRET_KEY=b2_application_key" \
minio/minio gateway b2
```
### Using Binary
```
export MINIO_ACCESS_KEY=b2_account_id
export MINIO_SECRET_KEY=b2_application_key
minio gateway b2
```
## Test using Minio Browser
Minio Gateway comes with an embedded web based object browser. Point your web browser to http://127.0.0.1:9000 to ensure that your server has started successfully.
![Screenshot](https://raw.githubusercontent.com/minio/minio/master/docs/screenshots/minio-browser-gateway.png)
## Test using Minio Client `mc`
`mc` provides a modern alternative to UNIX commands such as ls, cat, cp, mirror, diff etc. It supports filesystems and Amazon S3 compatible cloud storage services.
### Configure `mc`
```
mc config host add myb2 http://gateway-ip:9000 b2_account_id b2_application_key
```
### List buckets on Backblaze B2
```
mc ls myb2
[2017-02-22 01:50:43 PST] 0B ferenginar/
[2017-02-26 21:43:51 PST] 0B my-bucket/
[2017-02-26 22:10:11 PST] 0B test-bucket1/
```
### Known limitations
Gateway inherits the following B2 limitations:
- No support for CopyObject S3 API (There are no equivalent APIs available on Backblaze B2).
- No support for CopyObjectPart S3 API (There are no equivalent APIs available on Backblaze B2).
- Only read-only bucket policy supported at bucket level, all other variations will return API Notimplemented error.
- DeleteObject() might not delete the object right away on Backblaze B2, so you might see the object immediately after a Delete request.
Other limitations:
- Bucket notification APIs are not supported.
## Explore Further
- [`mc` command-line interface](https://docs.minio.io/docs/minio-client-quickstart-guide)
- [`aws` command-line interface](https://docs.minio.io/docs/aws-cli-with-minio)
- [`minio-go` Go SDK](https://docs.minio.io/docs/golang-client-quickstart-guide)
@@ -5,7 +5,7 @@ version: '2'
# 9001 through 9004. # 9001 through 9004.
services: services:
minio1: minio1:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
volumes: volumes:
- data1:/data - data1:/data
ports: ports:
@@ -15,7 +15,7 @@ services:
MINIO_SECRET_KEY: minio123 MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio2: minio2:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
volumes: volumes:
- data2:/data - data2:/data
ports: ports:
@@ -25,7 +25,7 @@ services:
MINIO_SECRET_KEY: minio123 MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio3: minio3:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
volumes: volumes:
- data3:/data - data3:/data
ports: ports:
@@ -35,7 +35,7 @@ services:
MINIO_SECRET_KEY: minio123 MINIO_SECRET_KEY: minio123
command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data command: server http://minio1/data http://minio2/data http://minio3/data http://minio4/data
minio4: minio4:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
volumes: volumes:
- data4:/data - data4:/data
ports: ports:
@@ -2,7 +2,7 @@ version: '3.1'
services: services:
minio1: minio1:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio1 hostname: minio1
volumes: volumes:
- minio1-data:/export - minio1-data:/export
@@ -24,7 +24,7 @@ services:
- access_key - access_key
minio2: minio2:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio2 hostname: minio2
volumes: volumes:
- minio2-data:/export - minio2-data:/export
@@ -46,7 +46,7 @@ services:
- access_key - access_key
minio3: minio3:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio3 hostname: minio3
volumes: volumes:
- minio3-data:/export - minio3-data:/export
@@ -68,7 +68,7 @@ services:
- access_key - access_key
minio4: minio4:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio4 hostname: minio4
volumes: volumes:
- minio4-data:/export - minio4-data:/export
@@ -2,7 +2,7 @@ version: '3'
services: services:
minio1: minio1:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio1 hostname: minio1
volumes: volumes:
- minio1-data:/export - minio1-data:/export
@@ -24,7 +24,7 @@ services:
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
minio2: minio2:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio2 hostname: minio2
volumes: volumes:
- minio2-data:/export - minio2-data:/export
@@ -46,7 +46,7 @@ services:
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
minio3: minio3:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio3 hostname: minio3
volumes: volumes:
- minio3-data:/export - minio3-data:/export
@@ -68,7 +68,7 @@ services:
command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export command: server http://minio1/export http://minio2/export http://minio3/export http://minio4/export
minio4: minio4:
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
hostname: minio4 hostname: minio4
volumes: volumes:
- minio4-data:/export - minio4-data:/export
@@ -30,7 +30,7 @@ spec:
value: "minio" value: "minio"
- name: MINIO_SECRET_KEY - name: MINIO_SECRET_KEY
value: "minio123" value: "minio123"
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
# Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future. # Unfortunately you must manually define each server. Perhaps autodiscovery via DNS can be implemented in the future.
args: args:
- server - server
@@ -21,7 +21,7 @@ spec:
value: "minio" value: "minio"
- name: MINIO_SECRET_KEY - name: MINIO_SECRET_KEY
value: "minio123" value: "minio123"
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
args: args:
- server - server
- http://minio-0.minio.default.svc.cluster.local/data - http://minio-0.minio.default.svc.cluster.local/data
@@ -21,7 +21,7 @@ spec:
containers: containers:
- name: minio - name: minio
# Pulls the default Minio image from Docker Hub # Pulls the default Minio image from Docker Hub
image: minio/minio:RELEASE.2019-01-23T23-18-58Z image: minio/minio:RELEASE.2019-02-06T21-16-36Z
args: args:
- gateway - gateway
- gcs - gcs

Some files were not shown because too many files have changed in this diff Show More