mirror of
https://github.com/pgsty/minio.git
synced 2026-11-03 14:22:12 +03:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d0f30f062 | |||
| cea2ca8c8e | |||
| f713436dd0 | |||
| b923a62425 | |||
| 67fce4a5b3 | |||
| eaa65b7ade | |||
| 820d94447c | |||
| ed20134a7b | |||
| d19cbc81b5 | |||
| 1fd7946dce | |||
| 027ff0f3a8 | |||
| 8fa80874a6 | |||
| 430669cfad | |||
| 54b561898f | |||
| 65c104a589 | |||
| 0a0416b6ea | |||
| 441babdc41 | |||
| 1bf1fafc86 | |||
| 50d58e9b2d | |||
| e64b9f6751 | |||
| d67a846ec4 | |||
| ca2a1c3f60 | |||
| 93fbb228bf | |||
| 3683673fb0 | |||
| f37a5b6dae | |||
| 31b0decd46 | |||
| eb561e1c05 | |||
| 54c9ecff5b | |||
| edcd72585d | |||
| 1a17fc17bb | |||
| ddad231921 | |||
| e73894fa50 | |||
| 03b94f907f | |||
| 3fa7218c44 | |||
| 0f591d245d | |||
| 1b02e046c2 | |||
| d08e3cc895 | |||
| d98116559b | |||
| 71c95ad0d0 | |||
| 5c1a4ba5f9 | |||
| 698862ec5d | |||
| b4ef5ff294 | |||
| 3db658e51e | |||
| 5a9f7516d6 | |||
| 3039fd4519 | |||
| 095fc0561d | |||
| beb1924437 | |||
| c8e1154f1e | |||
| b204c2dbec | |||
| b22b39de96 | |||
| c242e6c391 | |||
| e05205756f | |||
| d03b244fcd | |||
| 5ef679d8f1 | |||
| d33c527e39 | |||
| 7bc95c47a3 | |||
| 475a88b555 | |||
| 9815dac48f | |||
| 1ece3d1dfe | |||
| 2146ed4033 | |||
| 52b88b52f0 |
@@ -6,6 +6,10 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
vulncheck:
|
||||
name: Analysis
|
||||
|
||||
@@ -64,12 +64,18 @@ test-iam: build ## verify IAM (external IDP, etcd backends)
|
||||
@echo "Running tests for IAM (external IDP, etcd backends) with -race"
|
||||
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd
|
||||
|
||||
test-replication: install ## verify multi site replication
|
||||
@echo "Running tests for replicating three sites"
|
||||
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
||||
test-replication-2site:
|
||||
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
|
||||
|
||||
test-replication-3site:
|
||||
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
|
||||
|
||||
test-delete-replication:
|
||||
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
|
||||
|
||||
test-replication: install test-replication-2site test-replication-3site test-delete-replication ## verify multi site replication
|
||||
@echo "Running tests for replicating three sites"
|
||||
|
||||
test-site-replication-ldap: install ## verify automatic site replication
|
||||
@echo "Running tests for automatic site replication of IAM (with LDAP)"
|
||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-ldap.sh)
|
||||
@@ -136,7 +142,7 @@ docker-hotfix: hotfix-push checks ## builds minio docker container with hotfix t
|
||||
@echo "Building minio docker image '$(TAG)'"
|
||||
@docker build -q --no-cache -t $(TAG) --build-arg RELEASE=$(VERSION) . -f Dockerfile.hotfix
|
||||
|
||||
docker: build checks ## builds minio docker container
|
||||
docker: build ## builds minio docker container
|
||||
@echo "Building minio docker image '$(TAG)'"
|
||||
@docker build -q --no-cache -t $(TAG) . -f Dockerfile
|
||||
|
||||
|
||||
+1
-1
@@ -22,9 +22,9 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/kes"
|
||||
@@ -41,6 +40,7 @@ import (
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
@@ -107,10 +107,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
// Call site replication hook.
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta))
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
@@ -154,15 +154,9 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
|
||||
Description: err.Error(),
|
||||
HTTPStatusCode: http.StatusForbidden,
|
||||
}
|
||||
case errors.Is(err, errIAMServiceAccount):
|
||||
case errors.Is(err, errIAMServiceAccountNotAllowed):
|
||||
apiErr = APIError{
|
||||
Code: "XMinioIAMServiceAccount",
|
||||
Description: err.Error(),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
case errors.Is(err, errIAMServiceAccountUsed):
|
||||
apiErr = APIError{
|
||||
Code: "XMinioIAMServiceAccountUsed",
|
||||
Code: "XMinioIAMServiceAccountNotAllowed",
|
||||
Description: err.Error(),
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/cache"
|
||||
@@ -37,6 +36,7 @@ import (
|
||||
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
@@ -117,6 +117,26 @@ func applyDynamic(ctx context.Context, objectAPI ObjectLayer, cfg config.Config,
|
||||
w.Header().Set(madmin.ConfigAppliedHeader, madmin.ConfigAppliedTrue)
|
||||
}
|
||||
|
||||
type badConfigErr struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error - return the error message
|
||||
func (bce badConfigErr) Error() string {
|
||||
return bce.Err.Error()
|
||||
}
|
||||
|
||||
// Unwrap the error to its underlying error.
|
||||
func (bce badConfigErr) Unwrap() error {
|
||||
return bce.Err
|
||||
}
|
||||
|
||||
type setConfigResult struct {
|
||||
Cfg config.Config
|
||||
SubSys string
|
||||
Dynamic bool
|
||||
}
|
||||
|
||||
// SetConfigKVHandler - PUT /minio/admin/v3/set-config-kv
|
||||
func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "SetConfigKV")
|
||||
@@ -142,46 +162,53 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := readServerConfig(ctx, objectAPI, nil)
|
||||
result, err := setConfigKV(ctx, objectAPI, kvBytes)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
switch err.(type) {
|
||||
case badConfigErr:
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
default:
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
dynamic, err := cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||
if result.Dynamic {
|
||||
applyDynamic(ctx, objectAPI, result.Cfg, result.SubSys, r, w)
|
||||
}
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
func setConfigKV(ctx context.Context, objectAPI ObjectLayer, kvBytes []byte) (result setConfigResult, err error) {
|
||||
result.Cfg, err = readServerConfig(ctx, objectAPI, nil)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
subSys, _, _, err := config.GetSubSys(string(kvBytes))
|
||||
result.Dynamic, err = result.Cfg.ReadConfig(bytes.NewReader(kvBytes))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = validateConfig(cfg, subSys); err != nil {
|
||||
writeCustomErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), err.Error(), r.URL)
|
||||
result.SubSys, _, _, err = config.GetSubSys(string(kvBytes))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if verr := validateConfig(result.Cfg, result.SubSys); verr != nil {
|
||||
err = badConfigErr{Err: verr}
|
||||
return
|
||||
}
|
||||
|
||||
// Update the actual server config on disk.
|
||||
if err = saveServerConfig(ctx, objectAPI, cfg); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
if err = saveServerConfig(ctx, objectAPI, result.Cfg); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Write to the config input KV to history.
|
||||
if err = saveServerConfigHistory(ctx, objectAPI, kvBytes); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if dynamic {
|
||||
applyDynamic(ctx, objectAPI, cfg, subSys, r, w)
|
||||
}
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
// Write the config input KV to history.
|
||||
err = saveServerConfigHistory(ctx, objectAPI, kvBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// GetConfigKVHandler - GET /minio/admin/v3/get-config-kv?key={key}
|
||||
|
||||
@@ -26,13 +26,13 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
cfgldap "github.com/minio/minio/internal/config/identity/ldap"
|
||||
"github.com/minio/minio/internal/config/identity/openid"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
"github.com/minio/pkg/ldap"
|
||||
)
|
||||
|
||||
@@ -22,9 +22,9 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
|
||||
+38
-12
@@ -22,9 +22,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
@@ -49,28 +50,53 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
pools, ok := objectAPI.(*erasureServerPools)
|
||||
if !ok {
|
||||
z, ok := objectAPI.(*erasureServerPools)
|
||||
if !ok || len(z.serverPools) == 1 {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if pools.IsRebalanceStarted() {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errDecommissionRebalanceAlreadyRunning), r.URL)
|
||||
if z.IsDecommissionRunning() {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errDecommissionAlreadyRunning), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if z.IsRebalanceStarted() {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminRebalanceAlreadyStarted), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
v := vars["pool"]
|
||||
|
||||
idx := globalEndpoints.GetPoolIdx(v)
|
||||
if idx == -1 {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
pools := strings.Split(v, ",")
|
||||
poolIndices := make([]int, 0, len(pools))
|
||||
|
||||
for _, pool := range pools {
|
||||
idx := globalEndpoints.GetPoolIdx(pool)
|
||||
if idx == -1 {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
var pool *erasureSets
|
||||
for pidx := range z.serverPools {
|
||||
if pidx == idx {
|
||||
pool = z.serverPools[idx]
|
||||
break
|
||||
}
|
||||
}
|
||||
if pool == nil {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
poolIndices = append(poolIndices, idx)
|
||||
}
|
||||
|
||||
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
||||
if len(poolIndices) > 0 && globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
|
||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||
if proxyEp.Endpoint.Host == ep.Host {
|
||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||
@@ -80,7 +106,7 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
}
|
||||
|
||||
if err := pools.Decommission(r.Context(), idx); err != nil {
|
||||
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/mux"
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
|
||||
+46
-68
@@ -21,6 +21,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -28,12 +29,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config/dns"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
@@ -66,17 +67,14 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemIAMUser,
|
||||
IAMUser: &madmin.SRIAMUser{
|
||||
AccessKey: accessKey,
|
||||
IsDeleteReq: true,
|
||||
},
|
||||
UpdatedAt: UTCNow(),
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// ListBucketUsers - GET /minio/admin/v3/list-users?bucket={bucket}
|
||||
@@ -261,16 +259,13 @@ func (a adminAPIHandlers) UpdateGroupMembers(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemGroupInfo,
|
||||
GroupInfo: &madmin.SRGroupInfo{
|
||||
UpdateReq: updReq,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// GetGroup - /minio/admin/v3/group?group=mygroup1
|
||||
@@ -360,7 +355,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemGroupInfo,
|
||||
GroupInfo: &madmin.SRGroupInfo{
|
||||
UpdateReq: madmin.GroupAddRemove{
|
||||
@@ -370,10 +365,7 @@ func (a adminAPIHandlers) SetGroupStatus(w http.ResponseWriter, r *http.Request)
|
||||
},
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// SetUserStatus - PUT /minio/admin/v3/set-user-status?accessKey=<access_key>&status=[enabled|disabled]
|
||||
@@ -403,7 +395,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemIAMUser,
|
||||
IAMUser: &madmin.SRIAMUser{
|
||||
AccessKey: accessKey,
|
||||
@@ -413,10 +405,7 @@ func (a adminAPIHandlers) SetUserStatus(w http.ResponseWriter, r *http.Request)
|
||||
},
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// AddUser - PUT /minio/admin/v3/add-user?accessKey=<access_key>
|
||||
@@ -515,7 +504,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemIAMUser,
|
||||
IAMUser: &madmin.SRIAMUser{
|
||||
AccessKey: accessKey,
|
||||
@@ -523,10 +512,7 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
UserReq: &ureq,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// TemporaryAccountInfo - GET /minio/admin/v3/temporary-account-info
|
||||
@@ -665,6 +651,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
opts := newServiceAccountOpts{
|
||||
accessKey: createReq.AccessKey,
|
||||
secretKey: createReq.SecretKey,
|
||||
comment: createReq.Comment,
|
||||
claims: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
@@ -679,6 +666,17 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
requestorIsDerivedCredential = true
|
||||
}
|
||||
|
||||
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType && targetUser != cred.AccessKey {
|
||||
// For internal IDP, ensure that the targetUser's parent account exists.
|
||||
// It could be a regular user account or the root account.
|
||||
_, isRegularUser := globalIAMSys.GetUser(ctx, targetUser)
|
||||
if !isRegularUser && targetUser != globalActiveCred.AccessKey {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||
fmt.Errorf("parent user %s does not exist. Cannot create service account", targetUser)), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we are creating svc account for request sender.
|
||||
isSvcAccForRequestor := false
|
||||
if targetUser == requestorUser || targetUser == requestorParentUser {
|
||||
@@ -797,7 +795,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
// Call hook for cluster-replication if the service account is not for a
|
||||
// root user.
|
||||
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||
err = globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{
|
||||
Create: &madmin.SRSvcAccCreate{
|
||||
@@ -805,17 +803,14 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
AccessKey: newCred.AccessKey,
|
||||
SecretKey: newCred.SecretKey,
|
||||
Groups: newCred.Groups,
|
||||
Comment: newCred.Comment,
|
||||
Claims: opts.claims,
|
||||
SessionPolicy: createReq.Policy,
|
||||
Status: auth.AccountOn,
|
||||
},
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -892,6 +887,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
opts := updateServiceAccountOpts{
|
||||
secretKey: updateReq.NewSecretKey,
|
||||
status: updateReq.NewStatus,
|
||||
comment: updateReq.NewComment,
|
||||
sessionPolicy: sp,
|
||||
}
|
||||
updatedAt, err := globalIAMSys.UpdateServiceAccount(ctx, accessKey, opts)
|
||||
@@ -902,22 +898,19 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// Call site replication hook - non-root user accounts are replicated.
|
||||
if svcAccount.ParentUser != globalActiveCred.AccessKey {
|
||||
err = globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{
|
||||
Update: &madmin.SRSvcAccUpdate{
|
||||
AccessKey: accessKey,
|
||||
SecretKey: opts.secretKey,
|
||||
Status: opts.status,
|
||||
Comment: opts.comment,
|
||||
SessionPolicy: updateReq.NewPolicy,
|
||||
},
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
writeSuccessNoContent(w)
|
||||
@@ -993,6 +986,7 @@ func (a adminAPIHandlers) InfoServiceAccount(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
infoResp := madmin.InfoServiceAccountResp{
|
||||
ParentUser: svcAccount.ParentUser,
|
||||
Comment: svcAccount.Comment,
|
||||
AccountStatus: svcAccount.Status,
|
||||
ImpliedPolicy: policy == nil,
|
||||
Policy: string(policyJSON),
|
||||
@@ -1145,7 +1139,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// Call site replication hook - non-root user accounts are replicated.
|
||||
if svcAccount.ParentUser != "" && svcAccount.ParentUser != globalActiveCred.AccessKey {
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{
|
||||
Delete: &madmin.SRSvcAccDelete{
|
||||
@@ -1153,10 +1147,7 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
},
|
||||
},
|
||||
UpdatedAt: UTCNow(),
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
writeSuccessNoContent(w)
|
||||
@@ -1517,14 +1508,11 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
// Call cluster-replication policy creation hook to replicate policy deletion to
|
||||
// other minio clusters.
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicy,
|
||||
Name: policyName,
|
||||
UpdatedAt: UTCNow(),
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// AddCannedPolicy - PUT /minio/admin/v3/add-canned-policy?name=<policy_name>
|
||||
@@ -1585,15 +1573,12 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
|
||||
|
||||
// Call cluster-replication policy creation hook to replicate policy to
|
||||
// other minio clusters.
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicy,
|
||||
Name: policyName,
|
||||
Policy: iamPolicyBytes,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// SetPolicyForUserOrGroup - PUT /minio/admin/v3/set-policy?policy=xxx&user-or-group=?[&is-group]
|
||||
@@ -1652,7 +1637,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: entityName,
|
||||
@@ -1661,10 +1646,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
|
||||
Policy: policyName,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx
|
||||
@@ -1819,7 +1801,7 @@ func (a adminAPIHandlers) AttachPolicyBuiltin(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: userOrGroup,
|
||||
@@ -1828,10 +1810,7 @@ func (a adminAPIHandlers) AttachPolicyBuiltin(w http.ResponseWriter, r *http.Req
|
||||
Policy: strings.Join(policiesToAttach, ","),
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
writeResponse(w, http.StatusCreated, nil, mimeNone)
|
||||
}
|
||||
@@ -1952,7 +1931,7 @@ func (a adminAPIHandlers) DetachPolicyBuiltin(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: userOrGroup,
|
||||
@@ -1961,10 +1940,7 @@ func (a adminAPIHandlers) DetachPolicyBuiltin(w http.ResponseWriter, r *http.Req
|
||||
Policy: strings.Join(policiesToDetach, ","),
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
// Return successful JSON response
|
||||
writeSuccessNoContent(w)
|
||||
@@ -2467,6 +2443,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
opts := updateServiceAccountOpts{
|
||||
secretKey: svcAcctReq.SecretKey,
|
||||
status: svcAcctReq.Status,
|
||||
comment: svcAcctReq.Comment,
|
||||
sessionPolicy: sp,
|
||||
}
|
||||
_, err = globalIAMSys.UpdateServiceAccount(ctx, svcAcctReq.AccessKey, opts)
|
||||
@@ -2481,6 +2458,7 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
secretKey: svcAcctReq.SecretKey,
|
||||
sessionPolicy: sp,
|
||||
claims: svcAcctReq.Claims,
|
||||
comment: svcAcctReq.Comment,
|
||||
}
|
||||
|
||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||
|
||||
@@ -1136,7 +1136,7 @@ func (s *TestSuiteIAM) TestAccMgmtPlugin(c *check) {
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||
|
||||
// 6. Check that service account **can** be created for some other user.
|
||||
// This is possible because of the policy enforced in the plugin.
|
||||
// This is possible because the policy enforced in the plugin.
|
||||
c.mustCreateSvcAccount(ctx, globalActiveCred.AccessKey, userAdmClient)
|
||||
}
|
||||
|
||||
|
||||
+11
-65
@@ -32,7 +32,6 @@ import (
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -45,16 +44,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/madmin-go/v2/estream"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/logger/message/log"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
"github.com/secure-io/sio-go"
|
||||
@@ -1102,7 +1102,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// If no ObjectLayer is provided no set status is returned.
|
||||
func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmin.BgHealState, error) {
|
||||
// Get local heal status first
|
||||
bgHealStates, ok := getBackgroundHealStatus(ctx, o)
|
||||
bgHealStates, ok := getLocalBackgroundHealStatus(ctx, o)
|
||||
if !ok {
|
||||
return bgHealStates, errServerNotInitialized
|
||||
}
|
||||
@@ -2341,66 +2341,6 @@ func getTLSInfo() madmin.TLSInfo {
|
||||
return tlsInfo
|
||||
}
|
||||
|
||||
// BandwidthMonitorHandler - GET /minio/admin/v3/bandwidth
|
||||
// ----------
|
||||
// Get bandwidth consumption information
|
||||
func (a adminAPIHandlers) BandwidthMonitorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "BandwidthMonitor")
|
||||
|
||||
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||
|
||||
// Validate request signature.
|
||||
_, adminAPIErr := checkAdminRequestAuth(ctx, r, iampolicy.BandwidthMonitorAction, "")
|
||||
if adminAPIErr != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(adminAPIErr), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
setEventStreamHeaders(w)
|
||||
reportCh := make(chan madmin.BucketBandwidthReport)
|
||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||
defer keepAliveTicker.Stop()
|
||||
bucketsRequestedString := r.Form.Get("buckets")
|
||||
bucketsRequested := strings.Split(bucketsRequestedString, ",")
|
||||
go func() {
|
||||
defer close(reportCh)
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case reportCh <- globalNotificationSys.GetBandwidthReports(ctx, bucketsRequested...):
|
||||
time.Sleep(time.Duration(rnd.Float64() * float64(2*time.Second)))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
for {
|
||||
select {
|
||||
case report, ok := <-reportCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := enc.Encode(report); err != nil {
|
||||
return
|
||||
}
|
||||
if len(reportCh) == 0 {
|
||||
// Flush if nothing is queued
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
case <-keepAliveTicker.C:
|
||||
if _, err := w.Write([]byte(" ")); err != nil {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServerInfoHandler - GET /minio/admin/v3/info
|
||||
// ----------
|
||||
// Get server information
|
||||
@@ -2920,10 +2860,16 @@ func createHostAnonymizer() map[string]string {
|
||||
}
|
||||
|
||||
hostAnonymizer := map[string]string{}
|
||||
hosts := set.NewStringSet()
|
||||
srvrIdx := 0
|
||||
|
||||
for poolIdx, pool := range globalEndpoints {
|
||||
for srvrIdx, endpoint := range pool.Endpoints {
|
||||
anonymizeHost(hostAnonymizer, endpoint, poolIdx+1, srvrIdx+1)
|
||||
for _, endpoint := range pool.Endpoints {
|
||||
if !hosts.Contains(endpoint.Host) {
|
||||
hosts.Add(endpoint.Host)
|
||||
srvrIdx++
|
||||
}
|
||||
anonymizeHost(hostAnonymizer, endpoint, poolIdx+1, srvrIdx)
|
||||
}
|
||||
}
|
||||
return hostAnonymizer
|
||||
|
||||
@@ -29,9 +29,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
// adminErasureTestBed - encapsulates subsystems that need to be setup for
|
||||
|
||||
+27
-9
@@ -176,11 +176,12 @@ func (ahs *allHealState) getHealLocalDiskEndpoints() Endpoints {
|
||||
return endpoints
|
||||
}
|
||||
|
||||
func (ahs *allHealState) markDiskForHealing(ep Endpoint) {
|
||||
// Set, in the memory, the state of the disk as currently healing or not
|
||||
func (ahs *allHealState) setDiskHealingStatus(ep Endpoint, healing bool) {
|
||||
ahs.Lock()
|
||||
defer ahs.Unlock()
|
||||
|
||||
ahs.healLocalDisks[ep] = true
|
||||
ahs.healLocalDisks[ep] = healing
|
||||
}
|
||||
|
||||
func (ahs *allHealState) pushHealLocalDisks(healLocalDisks ...Endpoint) {
|
||||
@@ -396,6 +397,7 @@ type healSource struct {
|
||||
bucket string
|
||||
object string
|
||||
versionID string
|
||||
noWait bool // a non blocking call, if task queue is full return right away.
|
||||
opts *madmin.HealOpts // optional heal option overrides default setting
|
||||
}
|
||||
|
||||
@@ -699,7 +701,6 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
||||
object: source.object,
|
||||
versionID: source.versionID,
|
||||
opts: h.settings,
|
||||
respCh: h.respCh,
|
||||
}
|
||||
if source.opts != nil {
|
||||
task.opts = *source.opts
|
||||
@@ -712,15 +713,32 @@ func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItem
|
||||
h.lastHealActivity = UTCNow()
|
||||
h.mutex.Unlock()
|
||||
|
||||
select {
|
||||
case globalBackgroundHealRoutine.tasks <- task:
|
||||
if serverDebugLog {
|
||||
logger.Info("Task in the queue: %#v", task)
|
||||
if source.noWait {
|
||||
select {
|
||||
case globalBackgroundHealRoutine.tasks <- task:
|
||||
if serverDebugLog {
|
||||
logger.Info("Task in the queue: %#v", task)
|
||||
}
|
||||
case <-h.ctx.Done():
|
||||
return nil
|
||||
default:
|
||||
// task queue is full, no more workers, we shall move on and heal later.
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// respCh must be set for guaranteed result
|
||||
task.respCh = h.respCh
|
||||
select {
|
||||
case globalBackgroundHealRoutine.tasks <- task:
|
||||
if serverDebugLog {
|
||||
logger.Info("Task in the queue: %#v", task)
|
||||
}
|
||||
case <-h.ctx.Done():
|
||||
return nil
|
||||
}
|
||||
case <-h.ctx.Done():
|
||||
return nil
|
||||
}
|
||||
|
||||
// task queued, now wait for the response.
|
||||
select {
|
||||
case res := <-h.respCh:
|
||||
if !h.reportProgress {
|
||||
|
||||
+1
-3
@@ -20,11 +20,11 @@ package cmd
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -303,8 +303,6 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
// -- Health API --
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
|
||||
HandlerFunc(gz(httpTraceHdrs(adminAPI.BandwidthMonitorHandler)))
|
||||
}
|
||||
|
||||
// If none of the routes match add default error handler routes
|
||||
|
||||
@@ -208,6 +208,8 @@ const (
|
||||
ErrSSEMultipartEncrypted
|
||||
ErrSSEEncryptedObject
|
||||
ErrInvalidEncryptionParameters
|
||||
ErrInvalidEncryptionParametersSSEC
|
||||
|
||||
ErrInvalidSSECustomerAlgorithm
|
||||
ErrInvalidSSECustomerKey
|
||||
ErrMissingSSECustomerKey
|
||||
@@ -1114,6 +1116,11 @@ var errorCodes = errorCodeMap{
|
||||
Description: "The encryption parameters are not applicable to this object.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidEncryptionParametersSSEC: {
|
||||
Code: "InvalidRequest",
|
||||
Description: "SSE-C encryption parameters are not supported on replicated bucket.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidSSECustomerAlgorithm: {
|
||||
Code: "InvalidArgument",
|
||||
Description: "Requests specifying Server Side Encryption with Customer provided keys must provide a valid encryption algorithm.",
|
||||
@@ -2006,6 +2013,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
|
||||
// SSE errors
|
||||
case errInvalidEncryptionParameters:
|
||||
apiErr = ErrInvalidEncryptionParameters
|
||||
case errInvalidEncryptionParametersSSEC:
|
||||
apiErr = ErrInvalidEncryptionParametersSSEC
|
||||
case crypto.ErrInvalidEncryptionMethod:
|
||||
apiErr = ErrInvalidEncryptionMethod
|
||||
case crypto.ErrInvalidEncryptionKeyID:
|
||||
|
||||
+23
-6
@@ -20,6 +20,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -64,15 +65,31 @@ func setCommonHeaders(w http.ResponseWriter) {
|
||||
|
||||
// Encodes the response headers into XML format.
|
||||
func encodeResponse(response interface{}) []byte {
|
||||
var bytesBuffer bytes.Buffer
|
||||
bytesBuffer.WriteString(xxml.Header)
|
||||
buf, err := xxml.Marshal(response)
|
||||
if err != nil {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(xml.Header)
|
||||
if err := xml.NewEncoder(&buf).Encode(response); err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return nil
|
||||
}
|
||||
bytesBuffer.Write(buf)
|
||||
return bytesBuffer.Bytes()
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Use this encodeResponseList() to support control characters
|
||||
// this function must be used by only ListObjects() for objects
|
||||
// with control characters, this is a specialized extension
|
||||
// to support AWS S3 compatible behavior.
|
||||
//
|
||||
// Do not use this function for anything other than ListObjects()
|
||||
// variants, please open a github discussion if you wish to use
|
||||
// this in other places.
|
||||
func encodeResponseList(response interface{}) []byte {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(xxml.Header)
|
||||
if err := xxml.NewEncoder(&buf).Encode(response); err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return nil
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// Encodes the response headers into JSON format.
|
||||
|
||||
+3
-3
@@ -314,12 +314,12 @@ func (s *Metadata) Set(k, v string) {
|
||||
}
|
||||
|
||||
type xmlKeyEntry struct {
|
||||
XMLName xml.Name
|
||||
XMLName xxml.Name
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// MarshalXML - StringMap marshals into XML.
|
||||
func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
func (s *Metadata) MarshalXML(e *xxml.Encoder, start xxml.StartElement) error {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -334,7 +334,7 @@ func (s *Metadata) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
|
||||
for _, item := range s.Items {
|
||||
if err := e.Encode(xmlKeyEntry{
|
||||
XMLName: xml.Name{Local: item.Key},
|
||||
XMLName: xxml.Name{Local: item.Key},
|
||||
Value: item.Value,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
||||
+1
-1
@@ -22,11 +22,11 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/minio/console/restapi"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/wildcard"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
+178
-177
File diff suppressed because one or more lines are too long
+16
-1
@@ -371,6 +371,7 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
||||
region := reqInfo.Region
|
||||
bucket := reqInfo.BucketName
|
||||
object := reqInfo.ObjectName
|
||||
versionID := reqInfo.VersionID
|
||||
|
||||
if action != policy.ListAllMyBucketsAction && cred.AccessKey == "" {
|
||||
// Anonymous checks are not meant for ListAllBuckets action
|
||||
@@ -404,7 +405,21 @@ func authorizeRequest(ctx context.Context, r *http.Request, action policy.Action
|
||||
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
if action == policy.DeleteObjectAction && versionID != "" {
|
||||
if !globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: iampolicy.Action(policy.DeleteObjectVersionAction),
|
||||
BucketName: bucket,
|
||||
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
|
||||
ObjectName: object,
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
DenyOnly: true,
|
||||
}) { // Request is not allowed if Deny action on DeleteObjectVersionAction
|
||||
return ErrAccessDenied
|
||||
}
|
||||
}
|
||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
|
||||
@@ -88,8 +88,7 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
||||
var err error
|
||||
switch task.bucket {
|
||||
case nopHeal:
|
||||
task.respCh <- healResult{err: errSkipFile}
|
||||
continue
|
||||
err = errSkipFile
|
||||
case SlashSeparator:
|
||||
res, err = healDiskFormat(ctx, objAPI, task.opts)
|
||||
default:
|
||||
@@ -100,7 +99,10 @@ func (h *healRoutine) AddWorker(ctx context.Context, objAPI ObjectLayer) {
|
||||
}
|
||||
}
|
||||
|
||||
task.respCh <- healResult{result: res, err: err}
|
||||
if task.respCh != nil {
|
||||
task.respCh <- healResult{result: res, err: err}
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
@@ -441,9 +441,10 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
|
||||
|
||||
for _, disk := range healDisks {
|
||||
go func(disk Endpoint) {
|
||||
globalBackgroundHealState.markDiskForHealing(disk)
|
||||
globalBackgroundHealState.setDiskHealingStatus(disk, true)
|
||||
err := healFreshDisk(ctx, z, disk)
|
||||
if err != nil {
|
||||
globalBackgroundHealState.setDiskHealingStatus(disk, false)
|
||||
printEndpointError(disk, err, false)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -25,14 +25,13 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
@@ -53,24 +52,22 @@ type bootstrapRESTServer struct{}
|
||||
|
||||
// ServerSystemConfig - captures information about server configuration.
|
||||
type ServerSystemConfig struct {
|
||||
MinioPlatform string
|
||||
MinioEndpoints EndpointServerPools
|
||||
MinioEnv map[string]string
|
||||
}
|
||||
|
||||
// Diff - returns error on first difference found in two configs.
|
||||
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
||||
if s1.MinioPlatform != s2.MinioPlatform {
|
||||
return fmt.Errorf("Expected platform '%s', found to be running '%s'",
|
||||
s1.MinioPlatform, s2.MinioPlatform)
|
||||
}
|
||||
|
||||
if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
|
||||
return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
|
||||
s2.MinioEndpoints.NEndpoints())
|
||||
}
|
||||
|
||||
for i, ep := range s1.MinioEndpoints {
|
||||
if ep.CmdLine != s2.MinioEndpoints[i].CmdLine {
|
||||
return fmt.Errorf("Expected command line argument %s, seen %s", ep.CmdLine,
|
||||
s2.MinioEndpoints[i].CmdLine)
|
||||
}
|
||||
if ep.SetCount != s2.MinioEndpoints[i].SetCount {
|
||||
return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
|
||||
s2.MinioEndpoints[i].SetCount)
|
||||
@@ -79,11 +76,9 @@ func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
||||
return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
|
||||
s2.MinioEndpoints[i].DrivesPerSet)
|
||||
}
|
||||
for j, endpoint := range ep.Endpoints {
|
||||
if endpoint.String() != s2.MinioEndpoints[i].Endpoints[j].String() {
|
||||
return fmt.Errorf("Expected endpoint %s, seen %s", endpoint,
|
||||
s2.MinioEndpoints[i].Endpoints[j])
|
||||
}
|
||||
if ep.Platform != s2.MinioEndpoints[i].Platform {
|
||||
return fmt.Errorf("Expected platform '%s', found to be on '%s'",
|
||||
ep.Platform, s2.MinioEndpoints[i].Platform)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
|
||||
@@ -126,7 +121,6 @@ func getServerSystemCfg() ServerSystemConfig {
|
||||
envValues[envK] = env.Get(envK, "")
|
||||
}
|
||||
return ServerSystemConfig{
|
||||
MinioPlatform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||
MinioEndpoints: globalEndpoints,
|
||||
MinioEnv: envValues,
|
||||
}
|
||||
@@ -232,7 +226,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
||||
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
|
||||
}
|
||||
if len(incorrectConfigs) > 0 {
|
||||
logger.Info(fmt.Sprintf("Following servers mismatch in their configuration %s", incorrectConfigs))
|
||||
logger.Info(fmt.Sprintf("Following servers have mismatching configuration %s", incorrectConfigs))
|
||||
}
|
||||
retries = 0 // reset to log again after 5 retries.
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/kes"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
@@ -51,11 +51,6 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
|
||||
@@ -119,15 +114,12 @@ func (api objectAPIHandlers) PutBucketEncryptionHandler(w http.ResponseWriter, r
|
||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||
// errors.
|
||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||
Bucket: bucket,
|
||||
SSEConfig: &cfgStr,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
@@ -209,16 +201,14 @@ func (api objectAPIHandlers) DeleteBucketEncryptionHandler(w http.ResponseWriter
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Call site replication hook.
|
||||
//
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypeSSEConfig,
|
||||
Bucket: bucket,
|
||||
SSEConfig: nil,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
writeSuccessNoContent(w)
|
||||
}
|
||||
|
||||
+51
-62
@@ -34,7 +34,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/mux"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
@@ -851,7 +851,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
|
||||
globalNotificationSys.LoadBucketMetadata(GlobalContext, bucket)
|
||||
|
||||
// Call site replication hook
|
||||
globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts)
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.MakeBucketHook(ctx, bucket, opts))
|
||||
|
||||
// Make sure to add Location information here only for bucket
|
||||
if cp := pathClean(r.URL.Path); cp != "" {
|
||||
@@ -890,11 +890,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
bucket := mux.Vars(r)["bucket"]
|
||||
|
||||
// Require Content-Length to be set in the request
|
||||
@@ -1066,51 +1061,56 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.Requested(formValues) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
return
|
||||
}
|
||||
var (
|
||||
reader io.Reader
|
||||
keyID string
|
||||
key []byte
|
||||
kmsCtx kms.Context
|
||||
)
|
||||
kind, _ := crypto.IsRequested(formValues)
|
||||
switch kind {
|
||||
case crypto.SSEC:
|
||||
key, err = ParseSSECustomerHeader(formValues)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
case crypto.S3KMS:
|
||||
keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
||||
|
||||
if crypto.Requested(formValues) {
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
reader io.Reader
|
||||
keyID string
|
||||
key []byte
|
||||
kmsCtx kms.Context
|
||||
)
|
||||
kind, _ := crypto.IsRequested(formValues)
|
||||
switch kind {
|
||||
case crypto.SSEC:
|
||||
key, err = ParseSSECustomerHeader(formValues)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
info := ObjectInfo{Size: fileSize}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
case crypto.S3KMS:
|
||||
keyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(formValues)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
reader, objectEncryptionKey, err = newEncryptReader(ctx, hashReader, kind, keyID, key, bucket, object, metadata, kmsCtx)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
info := ObjectInfo{Size: fileSize}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
|
||||
@@ -1352,11 +1352,9 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
||||
|
||||
globalNotificationSys.DeleteBucketMetadata(ctx, bucket)
|
||||
globalReplicationPool.deleteResyncMetadata(ctx, bucket)
|
||||
|
||||
// Call site replication hook.
|
||||
if err := globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.DeleteBucketHook(ctx, bucket, forceDelete))
|
||||
|
||||
// Write success response.
|
||||
writeSuccessNoContent(w)
|
||||
@@ -1425,15 +1423,12 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
|
||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||
// errors.
|
||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypeObjectLockConfig,
|
||||
Bucket: bucket,
|
||||
ObjectLockConfig: &cfgStr,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
@@ -1532,15 +1527,12 @@ func (api objectAPIHandlers) PutBucketTaggingHandler(w http.ResponseWriter, r *h
|
||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||
// errors.
|
||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypeTags,
|
||||
Bucket: bucket,
|
||||
Tags: &cfgStr,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
@@ -1611,14 +1603,11 @@ func (api objectAPIHandlers) DeleteBucketTaggingHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypeTags,
|
||||
Bucket: bucket,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
+24
-11
@@ -662,6 +662,9 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
var objectNames []string
|
||||
for i := 0; i < 10; i++ {
|
||||
objectName := "test-object-" + strconv.Itoa(i)
|
||||
if i == 0 {
|
||||
objectName += "/"
|
||||
}
|
||||
// uploading the object.
|
||||
_, err = obj.PutObject(GlobalContext, bucketName, objectName, mustGetPutObjReader(t, bytes.NewReader(contentBytes), int64(len(contentBytes)), "", sha256sum), ObjectOptions{})
|
||||
// if object upload fails stop the test.
|
||||
@@ -793,9 +796,9 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
expectedContent []byte
|
||||
expectedRespStatus int
|
||||
}{
|
||||
// Test case - 1.
|
||||
// Test case - 0.
|
||||
// Delete objects with invalid access key.
|
||||
{
|
||||
0: {
|
||||
bucket: bucketName,
|
||||
objects: successRequest0,
|
||||
accessKey: "Invalid-AccessID",
|
||||
@@ -803,9 +806,19 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
expectedContent: nil,
|
||||
expectedRespStatus: http.StatusForbidden,
|
||||
},
|
||||
// Test case - 2.
|
||||
// Test case - 1.
|
||||
// Delete valid objects with quiet flag off.
|
||||
{
|
||||
1: {
|
||||
bucket: bucketName,
|
||||
objects: successRequest0,
|
||||
accessKey: credentials.AccessKey,
|
||||
secretKey: credentials.SecretKey,
|
||||
expectedContent: encodedSuccessResponse0,
|
||||
expectedRespStatus: http.StatusOK,
|
||||
},
|
||||
// Test case - 2.
|
||||
// Delete deleted objects with quiet flag off.
|
||||
2: {
|
||||
bucket: bucketName,
|
||||
objects: successRequest0,
|
||||
accessKey: credentials.AccessKey,
|
||||
@@ -815,7 +828,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
},
|
||||
// Test case - 3.
|
||||
// Delete valid objects with quiet flag on.
|
||||
{
|
||||
3: {
|
||||
bucket: bucketName,
|
||||
objects: successRequest1,
|
||||
accessKey: credentials.AccessKey,
|
||||
@@ -825,7 +838,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
},
|
||||
// Test case - 4.
|
||||
// Delete previously deleted objects.
|
||||
{
|
||||
4: {
|
||||
bucket: bucketName,
|
||||
objects: successRequest1,
|
||||
accessKey: credentials.AccessKey,
|
||||
@@ -836,7 +849,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
// Test case - 5.
|
||||
// Anonymous user access denied response
|
||||
// Currently anonymous users cannot delete multiple objects in MinIO server
|
||||
{
|
||||
5: {
|
||||
bucket: bucketName,
|
||||
objects: anonRequest,
|
||||
accessKey: "",
|
||||
@@ -847,7 +860,7 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
// Test case - 6.
|
||||
// Anonymous user has access to some public folder, issue removing with
|
||||
// another private object as well
|
||||
{
|
||||
6: {
|
||||
bucket: bucketName,
|
||||
objects: anonRequestWithPartialPublicAccess,
|
||||
accessKey: "",
|
||||
@@ -881,19 +894,19 @@ func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketNa
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
// Assert the response code with the expected status.
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i+1, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||
t.Errorf("Test %d: MinIO %s: Expected the response status to be `%d`, but instead found `%d`", i, instanceType, testCase.expectedRespStatus, rec.Code)
|
||||
}
|
||||
|
||||
// read the response body.
|
||||
actualContent, err = io.ReadAll(rec.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i+1, instanceType, err)
|
||||
t.Fatalf("Test %d : MinIO %s: Failed parsing response body: <ERROR> %v", i, instanceType, err)
|
||||
}
|
||||
|
||||
// Verify whether the bucket obtained object is same as the one created.
|
||||
if testCase.expectedContent != nil && !bytes.Equal(testCase.expectedContent, actualContent) {
|
||||
t.Log(string(testCase.expectedContent), string(actualContent))
|
||||
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i+1, instanceType)
|
||||
t.Errorf("Test %d : MinIO %s: Object content differs from expected value.", i, instanceType)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
|
||||
@@ -660,7 +660,9 @@ func putRestoreOpts(bucket, object string, rreq *RestoreObjectRequest, objInfo O
|
||||
if len(objInfo.UserTags) != 0 {
|
||||
meta[xhttp.AmzObjectTagging] = objInfo.UserTags
|
||||
}
|
||||
|
||||
// Set restore object status
|
||||
restoreExpiry := lifecycle.ExpectedExpiryTime(time.Now().UTC(), rreq.Days)
|
||||
meta[xhttp.AmzRestore] = completedRestoreObj(restoreExpiry).String()
|
||||
return ObjectOptions{
|
||||
Versioned: globalBucketVersioningSys.PrefixEnabled(bucket, object),
|
||||
VersionSuspended: globalBucketVersioningSys.PrefixSuspended(bucket, object),
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
@@ -115,7 +115,7 @@ func (api objectAPIHandlers) ListObjectVersionsHandler(w http.ResponseWriter, r
|
||||
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo)
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodeResponse(response))
|
||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||
}
|
||||
|
||||
// ListObjectsV2MHandler - GET Bucket (List Objects) Version 2 with metadata.
|
||||
@@ -185,7 +185,7 @@ func (api objectAPIHandlers) ListObjectsV2MHandler(w http.ResponseWriter, r *htt
|
||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, true)
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodeResponse(response))
|
||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||
}
|
||||
|
||||
// ListObjectsV2Handler - GET Bucket (List Objects) Version 2.
|
||||
@@ -260,7 +260,7 @@ func (api objectAPIHandlers) ListObjectsV2Handler(w http.ResponseWriter, r *http
|
||||
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, false)
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodeResponse(response))
|
||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||
}
|
||||
|
||||
func parseRequestToken(token string) (subToken string, nodeIndex int) {
|
||||
@@ -357,5 +357,5 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
||||
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
|
||||
|
||||
// Write success response.
|
||||
writeSuccessResponseXML(w, encodeResponse(response))
|
||||
writeSuccessResponseXML(w, encodeResponseList(response))
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@ import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
@@ -50,11 +50,6 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsNotificationSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetBucketNotificationAction, bucketName, ""); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
return
|
||||
@@ -119,11 +114,6 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
if !objectAPI.IsNotificationSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucketName := vars["bucket"]
|
||||
|
||||
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
"net/http"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
@@ -109,15 +109,12 @@ func (api objectAPIHandlers) PutBucketPolicyHandler(w http.ResponseWriter, r *ht
|
||||
}
|
||||
|
||||
// Call site replication hook.
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypePolicy,
|
||||
Bucket: bucket,
|
||||
Policy: bucketPolicyBytes,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
// Success.
|
||||
writeSuccessNoContent(w)
|
||||
@@ -156,14 +153,11 @@ func (api objectAPIHandlers) DeleteBucketPolicyHandler(w http.ResponseWriter, r
|
||||
}
|
||||
|
||||
// Call site replication hook.
|
||||
if err := globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypePolicy,
|
||||
Bucket: bucket,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
// Success.
|
||||
writeSuccessNoContent(w)
|
||||
|
||||
@@ -25,10 +25,10 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/bucket/replication"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
@@ -231,7 +231,19 @@ func (api objectAPIHandlers) GetBucketReplicationMetricsHandler(w http.ResponseW
|
||||
w.Header().Set(xhttp.ContentType, string(mimeJSON))
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
if err = enc.Encode(globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)); err != nil {
|
||||
stats := globalReplicationStats.getLatestReplicationStats(bucket, usageInfo)
|
||||
bwRpt := globalNotificationSys.GetBandwidthReports(ctx, bucket)
|
||||
bwMap := bwRpt.BucketStats[bucket]
|
||||
for arn, st := range stats.Stats {
|
||||
if bwMap != nil {
|
||||
if bw, ok := bwMap[arn]; ok {
|
||||
st.BandWidthLimitInBytesPerSecond = bw.LimitInBytesPerSecond
|
||||
st.CurrentBandwidthInBytesPerSecond = bw.CurrentBandwidthInBytesPerSecond
|
||||
stats.Stats[arn] = st
|
||||
}
|
||||
}
|
||||
}
|
||||
if err = enc.Encode(stats); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -197,7 +197,8 @@ func (r *ReplicationStats) loadInitialReplicationMetrics(ctx context.Context) {
|
||||
m := make(map[string]*BucketReplicationStats)
|
||||
if stats, err := globalReplicationPool.loadStatsFromDisk(); err == nil {
|
||||
for b, st := range stats {
|
||||
m[b] = &st
|
||||
c := st.Clone()
|
||||
m[b] = &c
|
||||
}
|
||||
r.ulock.Lock()
|
||||
r.UsageCache = m
|
||||
|
||||
@@ -74,6 +74,11 @@ const (
|
||||
ReplicationWorkerMultiplier = 1.5
|
||||
)
|
||||
|
||||
func isReplicationEnabled(ctx context.Context, bucketName string) bool {
|
||||
rc, _ := getReplicationConfig(ctx, bucketName)
|
||||
return rc != nil
|
||||
}
|
||||
|
||||
// gets replication config associated to a given bucket name.
|
||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||
rCfg, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
||||
@@ -1144,10 +1149,11 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||
|
||||
opts := &bandwidth.MonitorReaderOptions{
|
||||
Bucket: objInfo.Bucket,
|
||||
TargetARN: tgt.ARN,
|
||||
HeaderSize: headerSize,
|
||||
}
|
||||
newCtx := ctx
|
||||
if globalBucketMonitor.IsThrottled(bucket) {
|
||||
if globalBucketMonitor.IsThrottled(bucket, tgt.ARN) {
|
||||
var cancel context.CancelFunc
|
||||
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
||||
defer cancel()
|
||||
@@ -1344,10 +1350,11 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
|
||||
opts := &bandwidth.MonitorReaderOptions{
|
||||
Bucket: objInfo.Bucket,
|
||||
TargetARN: tgt.ARN,
|
||||
HeaderSize: headerSize,
|
||||
}
|
||||
newCtx := ctx
|
||||
if globalBucketMonitor.IsThrottled(bucket) {
|
||||
if globalBucketMonitor.IsThrottled(bucket, tgt.ARN) {
|
||||
var cancel context.CancelFunc
|
||||
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
||||
defer cancel()
|
||||
@@ -1356,13 +1363,21 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
if objInfo.isMultipart() {
|
||||
if err := replicateObjectWithMultipart(ctx, c, tgt.Bucket, object,
|
||||
r, objInfo, putOpts); err != nil {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||
if minio.ToErrorResponse(err).Code != "PreConditionFailed" {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||
} else {
|
||||
rinfo.ReplicationStatus = replication.Completed
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts); err != nil {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||
if minio.ToErrorResponse(err).Code != "PreConditionFailed" {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||
} else {
|
||||
rinfo.ReplicationStatus = replication.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1903,7 +1918,7 @@ func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs
|
||||
return nil, proxy, err
|
||||
}
|
||||
c := minio.Core{Client: tgt.Client}
|
||||
obj, _, h, err := c.GetObject(ctx, bucket, object, gopts)
|
||||
obj, _, h, err := c.GetObject(ctx, tgt.Bucket, object, gopts)
|
||||
if err != nil {
|
||||
return nil, proxy, err
|
||||
}
|
||||
@@ -1928,7 +1943,7 @@ func getProxyTargets(ctx context.Context, bucket, object string, opts ObjectOpti
|
||||
if opts.VersionSuspended {
|
||||
return &madmin.BucketTargets{}
|
||||
}
|
||||
if !opts.ProxyRequest {
|
||||
if opts.ProxyRequest || (opts.ProxyHeaderSet && !opts.ProxyRequest) {
|
||||
return &madmin.BucketTargets{}
|
||||
}
|
||||
cfg, err := getReplicationConfig(ctx, bucket)
|
||||
|
||||
@@ -120,6 +120,10 @@ type BucketReplicationStat struct {
|
||||
FailedCount int64 `json:"failedReplicationCount"`
|
||||
// Replication latency information
|
||||
Latency ReplicationLatency `json:"replicationLatency"`
|
||||
// bandwidth limit for target
|
||||
BandWidthLimitInBytesPerSecond int64 `json:"limitInBits"`
|
||||
// current bandwidth reported
|
||||
CurrentBandwidthInBytesPerSecond float64 `json:"currentBandwidth"`
|
||||
}
|
||||
|
||||
func (bs *BucketReplicationStat) hasReplicationUsage() bool {
|
||||
|
||||
+55
-5
@@ -89,6 +89,18 @@ func (z *BucketReplicationStat) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case "BandWidthLimitInBytesPerSecond":
|
||||
z.BandWidthLimitInBytesPerSecond, err = dc.ReadInt64()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
|
||||
return
|
||||
}
|
||||
case "CurrentBandwidthInBytesPerSecond":
|
||||
z.CurrentBandwidthInBytesPerSecond, err = dc.ReadFloat64()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -102,9 +114,9 @@ func (z *BucketReplicationStat) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 7
|
||||
// map header, size 9
|
||||
// write "PendingSize"
|
||||
err = en.Append(0x87, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
||||
err = en.Append(0x89, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -179,15 +191,35 @@ func (z *BucketReplicationStat) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Latency", "UploadHistogram")
|
||||
return
|
||||
}
|
||||
// write "BandWidthLimitInBytesPerSecond"
|
||||
err = en.Append(0xbe, 0x42, 0x61, 0x6e, 0x64, 0x57, 0x69, 0x64, 0x74, 0x68, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteInt64(z.BandWidthLimitInBytesPerSecond)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
|
||||
return
|
||||
}
|
||||
// write "CurrentBandwidthInBytesPerSecond"
|
||||
err = en.Append(0xd9, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteFloat64(z.CurrentBandwidthInBytesPerSecond)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 7
|
||||
// map header, size 9
|
||||
// string "PendingSize"
|
||||
o = append(o, 0x87, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
||||
o = append(o, 0x89, 0xab, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x69, 0x7a, 0x65)
|
||||
o = msgp.AppendInt64(o, z.PendingSize)
|
||||
// string "ReplicatedSize"
|
||||
o = append(o, 0xae, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x53, 0x69, 0x7a, 0x65)
|
||||
@@ -214,6 +246,12 @@ func (z *BucketReplicationStat) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Latency", "UploadHistogram")
|
||||
return
|
||||
}
|
||||
// string "BandWidthLimitInBytesPerSecond"
|
||||
o = append(o, 0xbe, 0x42, 0x61, 0x6e, 0x64, 0x57, 0x69, 0x64, 0x74, 0x68, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||
o = msgp.AppendInt64(o, z.BandWidthLimitInBytesPerSecond)
|
||||
// string "CurrentBandwidthInBytesPerSecond"
|
||||
o = append(o, 0xd9, 0x20, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x49, 0x6e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x50, 0x65, 0x72, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64)
|
||||
o = msgp.AppendFloat64(o, z.CurrentBandwidthInBytesPerSecond)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -300,6 +338,18 @@ func (z *BucketReplicationStat) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
case "BandWidthLimitInBytesPerSecond":
|
||||
z.BandWidthLimitInBytesPerSecond, bts, err = msgp.ReadInt64Bytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "BandWidthLimitInBytesPerSecond")
|
||||
return
|
||||
}
|
||||
case "CurrentBandwidthInBytesPerSecond":
|
||||
z.CurrentBandwidthInBytesPerSecond, bts, err = msgp.ReadFloat64Bytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CurrentBandwidthInBytesPerSecond")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -314,7 +364,7 @@ func (z *BucketReplicationStat) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *BucketReplicationStat) Msgsize() (s int) {
|
||||
s = 1 + 12 + msgp.Int64Size + 15 + msgp.Int64Size + 12 + msgp.Int64Size + 11 + msgp.Int64Size + 13 + msgp.Int64Size + 12 + msgp.Int64Size + 8 + 1 + 16 + z.Latency.UploadHistogram.Msgsize()
|
||||
s = 1 + 12 + msgp.Int64Size + 15 + msgp.Int64Size + 12 + msgp.Int64Size + 11 + msgp.Int64Size + 13 + msgp.Int64Size + 12 + msgp.Int64Size + 8 + 1 + 16 + z.Latency.UploadHistogram.Msgsize() + 31 + msgp.Int64Size + 34 + msgp.Float64Size
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+11
-15
@@ -98,7 +98,7 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
||||
select {
|
||||
case <-hcTimer.C:
|
||||
sys.hMutex.RLock()
|
||||
var eps []madmin.ServerProperties
|
||||
eps := make([]madmin.ServerProperties, 0, len(sys.hc))
|
||||
for _, ep := range sys.hc {
|
||||
eps = append(eps, madmin.ServerProperties{Endpoint: ep.Endpoint, Scheme: ep.Scheme})
|
||||
}
|
||||
@@ -106,16 +106,12 @@ func (sys *BucketTargetSys) heartBeat(ctx context.Context) {
|
||||
|
||||
if len(eps) > 0 {
|
||||
cctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
m := map[string]epHealth{}
|
||||
m := make(map[string]epHealth, len(eps))
|
||||
for result := range sys.hcClient.Alive(cctx, madmin.AliveOpts{}, eps...) {
|
||||
var online bool
|
||||
if result.Error == nil {
|
||||
online = result.Online
|
||||
}
|
||||
m[result.Endpoint.Host] = epHealth{
|
||||
Endpoint: result.Endpoint.Host,
|
||||
Scheme: result.Endpoint.Scheme,
|
||||
Online: online,
|
||||
Online: result.Online,
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
@@ -269,18 +265,18 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
|
||||
|
||||
sys.targetsMap[bucket] = newtgts
|
||||
sys.arnRemotesMap[tgt.Arn] = clnt
|
||||
sys.updateBandwidthLimit(bucket, tgt.BandwidthLimit)
|
||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sys *BucketTargetSys) updateBandwidthLimit(bucket string, limit int64) {
|
||||
func (sys *BucketTargetSys) updateBandwidthLimit(bucket, arn string, limit int64) {
|
||||
if limit == 0 {
|
||||
globalBucketMonitor.DeleteBucket(bucket)
|
||||
globalBucketMonitor.DeleteBucketThrottle(bucket, arn)
|
||||
return
|
||||
}
|
||||
// Setup bandwidth throttling
|
||||
|
||||
globalBucketMonitor.SetBandwidthLimit(bucket, limit)
|
||||
globalBucketMonitor.SetBandwidthLimit(bucket, arn, limit)
|
||||
}
|
||||
|
||||
// RemoveTarget - removes a remote bucket target for this source bucket.
|
||||
@@ -332,7 +328,7 @@ func (sys *BucketTargetSys) RemoveTarget(ctx context.Context, bucket, arnStr str
|
||||
}
|
||||
sys.targetsMap[bucket] = targets
|
||||
delete(sys.arnRemotesMap, arnStr)
|
||||
sys.updateBandwidthLimit(bucket, 0)
|
||||
sys.updateBandwidthLimit(bucket, arnStr, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -402,7 +398,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
||||
|
||||
// No need for more if not adding anything
|
||||
if tgts == nil || tgts.Empty() {
|
||||
sys.updateBandwidthLimit(bucket, 0)
|
||||
globalBucketMonitor.DeleteBucket(bucket)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -415,7 +411,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
|
||||
continue
|
||||
}
|
||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||
sys.updateBandwidthLimit(bucket, tgt.BandwidthLimit)
|
||||
sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
|
||||
}
|
||||
sys.targetsMap[bucket] = tgts.Targets
|
||||
}
|
||||
@@ -438,7 +434,7 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
|
||||
continue
|
||||
}
|
||||
sys.arnRemotesMap[tgt.Arn] = tgtClient
|
||||
sys.updateBandwidthLimit(bucket.Name, tgt.BandwidthLimit)
|
||||
sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit)
|
||||
}
|
||||
sys.targetsMap[bucket.Name] = cfg.Targets
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/bucket/versioning"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
@@ -108,15 +108,12 @@ func (api objectAPIHandlers) PutBucketVersioningHandler(w http.ResponseWriter, r
|
||||
// We encode the xml bytes as base64 to ensure there are no encoding
|
||||
// errors.
|
||||
cfgStr := base64.StdEncoding.EncodeToString(configData)
|
||||
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
|
||||
Type: madmin.SRBucketMetaTypeVersionConfig,
|
||||
Bucket: bucket,
|
||||
Versioning: &cfgStr,
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
||||
+1
-12
@@ -297,16 +297,9 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
|
||||
return err
|
||||
}
|
||||
case config.CompressionSubSys:
|
||||
compCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
|
||||
if err != nil {
|
||||
if _, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if objAPI != nil {
|
||||
if compCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||
return fmt.Errorf("Backend does not support compression")
|
||||
}
|
||||
}
|
||||
case config.HealSubSys:
|
||||
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
||||
return err
|
||||
@@ -548,10 +541,6 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to setup Compression: %w", err)
|
||||
}
|
||||
// Validate if the object layer supports compression.
|
||||
if cmpCfg.Enabled && !objAPI.IsCompressionSupported() {
|
||||
return fmt.Errorf("Backend does not support compression")
|
||||
}
|
||||
globalCompressConfigMu.Lock()
|
||||
globalCompressConfig = cmpCfg
|
||||
globalCompressConfigMu.Unlock()
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
// Test cross domain xml handler.
|
||||
|
||||
@@ -20,8 +20,8 @@ package cmd
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ var (
|
||||
// Additional MinIO errors for SSE-C requests.
|
||||
errObjectTampered = errors.New("The requested object was modified and may be compromised")
|
||||
// error returned when invalid encryption parameters are specified
|
||||
errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object")
|
||||
errInvalidEncryptionParameters = errors.New("The encryption parameters are not applicable to this object")
|
||||
errInvalidEncryptionParametersSSEC = errors.New("SSE-C encryption parameters are not supported on this bucket")
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -19,6 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -364,6 +365,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
||||
DrivesPerSet: len(setArgs[0]),
|
||||
Endpoints: endpointList,
|
||||
CmdLine: strings.Join(args, " "),
|
||||
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||
})
|
||||
setupType = newSetupType
|
||||
return endpointServerPools, setupType, nil
|
||||
@@ -389,6 +391,7 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
||||
DrivesPerSet: len(setArgs[0]),
|
||||
Endpoints: endpointList,
|
||||
CmdLine: arg,
|
||||
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||
}); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
@@ -203,6 +203,7 @@ type PoolEndpoints struct {
|
||||
DrivesPerSet int
|
||||
Endpoints Endpoints
|
||||
CmdLine string
|
||||
Platform string
|
||||
}
|
||||
|
||||
// EndpointServerPools - list of list of endpoints
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/minio/minio/internal/sync/errgroup"
|
||||
)
|
||||
|
||||
// list all errors that can be ignore in a bucket operation.
|
||||
var bucketOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnformattedDisk)
|
||||
|
||||
// list all errors that can be ignored in a bucket metadata operation.
|
||||
var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound)
|
||||
|
||||
// markDelete creates a vol entry in .minio.sys/buckets/.deleted until site replication
|
||||
// syncs the delete to peers
|
||||
func (er erasureObjects) markDelete(ctx context.Context, bucket, prefix string) error {
|
||||
storageDisks := er.getDisks()
|
||||
g := errgroup.WithNErrs(len(storageDisks))
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range storageDisks {
|
||||
index := index
|
||||
if storageDisks[index] == nil {
|
||||
continue
|
||||
}
|
||||
g.Go(func() error {
|
||||
if err := storageDisks[index].MakeVol(ctx, pathJoin(bucket, prefix)); err != nil {
|
||||
if errors.Is(err, errVolumeExists) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}, index)
|
||||
}
|
||||
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
|
||||
return toObjectErr(err, bucket)
|
||||
}
|
||||
|
||||
// purgeDelete deletes vol entry in .minio.sys/buckets/.deleted after site replication
|
||||
// syncs the delete to peers OR on a new MakeBucket call.
|
||||
func (er erasureObjects) purgeDelete(ctx context.Context, bucket, prefix string) error {
|
||||
storageDisks := er.getDisks()
|
||||
g := errgroup.WithNErrs(len(storageDisks))
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range storageDisks {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if storageDisks[index] != nil {
|
||||
return storageDisks[index].DeleteVol(ctx, pathJoin(bucket, prefix), true)
|
||||
}
|
||||
return errDiskNotFound
|
||||
}, index)
|
||||
}
|
||||
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
|
||||
return toObjectErr(err, bucket)
|
||||
}
|
||||
|
||||
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
|
||||
func (er erasureObjects) IsNotificationSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (er erasureObjects) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsEncryptionSupported returns whether server side encryption is implemented for this layer.
|
||||
func (er erasureObjects) IsEncryptionSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCompressionSupported returns whether compression is applicable for this layer.
|
||||
func (er erasureObjects) IsCompressionSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsTaggingSupported indicates whether erasureObjects implements tagging support.
|
||||
func (er erasureObjects) IsTaggingSupported() bool {
|
||||
return true
|
||||
}
|
||||
+12
-6
@@ -937,19 +937,22 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
// We can consider an object data not reliable
|
||||
// when xl.meta is not found in read quorum disks.
|
||||
// or when xl.meta is not readable in read quorum disks.
|
||||
danglingErrsCount := func(cerrs []error) (int, int) {
|
||||
danglingErrsCount := func(cerrs []error) (int, int, int) {
|
||||
var (
|
||||
notFoundCount int
|
||||
corruptedCount int
|
||||
notFoundCount int
|
||||
corruptedCount int
|
||||
diskNotFoundCount int
|
||||
)
|
||||
for _, readErr := range cerrs {
|
||||
if errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound) {
|
||||
notFoundCount++
|
||||
} else if errors.Is(readErr, errFileCorrupt) {
|
||||
corruptedCount++
|
||||
} else if errors.Is(readErr, errDiskNotFound) {
|
||||
diskNotFoundCount++
|
||||
}
|
||||
}
|
||||
return notFoundCount, corruptedCount
|
||||
return notFoundCount, corruptedCount, diskNotFoundCount
|
||||
}
|
||||
|
||||
ndataErrs := make([]error, len(dataErrs))
|
||||
@@ -963,9 +966,12 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
}
|
||||
}
|
||||
|
||||
notFoundMetaErrs, corruptedMetaErrs := danglingErrsCount(errs)
|
||||
notFoundPartsErrs, corruptedPartsErrs := danglingErrsCount(ndataErrs)
|
||||
notFoundMetaErrs, corruptedMetaErrs, driveNotFoundMetaErrs := danglingErrsCount(errs)
|
||||
notFoundPartsErrs, corruptedPartsErrs, driveNotFoundPartsErrs := danglingErrsCount(ndataErrs)
|
||||
|
||||
if driveNotFoundMetaErrs > 0 || driveNotFoundPartsErrs > 0 {
|
||||
return validMeta, false
|
||||
}
|
||||
for _, m := range metaArr {
|
||||
if m.IsValid() {
|
||||
validMeta = m
|
||||
|
||||
@@ -428,7 +428,13 @@ func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int)
|
||||
parities[index] = -1
|
||||
continue
|
||||
}
|
||||
parities[index] = metadata.Erasure.ParityBlocks
|
||||
if !metadata.IsValid() {
|
||||
parities[index] = -1
|
||||
continue
|
||||
}
|
||||
if !metadata.Deleted {
|
||||
parities[index] = metadata.Erasure.ParityBlocks
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
+18
-12
@@ -636,7 +636,10 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
|
||||
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
|
||||
if errors.Is(reducedErr, errErasureReadQuorum) && !strings.HasPrefix(bucket, minioMetaBucket) {
|
||||
er.deleteIfDangling(ctx, bucket, object, metaArr, errs, nil, opts)
|
||||
_, derr := er.deleteIfDangling(ctx, bucket, object, metaArr, errs, nil, opts)
|
||||
if derr != nil {
|
||||
reducedErr = derr
|
||||
}
|
||||
}
|
||||
return fi, nil, nil, toObjectErr(reducedErr, bucket, object)
|
||||
}
|
||||
@@ -773,7 +776,10 @@ func renameData(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry str
|
||||
err := reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
|
||||
if err == nil {
|
||||
versions := reduceCommonVersions(diskVersions, writeQuorum)
|
||||
for _, dversions := range diskVersions {
|
||||
for index, dversions := range diskVersions {
|
||||
if errs[index] != nil {
|
||||
continue
|
||||
}
|
||||
if versions != dversions {
|
||||
versionsDisparity = true
|
||||
break
|
||||
@@ -961,7 +967,7 @@ func healObjectVersionsDisparity(bucket string, entry metaCacheEntry) error {
|
||||
|
||||
fivs, err := entry.fileInfoVersions(bucket)
|
||||
if err != nil {
|
||||
healObject(bucket, entry.name, "", madmin.HealNormalScan)
|
||||
go healObject(bucket, entry.name, "", madmin.HealNormalScan)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -971,7 +977,7 @@ func healObjectVersionsDisparity(bucket string, entry metaCacheEntry) error {
|
||||
}
|
||||
|
||||
for _, version := range fivs.Versions {
|
||||
healObject(bucket, entry.name, version.VersionID, madmin.HealNormalScan)
|
||||
go healObject(bucket, entry.name, version.VersionID, madmin.HealNormalScan)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -2000,15 +2006,13 @@ func (er erasureObjects) RestoreTransitionedObject(ctx context.Context, bucket,
|
||||
}
|
||||
|
||||
// update restore status header in the metadata
|
||||
func (er erasureObjects) updateRestoreMetadata(ctx context.Context, bucket, object string, objInfo ObjectInfo, opts ObjectOptions, rerr error) error {
|
||||
func (er erasureObjects) updateRestoreMetadata(ctx context.Context, bucket, object string, objInfo ObjectInfo, opts ObjectOptions) error {
|
||||
oi := objInfo.Clone()
|
||||
oi.metadataOnly = true // Perform only metadata updates.
|
||||
|
||||
if rerr == nil {
|
||||
oi.UserDefined[xhttp.AmzRestore] = completedRestoreObj(opts.Transition.RestoreExpiry).String()
|
||||
} else { // allow retry in the case of failure to restore
|
||||
delete(oi.UserDefined, xhttp.AmzRestore)
|
||||
}
|
||||
// allow retry in the case of failure to restore
|
||||
delete(oi.UserDefined, xhttp.AmzRestore)
|
||||
|
||||
if _, err := er.CopyObject(ctx, bucket, object, bucket, object, oi, ObjectOptions{
|
||||
VersionID: oi.VersionID,
|
||||
}, ObjectOptions{
|
||||
@@ -2024,7 +2028,10 @@ func (er erasureObjects) updateRestoreMetadata(ctx context.Context, bucket, obje
|
||||
// as in the xl.meta for this version and rehydrates the part.n into the fi.DataDir for this version as in the xl.meta
|
||||
func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket string, object string, opts ObjectOptions) error {
|
||||
setRestoreHeaderFn := func(oi ObjectInfo, rerr error) error {
|
||||
er.updateRestoreMetadata(ctx, bucket, object, oi, opts, rerr)
|
||||
if rerr == nil {
|
||||
return nil // nothing to do; restore object was successful
|
||||
}
|
||||
er.updateRestoreMetadata(ctx, bucket, object, oi, opts)
|
||||
return rerr
|
||||
}
|
||||
var oi ObjectInfo
|
||||
@@ -2048,7 +2055,6 @@ func (er erasureObjects) restoreTransitionedObject(ctx context.Context, bucket s
|
||||
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
||||
}
|
||||
pReader := NewPutObjReader(hashReader)
|
||||
ropts.UserDefined[xhttp.AmzRestore] = completedRestoreObj(opts.Transition.RestoreExpiry).String()
|
||||
_, err = er.PutObject(ctx, bucket, object, pReader, ropts)
|
||||
return setRestoreHeaderFn(oi, toObjectErr(err, bucket, object))
|
||||
}
|
||||
|
||||
+159
-127
@@ -126,7 +126,7 @@ type poolMeta struct {
|
||||
|
||||
// A decommission resumable tells us if decommission is worth
|
||||
// resuming upon restart of a cluster.
|
||||
func (p *poolMeta) returnResumablePools(n int) []PoolStatus {
|
||||
func (p *poolMeta) returnResumablePools() []PoolStatus {
|
||||
var newPools []PoolStatus
|
||||
for _, pool := range p.Pools {
|
||||
if pool.Decommission == nil {
|
||||
@@ -139,11 +139,8 @@ func (p *poolMeta) returnResumablePools(n int) []PoolStatus {
|
||||
continue
|
||||
} // In all other situations we need to resume
|
||||
newPools = append(newPools, pool)
|
||||
if n > 0 && len(newPools) == n {
|
||||
return newPools
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return newPools
|
||||
}
|
||||
|
||||
func (p *poolMeta) DecommissionComplete(idx int) bool {
|
||||
@@ -251,18 +248,6 @@ var (
|
||||
)
|
||||
|
||||
func (p *poolMeta) Decommission(idx int, pi poolSpaceInfo) error {
|
||||
for i, pool := range p.Pools {
|
||||
if idx == i {
|
||||
continue
|
||||
}
|
||||
if pool.Decommission != nil {
|
||||
// Do not allow multiple decommissions at the same time.
|
||||
// We shall for now only allow one pool decommission at
|
||||
// a time.
|
||||
return fmt.Errorf("%w at index: %d", errDecommissionAlreadyRunning, i)
|
||||
}
|
||||
}
|
||||
|
||||
// Return an error when there is decommission on going - the user needs
|
||||
// to explicitly cancel it first in order to restart decommissioning again.
|
||||
if p.Pools[idx].Decommission != nil &&
|
||||
@@ -510,7 +495,6 @@ func (z *erasureServerPools) Init(ctx context.Context) error {
|
||||
z.StartRebalance()
|
||||
|
||||
meta := poolMeta{}
|
||||
|
||||
if err := meta.load(ctx, z.serverPools[0], z.serverPools); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -524,38 +508,38 @@ func (z *erasureServerPools) Init(ctx context.Context) error {
|
||||
if !update {
|
||||
z.poolMeta = meta
|
||||
|
||||
// We are only supporting single pool decommission at this time
|
||||
// so it makes sense to only resume single pools at any given
|
||||
// time, in future meta.returnResumablePools() might take
|
||||
// '-1' as argument to decommission multiple pools at a time
|
||||
// but this is not a priority at the moment.
|
||||
for _, pool := range meta.returnResumablePools(1) {
|
||||
pools := meta.returnResumablePools()
|
||||
poolIndices := make([]int, 0, len(pools))
|
||||
for _, pool := range pools {
|
||||
idx := globalEndpoints.GetPoolIdx(pool.CmdLine)
|
||||
if idx == -1 {
|
||||
return fmt.Errorf("unexpected state present for decommission status pool(%s) not found", pool.CmdLine)
|
||||
}
|
||||
if globalEndpoints[idx].Endpoints[0].IsLocal {
|
||||
go func(pool PoolStatus) {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for {
|
||||
if err := z.Decommission(ctx, pool.ID); err != nil {
|
||||
if errors.Is(err, errDecommissionAlreadyRunning) {
|
||||
// A previous decommission running found restart it.
|
||||
poolIndices = append(poolIndices, idx)
|
||||
}
|
||||
|
||||
if len(poolIndices) > 0 && globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||
go func() {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for {
|
||||
if err := z.Decommission(ctx, poolIndices...); err != nil {
|
||||
if errors.Is(err, errDecommissionAlreadyRunning) {
|
||||
// A previous decommission running found restart it.
|
||||
for _, idx := range poolIndices {
|
||||
z.doDecommissionInRoutine(ctx, idx)
|
||||
return
|
||||
}
|
||||
if configRetriableErrors(err) {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to resume decommission of pool %v: %w: retrying..", pool, err))
|
||||
time.Sleep(time.Second + time.Duration(r.Float64()*float64(5*time.Second)))
|
||||
continue
|
||||
}
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to resume decommission of pool %v: %w", pool, err))
|
||||
return
|
||||
}
|
||||
break
|
||||
if configRetriableErrors(err) {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to resume decommission of pools %v: %w: retrying..", pools, err))
|
||||
time.Sleep(time.Second + time.Duration(r.Float64()*float64(5*time.Second)))
|
||||
continue
|
||||
}
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to resume decommission of pool %v: %w", pools, err))
|
||||
return
|
||||
}
|
||||
}(pool)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -585,7 +569,10 @@ func (z *erasureServerPools) IsDecommissionRunning() bool {
|
||||
defer z.poolMetaMutex.RUnlock()
|
||||
meta := z.poolMeta
|
||||
for _, pool := range meta.Pools {
|
||||
if pool.Decommission != nil {
|
||||
if pool.Decommission != nil &&
|
||||
!pool.Decommission.Complete &&
|
||||
!pool.Decommission.Failed &&
|
||||
!pool.Decommission.Canceled {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -686,6 +673,39 @@ func (v versionsSorter) reverse() {
|
||||
})
|
||||
}
|
||||
|
||||
func (set *erasureObjects) listObjectsToDecommission(ctx context.Context, bi decomBucketInfo, fn func(entry metaCacheEntry)) error {
|
||||
disks := set.getOnlineDisks()
|
||||
if len(disks) == 0 {
|
||||
return fmt.Errorf("no online drives found for set with endpoints %s", set.getEndpoints())
|
||||
}
|
||||
|
||||
// How to resolve partial results.
|
||||
resolver := metadataResolutionParams{
|
||||
dirQuorum: len(disks) / 2, // make sure to capture all quorum ratios
|
||||
objQuorum: len(disks) / 2, // make sure to capture all quorum ratios
|
||||
bucket: bi.Name,
|
||||
}
|
||||
|
||||
err := listPathRaw(ctx, listPathRawOptions{
|
||||
disks: disks,
|
||||
bucket: bi.Name,
|
||||
path: bi.Prefix,
|
||||
recursive: true,
|
||||
forwardTo: "",
|
||||
minDisks: len(disks) / 2, // to capture all quorum ratios
|
||||
reportNotFound: false,
|
||||
agreed: fn,
|
||||
partial: func(entries metaCacheEntries, _ []error) {
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if ok {
|
||||
fn(*entry)
|
||||
}
|
||||
},
|
||||
finished: nil,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool *erasureSets, bi decomBucketInfo) error {
|
||||
ctx = logger.SetReqInfo(ctx, &logger.ReqInfo{})
|
||||
|
||||
@@ -700,12 +720,6 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
|
||||
for _, set := range pool.sets {
|
||||
set := set
|
||||
disks := set.getOnlineDisks()
|
||||
if len(disks) == 0 {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("no online drives found for set with endpoints %s",
|
||||
set.getEndpoints()))
|
||||
continue
|
||||
}
|
||||
|
||||
vc, _ := globalBucketVersioningSys.Get(bi.Name)
|
||||
|
||||
@@ -879,39 +893,16 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
z.poolMetaMutex.Unlock()
|
||||
}
|
||||
|
||||
// How to resolve partial results.
|
||||
resolver := metadataResolutionParams{
|
||||
dirQuorum: len(disks) / 2, // make sure to capture all quorum ratios
|
||||
objQuorum: len(disks) / 2, // make sure to capture all quorum ratios
|
||||
bucket: bi.Name,
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := listPathRaw(ctx, listPathRawOptions{
|
||||
disks: disks,
|
||||
bucket: bi.Name,
|
||||
path: bi.Prefix,
|
||||
recursive: true,
|
||||
forwardTo: "",
|
||||
minDisks: len(disks) / 2, // to capture all quorum ratios
|
||||
reportNotFound: false,
|
||||
agreed: func(entry metaCacheEntry) {
|
||||
err := set.listObjectsToDecommission(ctx, bi,
|
||||
func(entry metaCacheEntry) {
|
||||
parallelWorkers <- struct{}{}
|
||||
wg.Add(1)
|
||||
go decommissionEntry(entry)
|
||||
},
|
||||
partial: func(entries metaCacheEntries, _ []error) {
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if ok {
|
||||
parallelWorkers <- struct{}{}
|
||||
wg.Add(1)
|
||||
go decommissionEntry(*entry)
|
||||
}
|
||||
},
|
||||
finished: nil,
|
||||
})
|
||||
)
|
||||
logger.LogIf(ctx, err)
|
||||
}()
|
||||
}
|
||||
@@ -992,6 +983,33 @@ func (z *erasureServerPools) decommissionInBackground(ctx context.Context, idx i
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) checkAfterDecom(ctx context.Context, idx int) error {
|
||||
buckets, err := z.getBucketsToDecommission(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pool := z.serverPools[idx]
|
||||
for _, set := range pool.sets {
|
||||
for _, bi := range buckets {
|
||||
var objectsFound int
|
||||
err := set.listObjectsToDecommission(ctx, bi, func(entry metaCacheEntry) {
|
||||
if entry.isObject() {
|
||||
objectsFound++
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if objectsFound > 0 {
|
||||
return fmt.Errorf("at least %d objects were found in bucket `%s` after decommissioning", objectsFound, bi.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) doDecommissionInRoutine(ctx context.Context, idx int) {
|
||||
z.poolMetaMutex.Lock()
|
||||
var dctx context.Context
|
||||
@@ -1008,9 +1026,19 @@ func (z *erasureServerPools) doDecommissionInRoutine(ctx context.Context, idx in
|
||||
}
|
||||
|
||||
z.poolMetaMutex.Lock()
|
||||
failed := z.poolMeta.Pools[idx].Decommission.ItemsDecommissionFailed > 0
|
||||
failed := z.poolMeta.Pools[idx].Decommission.ItemsDecommissionFailed > 0 || contextCanceled(dctx)
|
||||
poolCmdLine := z.poolMeta.Pools[idx].CmdLine
|
||||
z.poolMetaMutex.Unlock()
|
||||
|
||||
if !failed {
|
||||
logger.Info("Decommissioning complete for pool '%s', verifying for any pending objects", poolCmdLine)
|
||||
err := z.checkAfterDecom(dctx, idx)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
|
||||
if failed {
|
||||
// Decommission failed indicate as such.
|
||||
logger.LogIf(GlobalContext, z.DecommissionFailed(dctx, idx))
|
||||
@@ -1027,8 +1055,8 @@ func (z *erasureServerPools) IsSuspended(idx int) bool {
|
||||
}
|
||||
|
||||
// Decommission - start decommission session.
|
||||
func (z *erasureServerPools) Decommission(ctx context.Context, idx int) error {
|
||||
if idx < 0 {
|
||||
func (z *erasureServerPools) Decommission(ctx context.Context, indices ...int) error {
|
||||
if len(indices) == 0 {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
@@ -1037,11 +1065,15 @@ func (z *erasureServerPools) Decommission(ctx context.Context, idx int) error {
|
||||
}
|
||||
|
||||
// Make pool unwritable before decommissioning.
|
||||
if err := z.StartDecommission(ctx, idx); err != nil {
|
||||
if err := z.StartDecommission(ctx, indices...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go z.doDecommissionInRoutine(ctx, idx)
|
||||
go func() {
|
||||
for _, idx := range indices {
|
||||
z.doDecommissionInRoutine(ctx, idx)
|
||||
}
|
||||
}()
|
||||
|
||||
// Successfully started decommissioning.
|
||||
return nil
|
||||
@@ -1193,25 +1225,10 @@ func (z *erasureServerPools) CompleteDecommission(ctx context.Context, idx int)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) StartDecommission(ctx context.Context, idx int) (err error) {
|
||||
if idx < 0 {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
if z.SinglePool() {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) getBucketsToDecommission(ctx context.Context) ([]decomBucketInfo, error) {
|
||||
buckets, err := z.ListBuckets(ctx, BucketOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure to heal the buckets to ensure the new
|
||||
// pool has the new buckets, this is to avoid
|
||||
// failures later.
|
||||
for _, bucket := range buckets {
|
||||
z.HealBucket(ctx, bucket.Name, madmin.HealOpts{})
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decomBuckets := make([]decomBucketInfo, len(buckets))
|
||||
@@ -1221,6 +1238,38 @@ func (z *erasureServerPools) StartDecommission(ctx context.Context, idx int) (er
|
||||
}
|
||||
}
|
||||
|
||||
// Buckets data are dispersed in multiple zones/sets, make
|
||||
// sure to decommission the necessary metadata.
|
||||
decomBuckets = append(decomBuckets, decomBucketInfo{
|
||||
Name: minioMetaBucket,
|
||||
Prefix: minioConfigPrefix,
|
||||
})
|
||||
decomBuckets = append(decomBuckets, decomBucketInfo{
|
||||
Name: minioMetaBucket,
|
||||
Prefix: bucketMetaPrefix,
|
||||
})
|
||||
|
||||
return decomBuckets, nil
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) StartDecommission(ctx context.Context, indices ...int) (err error) {
|
||||
if len(indices) == 0 {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
if z.SinglePool() {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
decomBuckets, err := z.getBucketsToDecommission(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, bucket := range decomBuckets {
|
||||
z.HealBucket(ctx, bucket.Name, madmin.HealOpts{})
|
||||
}
|
||||
|
||||
// TODO: Support decommissioning transition tiers.
|
||||
for _, bucket := range decomBuckets {
|
||||
if lc, err := globalLifecycleSys.Get(bucket.Name); err == nil {
|
||||
@@ -1247,45 +1296,28 @@ func (z *erasureServerPools) StartDecommission(ctx context.Context, idx int) (er
|
||||
}
|
||||
}
|
||||
|
||||
// Buckets data are dispersed in multiple zones/sets, make
|
||||
// sure to decommission the necessary metadata.
|
||||
decomBuckets = append(decomBuckets, decomBucketInfo{
|
||||
Name: minioMetaBucket,
|
||||
Prefix: minioConfigPrefix,
|
||||
})
|
||||
decomBuckets = append(decomBuckets, decomBucketInfo{
|
||||
Name: minioMetaBucket,
|
||||
Prefix: bucketMetaPrefix,
|
||||
})
|
||||
|
||||
var pool *erasureSets
|
||||
for pidx := range z.serverPools {
|
||||
if pidx == idx {
|
||||
pool = z.serverPools[idx]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if pool == nil {
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
pi, err := z.getDecommissionPoolSpaceInfo(idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.poolMetaMutex.Lock()
|
||||
defer z.poolMetaMutex.Unlock()
|
||||
|
||||
if err = z.poolMeta.Decommission(idx, pi); err != nil {
|
||||
return err
|
||||
for _, idx := range indices {
|
||||
pi, err := z.getDecommissionPoolSpaceInfo(idx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = z.poolMeta.Decommission(idx, pi); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
z.poolMeta.QueueBuckets(idx, decomBuckets)
|
||||
}
|
||||
z.poolMeta.QueueBuckets(idx, decomBuckets)
|
||||
|
||||
if err = z.poolMeta.save(ctx, z.serverPools); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
globalNotificationSys.ReloadPoolMeta(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,11 @@ func (z *erasureServerPools) initRebalanceMeta(ctx context.Context, buckets []st
|
||||
}, len(z.serverPools))
|
||||
var totalCap, totalFree uint64
|
||||
for _, disk := range si.Disks {
|
||||
// Ignore invalid.
|
||||
if disk.PoolIndex < 0 || len(diskStats) <= disk.PoolIndex {
|
||||
// https://github.com/minio/minio/issues/16500
|
||||
continue
|
||||
}
|
||||
totalCap += disk.TotalSpace
|
||||
totalFree += disk.AvailableSpace
|
||||
|
||||
@@ -353,6 +358,7 @@ func (z *erasureServerPools) rebalanceBuckets(ctx context.Context, poolIdx int)
|
||||
z.rebalMu.Lock()
|
||||
z.rebalMeta.PoolStats[poolIdx].Info.Status = rebalStopped
|
||||
z.rebalMeta.PoolStats[poolIdx].Info.EndTime = now
|
||||
z.rebalMeta.cancel = nil // remove the already used context.CancelFunc
|
||||
z.rebalMu.Unlock()
|
||||
|
||||
rebalDone = true
|
||||
|
||||
+12
-56
@@ -94,11 +94,14 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
// -- Default for Standard Storage class is, parity = 3 - disks 6, 7
|
||||
// -- Default for Standard Storage class is, parity = 4 - disks 8 to 16
|
||||
if commonParityDrives == 0 {
|
||||
commonParityDrives = ecDrivesNoConfig(ep.DrivesPerSet)
|
||||
commonParityDrives, err = ecDrivesNoConfig(ep.DrivesPerSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = storageclass.ValidateParity(commonParityDrives, ep.DrivesPerSet); err != nil {
|
||||
return nil, fmt.Errorf("parity validation returned an error %w <- (%d, %d), for pool(%s)", err, commonParityDrives, ep.DrivesPerSet, humanize.Ordinal(i+1))
|
||||
return nil, fmt.Errorf("parity validation returned an error: %w <- (%d, %d), for pool(%s)", err, commonParityDrives, ep.DrivesPerSet, humanize.Ordinal(i+1))
|
||||
}
|
||||
|
||||
storageDisks[i], formats[i], err = waitForFormatErasure(local, ep.Endpoints, i+1,
|
||||
@@ -184,10 +187,8 @@ func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
|
||||
}
|
||||
res := make([]StorageAPI, 0, len(idMap))
|
||||
for _, s := range z.serverPools {
|
||||
s.erasureDisksMu.RLock()
|
||||
defer s.erasureDisksMu.RUnlock()
|
||||
for _, disks := range s.erasureDisks {
|
||||
for _, disk := range disks {
|
||||
for _, set := range s.sets {
|
||||
for _, disk := range set.getDisks() {
|
||||
if disk == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
@@ -208,8 +209,8 @@ func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
|
||||
func (z *erasureServerPools) GetRawData(ctx context.Context, volume, file string, fn func(r io.Reader, host string, disk string, filename string, info StatInfo) error) error {
|
||||
found := 0
|
||||
for _, s := range z.serverPools {
|
||||
for _, disks := range s.erasureDisks {
|
||||
for _, disk := range disks {
|
||||
for _, set := range s.sets {
|
||||
for _, disk := range set.getDisks() {
|
||||
if disk == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
@@ -1065,7 +1066,7 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
|
||||
derrs[j] = err
|
||||
}
|
||||
dobjects[j] = DeletedObject{
|
||||
ObjectName: obj.ObjectName,
|
||||
ObjectName: decodeDirObject(obj.ObjectName),
|
||||
VersionID: obj.VersionID,
|
||||
}
|
||||
return nil
|
||||
@@ -1077,7 +1078,7 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
|
||||
DeleteMarker: pinfo.ObjInfo.DeleteMarker,
|
||||
DeleteMarkerVersionID: pinfo.ObjInfo.VersionID,
|
||||
DeleteMarkerMTime: DeleteMarkerMTime{pinfo.ObjInfo.ModTime},
|
||||
ObjectName: pinfo.ObjInfo.Name,
|
||||
ObjectName: decodeDirObject(pinfo.ObjInfo.Name),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1611,30 +1612,6 @@ func (z *erasureServerPools) GetBucketInfo(ctx context.Context, bucket string, o
|
||||
return bucketInfo, nil
|
||||
}
|
||||
|
||||
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
|
||||
func (z *erasureServerPools) IsNotificationSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (z *erasureServerPools) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsEncryptionSupported returns whether server side encryption is implemented for this layer.
|
||||
func (z *erasureServerPools) IsEncryptionSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsCompressionSupported returns whether compression is applicable for this layer.
|
||||
func (z *erasureServerPools) IsCompressionSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) IsTaggingSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// DeleteBucket - deletes a bucket on all serverPools simultaneously,
|
||||
// even if one of the serverPools fail to delete buckets, we proceed to
|
||||
// undo a successful operation.
|
||||
@@ -1665,7 +1642,7 @@ func (z *erasureServerPools) DeleteBucket(ctx context.Context, bucket string, op
|
||||
// If site replication is configured, hold on to deleted bucket state until sites sync
|
||||
switch opts.SRDeleteOp {
|
||||
case MarkDelete:
|
||||
z.markDelete(context.Background(), minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix, bucket))
|
||||
z.s3Peer.MakeBucket(context.Background(), pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket), MakeBucketOptions{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1696,27 +1673,6 @@ func (z *erasureServerPools) deleteAll(ctx context.Context, bucket, prefix strin
|
||||
}
|
||||
}
|
||||
|
||||
// markDelete will create a directory of deleted bucket in .minio.sys/buckets/.deleted across all disks
|
||||
// in situations where the deleted bucket needs to be held on to until all sites are in sync for
|
||||
// site replication
|
||||
func (z *erasureServerPools) markDelete(ctx context.Context, bucket, prefix string) {
|
||||
for _, servers := range z.serverPools {
|
||||
for _, set := range servers.sets {
|
||||
set.markDelete(ctx, bucket, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// purgeDelete deletes vol entry in .minio.sys/buckets/.deleted after site replication
|
||||
// syncs the delete to peers.
|
||||
func (z *erasureServerPools) purgeDelete(ctx context.Context, bucket, prefix string) {
|
||||
for _, servers := range z.serverPools {
|
||||
for _, set := range servers.sets {
|
||||
set.purgeDelete(ctx, bucket, prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List all buckets from one of the serverPools, we are not doing merge
|
||||
// sort here just for simplification. As per design it is assumed
|
||||
// that all buckets are present on all serverPools.
|
||||
|
||||
@@ -354,10 +354,6 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
||||
endpointStrings[i] = endpoint.String()
|
||||
}
|
||||
|
||||
if defaultParityCount == 0 {
|
||||
logger.Error("Warning: Default parity set to 0. This can lead to data loss.")
|
||||
}
|
||||
|
||||
// Initialize the erasure sets instance.
|
||||
s := &erasureSets{
|
||||
sets: make([]*erasureObjects, setCount),
|
||||
@@ -722,30 +718,6 @@ func (s *erasureSets) getHashedSet(input string) (set *erasureObjects) {
|
||||
return s.sets[s.getHashedSetIndex(input)]
|
||||
}
|
||||
|
||||
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
|
||||
func (s *erasureSets) IsNotificationSupported() bool {
|
||||
return s.getHashedSet("").IsNotificationSupported()
|
||||
}
|
||||
|
||||
// IsListenSupported returns whether listen bucket notification is applicable for this layer.
|
||||
func (s *erasureSets) IsListenSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsEncryptionSupported returns whether server side encryption is implemented for this layer.
|
||||
func (s *erasureSets) IsEncryptionSupported() bool {
|
||||
return s.getHashedSet("").IsEncryptionSupported()
|
||||
}
|
||||
|
||||
// IsCompressionSupported returns whether compression is applicable for this layer.
|
||||
func (s *erasureSets) IsCompressionSupported() bool {
|
||||
return s.getHashedSet("").IsCompressionSupported()
|
||||
}
|
||||
|
||||
func (s *erasureSets) IsTaggingSupported() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// listDeletedBuckets lists deleted buckets from all disks.
|
||||
func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBuckets map[string]VolInfo, readQuorum int) error {
|
||||
g := errgroup.WithNErrs(len(storageDisks))
|
||||
|
||||
@@ -191,7 +191,12 @@ func TestNewErasureSets(t *testing.T) {
|
||||
}
|
||||
|
||||
ep := PoolEndpoints{Endpoints: endpoints}
|
||||
if _, err := newErasureSets(ctx, ep, storageDisks, format, ecDrivesNoConfig(16), 0); err != nil {
|
||||
|
||||
parity, err := ecDrivesNoConfig(16)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error during EC drive config: %v", err)
|
||||
}
|
||||
if _, err := newErasureSets(ctx, ep, storageDisks, format, parity, 0); err != nil {
|
||||
t.Fatalf("Unable to initialize erasure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ import (
|
||||
"github.com/minio/pkg/console"
|
||||
)
|
||||
|
||||
// list all errors that can be ignore in a bucket operation.
|
||||
var bucketOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnformattedDisk)
|
||||
|
||||
// list all errors that can be ignored in a bucket metadata operation.
|
||||
var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound)
|
||||
|
||||
// OfflineDisk represents an unavailable disk.
|
||||
var OfflineDisk StorageAPI // zero value is nil
|
||||
|
||||
|
||||
@@ -741,7 +741,10 @@ func fixFormatErasureV3(storageDisks []StorageAPI, endpoints Endpoints, formats
|
||||
func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount, setDriveCount int, deploymentID, distributionAlgo string, sErrs []error) (*formatErasureV3, error) {
|
||||
format := newFormatErasureV3(setCount, setDriveCount)
|
||||
formats := make([]*formatErasureV3, len(storageDisks))
|
||||
wantAtMost := ecDrivesNoConfig(setDriveCount)
|
||||
wantAtMost, err := ecDrivesNoConfig(setDriveCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < setCount; i++ {
|
||||
hostCount := make(map[string]int, setDriveCount)
|
||||
@@ -795,13 +798,12 @@ func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount,
|
||||
|
||||
// ecDrivesNoConfig returns the erasure coded drives in a set if no config has been set.
|
||||
// It will attempt to read it from env variable and fall back to drives/2.
|
||||
func ecDrivesNoConfig(setDriveCount int) int {
|
||||
sc, _ := storageclass.LookupConfig(config.KVS{}, setDriveCount)
|
||||
ecDrives := sc.GetParityForSC(storageclass.STANDARD)
|
||||
if ecDrives < 0 {
|
||||
ecDrives = storageclass.DefaultParityBlocks(setDriveCount)
|
||||
func ecDrivesNoConfig(setDriveCount int) (int, error) {
|
||||
sc, err := storageclass.LookupConfig(config.KVS{}, setDriveCount)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return ecDrives
|
||||
return sc.GetParityForSC(storageclass.STANDARD), nil
|
||||
}
|
||||
|
||||
// Make Erasure backend meta volumes.
|
||||
|
||||
@@ -306,6 +306,7 @@ func setHTTPStatsHandler(h http.Handler) http.Handler {
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, storageRESTPrefix) ||
|
||||
strings.HasPrefix(r.URL.Path, peerRESTPrefix) ||
|
||||
strings.HasPrefix(r.URL.Path, peerS3Prefix) ||
|
||||
strings.HasPrefix(r.URL.Path, lockRESTPrefix) {
|
||||
globalConnStats.incInputBytes(meteredRequest.BytesRead())
|
||||
globalConnStats.incOutputBytes(meteredResponse.BytesWritten())
|
||||
@@ -550,6 +551,9 @@ func addCustomHeaders(h http.Handler) http.Handler {
|
||||
// part of the log entry, Error response XML and auditing.
|
||||
// Set custom headers such as x-amz-request-id for each request.
|
||||
w.Header().Set(xhttp.AmzRequestID, mustGetRequestID(UTCNow()))
|
||||
if globalLocalNodeName != "" {
|
||||
w.Header().Set(xhttp.AmzRequestNodeID, globalLocalNodeNameHex)
|
||||
}
|
||||
h.ServeHTTP(xhttp.NewResponseRecorder(w), r)
|
||||
})
|
||||
}
|
||||
|
||||
+6
-4
@@ -70,8 +70,8 @@ func newBgHealSequence() *healSequence {
|
||||
}
|
||||
}
|
||||
|
||||
// getBackgroundHealStatus will return the
|
||||
func getBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.BgHealState, bool) {
|
||||
// getLocalBackgroundHealStatus will return the heal status of the local node
|
||||
func getLocalBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.BgHealState, bool) {
|
||||
if globalBackgroundHealState == nil {
|
||||
return madmin.BgHealState{}, false
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func getBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.BgHealS
|
||||
return status, true
|
||||
}
|
||||
|
||||
si := o.StorageInfo(ctx)
|
||||
si := o.LocalStorageInfo(ctx)
|
||||
|
||||
indexed := make(map[string][]madmin.Disk)
|
||||
for _, disk := range si.Disks {
|
||||
@@ -437,7 +437,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
return retErr
|
||||
}
|
||||
|
||||
// healObject heals given object path in deep to fix bitrot.
|
||||
// healObject sends the given object/version to the background healing workers
|
||||
// and only returns when healing of the object is done.
|
||||
func healObject(bucket, object, versionID string, scan madmin.HealScanMode) {
|
||||
// Get background heal sequence to send elements to heal
|
||||
globalHealStateLK.Lock()
|
||||
@@ -448,6 +449,7 @@ func healObject(bucket, object, versionID string, scan madmin.HealScanMode) {
|
||||
bucket: bucket,
|
||||
object: object,
|
||||
versionID: versionID,
|
||||
noWait: true, // do not block callers.
|
||||
opts: &madmin.HealOpts{
|
||||
Remove: healDeleteDangling, // if found dangling purge it.
|
||||
ScanMode: scan,
|
||||
|
||||
+2
-1
@@ -233,7 +233,8 @@ var (
|
||||
globalEndpoints EndpointServerPools
|
||||
|
||||
// The name of this local node, fetched from arguments
|
||||
globalLocalNodeName string
|
||||
globalLocalNodeName string
|
||||
globalLocalNodeNameHex string
|
||||
|
||||
// The global subnet config
|
||||
globalSubnetConfig subnet.Config
|
||||
|
||||
@@ -403,6 +403,12 @@ func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
desc := "Do not upgrade one server at a time - please follow the recommended guidelines mentioned here https://github.com/minio/minio#upgrading-minio for your environment"
|
||||
switch {
|
||||
case strings.HasPrefix(r.URL.Path, peerS3Prefix):
|
||||
writeErrorResponseString(r.Context(), w, APIError{
|
||||
Code: "XMinioPeerS3VersionMismatch",
|
||||
Description: desc,
|
||||
HTTPStatusCode: http.StatusUpgradeRequired,
|
||||
}, r.URL)
|
||||
case strings.HasPrefix(r.URL.Path, peerRESTPrefix):
|
||||
writeErrorResponseString(r.Context(), w, APIError{
|
||||
Code: "XMinioPeerVersionMismatch",
|
||||
|
||||
@@ -20,7 +20,7 @@ package cmd
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+7
-3
@@ -2093,14 +2093,14 @@ func (store *IAMStoreSys) AddServiceAccount(ctx context.Context, cred auth.Crede
|
||||
if su, found := cache.iamUsersMap[accessKey]; found {
|
||||
scred := su.Credentials
|
||||
if scred.ParentUser != parentUser {
|
||||
return updatedAt, errIAMServiceAccountUsed
|
||||
return updatedAt, fmt.Errorf("%w: the service account access key is taken by another user", errIAMServiceAccountNotAllowed)
|
||||
}
|
||||
return updatedAt, errIAMServiceAccount
|
||||
return updatedAt, fmt.Errorf("%w: the service account access key already taken", errIAMServiceAccountNotAllowed)
|
||||
}
|
||||
|
||||
// Parent user must not be a service account.
|
||||
if u, found := cache.iamUsersMap[parentUser]; found && u.Credentials.IsServiceAccount() {
|
||||
return updatedAt, errIAMServiceAccount
|
||||
return updatedAt, fmt.Errorf("%w: unable to create a service account for another service account", errIAMServiceAccountNotAllowed)
|
||||
}
|
||||
|
||||
u := newUserIdentity(cred)
|
||||
@@ -2133,6 +2133,10 @@ func (store *IAMStoreSys) UpdateServiceAccount(ctx context.Context, accessKey st
|
||||
cr.SecretKey = opts.secretKey
|
||||
}
|
||||
|
||||
if opts.comment != "" {
|
||||
cr.Comment = opts.comment
|
||||
}
|
||||
|
||||
switch opts.status {
|
||||
// The caller did not ask to update status account, do nothing
|
||||
case "":
|
||||
|
||||
@@ -919,6 +919,7 @@ type newServiceAccountOpts struct {
|
||||
sessionPolicy *iampolicy.Policy
|
||||
accessKey string
|
||||
secretKey string
|
||||
comment string
|
||||
|
||||
claims map[string]interface{}
|
||||
}
|
||||
@@ -989,6 +990,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
|
||||
cred.ParentUser = parentUser
|
||||
cred.Groups = groups
|
||||
cred.Status = string(auth.AccountOn)
|
||||
cred.Comment = opts.comment
|
||||
|
||||
updatedAt, err := sys.store.AddServiceAccount(ctx, cred)
|
||||
if err != nil {
|
||||
@@ -1003,6 +1005,7 @@ type updateServiceAccountOpts struct {
|
||||
sessionPolicy *iampolicy.Policy
|
||||
secretKey string
|
||||
status string
|
||||
comment string
|
||||
}
|
||||
|
||||
// UpdateServiceAccount - edit a service account
|
||||
|
||||
+1
-1
@@ -20,10 +20,10 @@ package cmd
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
const (
|
||||
licUpdateCycle = 24 * time.Hour * 30
|
||||
licRenewURL = "https://subnet.min.io/api/cluster/renew-license"
|
||||
licRenewURLDev = "http://localhost:9000/api/cluster/renew-license"
|
||||
)
|
||||
|
||||
// initlicenseUpdateJob start the periodic license update job in the background.
|
||||
func initLicenseUpdateJob(ctx context.Context, objAPI ObjectLayer) {
|
||||
go func() {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
// Leader node (that successfully acquires the lock inside licenceUpdaterLoop)
|
||||
// will keep performing the license update. If the leader goes down for some
|
||||
// reason, the lock will be released and another node will acquire it and
|
||||
// take over because of this loop.
|
||||
for {
|
||||
licenceUpdaterLoop(ctx, objAPI)
|
||||
|
||||
// license update stopped for some reason.
|
||||
// sleep for some time and try again.
|
||||
duration := time.Duration(r.Float64() * float64(time.Hour))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func licenceUpdaterLoop(ctx context.Context, objAPI ObjectLayer) {
|
||||
ctx, cancel := globalLeaderLock.GetLock(ctx)
|
||||
defer cancel()
|
||||
|
||||
licenseUpdateTimer := time.NewTimer(licUpdateCycle)
|
||||
defer licenseUpdateTimer.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-licenseUpdateTimer.C:
|
||||
|
||||
if globalSubnetConfig.Registered() {
|
||||
performLicenseUpdate(ctx, objAPI)
|
||||
}
|
||||
|
||||
// Reset the timer for next cycle.
|
||||
licenseUpdateTimer.Reset(licUpdateCycle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func performLicenseUpdate(ctx context.Context, objectAPI ObjectLayer) {
|
||||
// the subnet license renewal api renews the license only
|
||||
// if required e.g. when it is expiring soon
|
||||
url := licRenewURL
|
||||
if globalIsCICD {
|
||||
url = licRenewURLDev
|
||||
}
|
||||
|
||||
resp, err := globalSubnetConfig.Post(url, nil)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("error from %s: %w", url, err))
|
||||
return
|
||||
}
|
||||
|
||||
r := gjson.Parse(resp).Get("license")
|
||||
if r.Index == 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("license not found in response from %s", url))
|
||||
return
|
||||
}
|
||||
|
||||
lic := r.String()
|
||||
if lic == globalSubnetConfig.License {
|
||||
// license hasn't changed.
|
||||
return
|
||||
}
|
||||
|
||||
kv := "subnet license=" + lic
|
||||
result, err := setConfigKV(ctx, objectAPI, []byte(kv))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("error setting subnet license config: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if result.Dynamic {
|
||||
if err := applyDynamicConfigForSubSys(GlobalContext, objectAPI, result.Cfg, result.SubSys); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("error applying subnet dynamic config: %w", err))
|
||||
return
|
||||
}
|
||||
globalNotificationSys.SignalConfigReload(result.SubSys)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -22,10 +22,10 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/pubsub"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
@@ -41,16 +41,6 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsNotificationSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsListenSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucketName := vars["bucket"]
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -189,6 +189,43 @@ func (e *metaCacheEntry) isLatestDeletemarker() bool {
|
||||
return xlMeta.versions[0].header.Type == DeleteType
|
||||
}
|
||||
|
||||
// isAllFreeVersions returns if all objects are free versions.
|
||||
// If metadata is NOT versioned false will always be returned.
|
||||
// If v2 and UNABLE to load metadata true will be returned.
|
||||
func (e *metaCacheEntry) isAllFreeVersions() bool {
|
||||
if e.cached != nil {
|
||||
if len(e.cached.versions) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, v := range e.cached.versions {
|
||||
if !v.header.FreeVersion() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !isXL2V1Format(e.metadata) {
|
||||
return false
|
||||
}
|
||||
if meta, _, err := isIndexedMetaV2(e.metadata); meta != nil {
|
||||
return meta.AllHidden(false)
|
||||
} else if err != nil {
|
||||
return true
|
||||
}
|
||||
// Fall back...
|
||||
xlMeta, err := e.xlmeta()
|
||||
if err != nil || len(xlMeta.versions) == 0 {
|
||||
return true
|
||||
}
|
||||
// Check versions..
|
||||
for _, v := range e.cached.versions {
|
||||
if !v.header.FreeVersion() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// fileInfo returns the decoded metadata.
|
||||
// If entry is a directory it is returned as that.
|
||||
// If versioned the latest version will be returned.
|
||||
|
||||
@@ -377,7 +377,12 @@ func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCa
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case out <- obj:
|
||||
queueReplicationHeal(ctx, o.Bucket, objInfo, o.Replication)
|
||||
if fiv, err := obj.fileInfoVersions(o.Bucket); err == nil {
|
||||
for _, version := range fiv.Versions {
|
||||
objInfo := version.ToObjectInfo(o.Bucket, obj.name, versioned)
|
||||
queueReplicationHeal(ctx, o.Bucket, objInfo, o.Replication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +367,10 @@ func (r *metacacheReader) filter(o listPathOptions) (entries metaCacheEntriesSor
|
||||
return true
|
||||
}
|
||||
if !o.InclDeleted && entry.isObject() && entry.isLatestDeletemarker() && !entry.isObjectDir() {
|
||||
return entries.len() < o.Limit
|
||||
return true
|
||||
}
|
||||
if entry.isAllFreeVersions() {
|
||||
return true
|
||||
}
|
||||
entries.o = append(entries.o, entry)
|
||||
return entries.len() < o.Limit
|
||||
|
||||
@@ -93,7 +93,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
if opts.Limit <= 0 {
|
||||
return
|
||||
}
|
||||
if m, _, _ := isIndexedMetaV2(metadata); m != nil && !m.IsLatestDeleteMarker() {
|
||||
if m, _, _ := isIndexedMetaV2(metadata); m != nil && !m.AllHidden(true) {
|
||||
objsReturned++
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ package cmd
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/env"
|
||||
)
|
||||
|
||||
|
||||
+3
-1
@@ -1802,6 +1802,7 @@ func getBucketUsageMetrics() *MetricsGroup {
|
||||
metrics = make([]Metric, 0, 50)
|
||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objLayer)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1947,8 +1948,9 @@ func getClusterTierMetrics() *MetricsGroup {
|
||||
return
|
||||
}
|
||||
|
||||
dui, err := loadDataUsageFromBackend(GlobalContext, objLayer)
|
||||
dui, err := loadDataUsageFromBackend(ctx, objLayer)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
// data usage has not captured any tier stats yet.
|
||||
|
||||
@@ -32,6 +32,11 @@ func GetTotalCapacity(diskInfo []madmin.Disk) (capacity uint64) {
|
||||
// GetTotalUsableCapacity gets the total usable capacity in the cluster.
|
||||
func GetTotalUsableCapacity(diskInfo []madmin.Disk, s StorageInfo) (capacity uint64) {
|
||||
for _, disk := range diskInfo {
|
||||
// Ignore invalid.
|
||||
if disk.PoolIndex < 0 || len(s.Backend.StandardSCData) <= disk.PoolIndex {
|
||||
// https://github.com/minio/minio/issues/16500
|
||||
continue
|
||||
}
|
||||
// Ignore parity disks
|
||||
if disk.DiskIndex < s.Backend.StandardSCData[disk.PoolIndex] {
|
||||
capacity += disk.TotalSpace
|
||||
@@ -51,6 +56,11 @@ func GetTotalCapacityFree(diskInfo []madmin.Disk) (capacity uint64) {
|
||||
// GetTotalUsableCapacityFree gets the total usable capacity free in the cluster.
|
||||
func GetTotalUsableCapacityFree(diskInfo []madmin.Disk, s StorageInfo) (capacity uint64) {
|
||||
for _, disk := range diskInfo {
|
||||
// Ignore invalid.
|
||||
if disk.PoolIndex < 0 || len(s.Backend.StandardSCData) <= disk.PoolIndex {
|
||||
// https://github.com/minio/minio/issues/16500
|
||||
continue
|
||||
}
|
||||
// Ignore parity disks
|
||||
if disk.DiskIndex < s.Backend.StandardSCData[disk.PoolIndex] {
|
||||
capacity += disk.AvailableSpace
|
||||
|
||||
+32
-12
@@ -33,7 +33,7 @@ import (
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
bucketBandwidth "github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/sync/errgroup"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
@@ -649,6 +649,15 @@ func (sys *NotificationSys) StopRebalance(ctx context.Context) {
|
||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo), nErr.Err)
|
||||
}
|
||||
}
|
||||
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
logger.LogIf(ctx, errServerNotInitialized)
|
||||
return
|
||||
}
|
||||
if pools, ok := objAPI.(*erasureServerPools); ok {
|
||||
pools.StopRebalance()
|
||||
}
|
||||
}
|
||||
|
||||
// LoadRebalanceMeta notifies all peers to load rebalance.bin from object layer.
|
||||
@@ -1095,8 +1104,8 @@ func NewNotificationSys(endpoints EndpointServerPools) *NotificationSys {
|
||||
}
|
||||
|
||||
// GetBandwidthReports - gets the bandwidth report from all nodes including self.
|
||||
func (sys *NotificationSys) GetBandwidthReports(ctx context.Context, buckets ...string) madmin.BucketBandwidthReport {
|
||||
reports := make([]*madmin.BucketBandwidthReport, len(sys.peerClients))
|
||||
func (sys *NotificationSys) GetBandwidthReports(ctx context.Context, buckets ...string) bandwidth.BucketBandwidthReport {
|
||||
reports := make([]*bandwidth.BucketBandwidthReport, len(sys.peerClients))
|
||||
g := errgroup.WithNErrs(len(sys.peerClients))
|
||||
for index := range sys.peerClients {
|
||||
if sys.peerClients[index] == nil {
|
||||
@@ -1116,9 +1125,9 @@ func (sys *NotificationSys) GetBandwidthReports(ctx context.Context, buckets ...
|
||||
ctx := logger.SetReqInfo(ctx, reqInfo)
|
||||
logger.LogOnceIf(ctx, err, sys.peerClients[index].host.String())
|
||||
}
|
||||
reports = append(reports, globalBucketMonitor.GetReport(bucketBandwidth.SelectBuckets(buckets...)))
|
||||
consolidatedReport := madmin.BucketBandwidthReport{
|
||||
BucketStats: make(map[string]madmin.BandwidthDetails),
|
||||
reports = append(reports, globalBucketMonitor.GetReport(bandwidth.SelectBuckets(buckets...)))
|
||||
consolidatedReport := bandwidth.BucketBandwidthReport{
|
||||
BucketStats: make(map[string]map[string]bandwidth.Details),
|
||||
}
|
||||
for _, report := range reports {
|
||||
if report == nil || report.BucketStats == nil {
|
||||
@@ -1127,15 +1136,26 @@ func (sys *NotificationSys) GetBandwidthReports(ctx context.Context, buckets ...
|
||||
for bucket := range report.BucketStats {
|
||||
d, ok := consolidatedReport.BucketStats[bucket]
|
||||
if !ok {
|
||||
consolidatedReport.BucketStats[bucket] = madmin.BandwidthDetails{}
|
||||
consolidatedReport.BucketStats[bucket] = make(map[string]bandwidth.Details)
|
||||
d = consolidatedReport.BucketStats[bucket]
|
||||
d.LimitInBytesPerSecond = report.BucketStats[bucket].LimitInBytesPerSecond
|
||||
for arn := range d {
|
||||
d[arn] = bandwidth.Details{
|
||||
LimitInBytesPerSecond: report.BucketStats[bucket][arn].LimitInBytesPerSecond,
|
||||
}
|
||||
}
|
||||
}
|
||||
if d.LimitInBytesPerSecond < report.BucketStats[bucket].LimitInBytesPerSecond {
|
||||
d.LimitInBytesPerSecond = report.BucketStats[bucket].LimitInBytesPerSecond
|
||||
for arn, st := range report.BucketStats[bucket] {
|
||||
bwDet := bandwidth.Details{}
|
||||
if bw, ok := d[arn]; ok {
|
||||
bwDet = bw
|
||||
}
|
||||
if bwDet.LimitInBytesPerSecond < st.LimitInBytesPerSecond {
|
||||
bwDet.LimitInBytesPerSecond = st.LimitInBytesPerSecond
|
||||
}
|
||||
bwDet.CurrentBandwidthInBytesPerSecond += st.CurrentBandwidthInBytesPerSecond
|
||||
d[arn] = bwDet
|
||||
consolidatedReport.BucketStats[bucket] = d
|
||||
}
|
||||
d.CurrentBandwidthInBytesPerSecond += report.BucketStats[bucket].CurrentBandwidthInBytesPerSecond
|
||||
consolidatedReport.BucketStats[bucket] = d
|
||||
}
|
||||
}
|
||||
return consolidatedReport
|
||||
|
||||
@@ -239,12 +239,6 @@ type ObjectLayer interface {
|
||||
AbortMultipartUpload(ctx context.Context, bucket, object, uploadID string, opts ObjectOptions) error
|
||||
CompleteMultipartUpload(ctx context.Context, bucket, object, uploadID string, uploadedParts []CompletePart, opts ObjectOptions) (objInfo ObjectInfo, err error)
|
||||
|
||||
// Supported operations check
|
||||
IsNotificationSupported() bool
|
||||
IsListenSupported() bool
|
||||
IsEncryptionSupported() bool
|
||||
IsTaggingSupported() bool
|
||||
IsCompressionSupported() bool
|
||||
SetDriveCounts() []int // list of erasure stripe size for each pool in order.
|
||||
|
||||
// Healing operations.
|
||||
|
||||
+243
-311
@@ -34,7 +34,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/klauspost/compress/gzhttp"
|
||||
miniogo "github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
@@ -57,6 +56,7 @@ import (
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/s3select"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
@@ -116,11 +116,6 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object, err := unescapePath(vars["object"])
|
||||
@@ -224,11 +219,9 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
||||
// filter object lock metadata if permission does not permit
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
actualSize, err := objInfo.GetActualSize()
|
||||
@@ -288,25 +281,23 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
||||
}
|
||||
|
||||
// Set encryption response headers
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
|
||||
case crypto.S3:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||
case crypto.S3KMS:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsID, objInfo.KMSKeyID())
|
||||
if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok {
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx)
|
||||
}
|
||||
case crypto.SSEC:
|
||||
// Validate the SSE-C Key set in the header.
|
||||
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
|
||||
case crypto.S3:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||
case crypto.S3KMS:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsID, objInfo.KMSKeyID())
|
||||
if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok {
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx)
|
||||
}
|
||||
case crypto.SSEC:
|
||||
// Validate the SSE-C Key set in the header.
|
||||
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
}
|
||||
|
||||
s3Select.Evaluate(w)
|
||||
@@ -328,10 +319,6 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
opts, err := getOpts(ctx, r, bucket, object)
|
||||
if err != nil {
|
||||
@@ -401,18 +388,13 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL)
|
||||
return
|
||||
}
|
||||
if rangeErr != nil {
|
||||
logger.LogIf(ctx, rangeErr, logger.Application)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate pre-conditions if any.
|
||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
|
||||
return checkPreconditions(ctx, w, r, oi, opts)
|
||||
@@ -431,7 +413,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
reader, proxy, perr = proxyGetToReplicationTarget(ctx, bucket, object, rs, r.Header, opts, proxytgts)
|
||||
if perr != nil {
|
||||
proxyGetErr := ErrorRespToObjectError(perr, bucket, object)
|
||||
if !isErrObjectNotFound(proxyGetErr) && !isErrVersionNotFound(proxyGetErr) &&
|
||||
if !isErrBucketNotFound(proxyGetErr) && !isErrObjectNotFound(proxyGetErr) && !isErrVersionNotFound(proxyGetErr) &&
|
||||
!isErrPreconditionFailed(proxyGetErr) && !isErrInvalidRange(proxyGetErr) {
|
||||
logger.LogIf(ctx, fmt.Errorf("Proxying request (replication) failed for %s/%s(%s) - %w", bucket, object, opts.VersionID, perr))
|
||||
}
|
||||
@@ -519,20 +501,18 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
// Set encryption response headers
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
|
||||
case crypto.S3:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||
case crypto.S3KMS:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsID, objInfo.KMSKeyID())
|
||||
if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok {
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx)
|
||||
}
|
||||
case crypto.SSEC:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
|
||||
case crypto.S3:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||
case crypto.S3KMS:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsID, objInfo.KMSKeyID())
|
||||
if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok {
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx)
|
||||
}
|
||||
case crypto.SSEC:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
}
|
||||
|
||||
if r.Header.Get(xhttp.AmzChecksumMode) == "ENABLED" {
|
||||
@@ -632,10 +612,6 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest))
|
||||
return
|
||||
}
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
getObjectInfo := objectAPI.GetObjectInfo
|
||||
if api.CacheAPI() != nil {
|
||||
@@ -780,11 +756,9 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
||||
// filter object lock metadata if permission does not permit
|
||||
objInfo.UserDefined = objectlock.FilterObjectLockMetadata(objInfo.UserDefined, getRetPerms != ErrNone, legalHoldPerms != ErrNone)
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
if _, err = DecryptObjectInfo(&objInfo, r); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate pre-conditions if any.
|
||||
@@ -813,25 +787,23 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
|
||||
}
|
||||
|
||||
// Set encryption response headers
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
|
||||
case crypto.S3:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||
case crypto.S3KMS:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsID, objInfo.KMSKeyID())
|
||||
if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok {
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx)
|
||||
}
|
||||
case crypto.SSEC:
|
||||
// Validate the SSE-C Key set in the header.
|
||||
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
switch kind, _ := crypto.IsEncrypted(objInfo.UserDefined); kind {
|
||||
case crypto.S3:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionAES)
|
||||
case crypto.S3KMS:
|
||||
w.Header().Set(xhttp.AmzServerSideEncryption, xhttp.AmzEncryptionKMS)
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsID, objInfo.KMSKeyID())
|
||||
if kmsCtx, ok := objInfo.UserDefined[crypto.MetaContext]; ok {
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionKmsContext, kmsCtx)
|
||||
}
|
||||
case crypto.SSEC:
|
||||
// Validate the SSE-C Key set in the header.
|
||||
if _, err = crypto.SSEC.UnsealObjectKey(r.Header, objInfo.UserDefined, bucket, object); err != nil {
|
||||
writeErrorResponseHeadersOnly(w, toAPIError(ctx, err))
|
||||
return
|
||||
}
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerAlgorithm))
|
||||
w.Header().Set(xhttp.AmzServerSideEncryptionCustomerKeyMD5, r.Header.Get(xhttp.AmzServerSideEncryptionCustomerKeyMD5))
|
||||
}
|
||||
|
||||
if r.Header.Get(xhttp.AmzChecksumMode) == "ENABLED" {
|
||||
@@ -1016,13 +988,6 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) {
|
||||
if !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
dstBucket := vars["bucket"]
|
||||
dstObject, err := unescapePath(vars["object"])
|
||||
@@ -1128,11 +1093,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
checkCopyPrecondFn := func(o ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&o, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if _, err := DecryptObjectInfo(&o, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
return checkCopyObjectPreconditions(ctx, w, r, o)
|
||||
}
|
||||
@@ -1214,8 +1177,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
var compressMetadata map[string]string
|
||||
// No need to compress for remote etcd calls
|
||||
// Pass the decompressed stream to such calls.
|
||||
isDstCompressed := objectAPI.IsCompressionSupported() &&
|
||||
isCompressible(r.Header, dstObject) &&
|
||||
isDstCompressed := isCompressible(r.Header, dstObject) &&
|
||||
length > minCompressibleSize &&
|
||||
!isRemoteCopyRequired(ctx, srcBucket, dstBucket, objectAPI) && !cpSrcDstSame && !objectEncryption
|
||||
if isDstCompressed {
|
||||
@@ -1225,7 +1187,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
compressMetadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10)
|
||||
|
||||
reader = etag.NewReader(reader, nil)
|
||||
wantEncryption := objectAPI.IsEncryptionSupported() && crypto.Requested(r.Header)
|
||||
wantEncryption := crypto.Requested(r.Header)
|
||||
s2c, cb := newS2CompressReader(reader, actualSize, wantEncryption)
|
||||
dstOpts.IndexCB = cb
|
||||
defer s2c.Close()
|
||||
@@ -1247,133 +1209,131 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// Handle encryption
|
||||
encMetadata := make(map[string]string)
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
// Encryption parameters not applicable for this object.
|
||||
if _, ok := crypto.IsEncrypted(srcInfo.UserDefined); !ok && crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
// Encryption parameters not applicable for this object.
|
||||
if _, ok := crypto.IsEncrypted(srcInfo.UserDefined); !ok && crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
return
|
||||
}
|
||||
// Encryption parameters not present for this object.
|
||||
if crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && !crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidSSECustomerAlgorithm), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var oldKey, newKey []byte
|
||||
var newKeyID string
|
||||
var kmsCtx kms.Context
|
||||
var objEncKey crypto.ObjectKey
|
||||
sseCopyKMS := crypto.S3KMS.IsEncrypted(srcInfo.UserDefined)
|
||||
sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined)
|
||||
sseCopyC := crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header)
|
||||
sseC := crypto.SSEC.IsRequested(r.Header)
|
||||
sseS3 := crypto.S3.IsRequested(r.Header)
|
||||
sseKMS := crypto.S3KMS.IsRequested(r.Header)
|
||||
|
||||
isSourceEncrypted := sseCopyC || sseCopyS3 || sseCopyKMS
|
||||
isTargetEncrypted := sseC || sseS3 || sseKMS
|
||||
|
||||
if sseC {
|
||||
newKey, err = ParseSSECustomerRequest(r)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
// Encryption parameters not present for this object.
|
||||
if crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && !crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidSSECustomerAlgorithm), r.URL)
|
||||
}
|
||||
if crypto.S3KMS.IsRequested(r.Header) {
|
||||
newKeyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(r.Header)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If src == dst and either
|
||||
// - the object is encrypted using SSE-C and two different SSE-C keys are present
|
||||
// - the object is encrypted using SSE-S3 and the SSE-S3 header is present
|
||||
// - the object storage class is not changing
|
||||
// then execute a key rotation.
|
||||
if cpSrcDstSame && (sseCopyC && sseC) && !chStorageClass {
|
||||
oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var oldKey, newKey []byte
|
||||
var newKeyID string
|
||||
var kmsCtx kms.Context
|
||||
var objEncKey crypto.ObjectKey
|
||||
sseCopyKMS := crypto.S3KMS.IsEncrypted(srcInfo.UserDefined)
|
||||
sseCopyS3 := crypto.S3.IsEncrypted(srcInfo.UserDefined)
|
||||
sseCopyC := crypto.SSEC.IsEncrypted(srcInfo.UserDefined) && crypto.SSECopy.IsRequested(r.Header)
|
||||
sseC := crypto.SSEC.IsRequested(r.Header)
|
||||
sseS3 := crypto.S3.IsRequested(r.Header)
|
||||
sseKMS := crypto.S3KMS.IsRequested(r.Header)
|
||||
|
||||
isSourceEncrypted := sseCopyC || sseCopyS3 || sseCopyKMS
|
||||
isTargetEncrypted := sseC || sseS3 || sseKMS
|
||||
|
||||
if sseC {
|
||||
newKey, err = ParseSSECustomerRequest(r)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
if crypto.S3KMS.IsRequested(r.Header) {
|
||||
newKeyID, kmsCtx, err = crypto.S3KMS.ParseHTTP(r.Header)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
for k, v := range srcInfo.UserDefined {
|
||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||
encMetadata[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// If src == dst and either
|
||||
// - the object is encrypted using SSE-C and two different SSE-C keys are present
|
||||
// - the object is encrypted using SSE-S3 and the SSE-S3 header is present
|
||||
// - the object storage class is not changing
|
||||
// then execute a key rotation.
|
||||
if cpSrcDstSame && (sseCopyC && sseC) && !chStorageClass {
|
||||
oldKey, err = ParseSSECopyCustomerRequest(r.Header, srcInfo.UserDefined)
|
||||
if err = rotateKey(ctx, oldKey, newKeyID, newKey, srcBucket, srcObject, encMetadata, kmsCtx); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Since we are rotating the keys, make sure to update the metadata.
|
||||
srcInfo.metadataOnly = true
|
||||
srcInfo.keyRotation = true
|
||||
} else {
|
||||
if isSourceEncrypted || isTargetEncrypted {
|
||||
// We are not only copying just metadata instead
|
||||
// we are creating a new object at this point, even
|
||||
// if source and destination are same objects.
|
||||
if !srcInfo.keyRotation {
|
||||
srcInfo.metadataOnly = false
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the size of the target object
|
||||
var targetSize int64
|
||||
|
||||
switch {
|
||||
case isDstCompressed:
|
||||
targetSize = -1
|
||||
case !isSourceEncrypted && !isTargetEncrypted:
|
||||
targetSize, _ = srcInfo.GetActualSize()
|
||||
case isSourceEncrypted && isTargetEncrypted:
|
||||
objInfo := ObjectInfo{Size: actualSize}
|
||||
targetSize = objInfo.EncryptedSize()
|
||||
case !isSourceEncrypted && isTargetEncrypted:
|
||||
targetSize = srcInfo.EncryptedSize()
|
||||
case isSourceEncrypted && !isTargetEncrypted:
|
||||
targetSize, _ = srcInfo.DecryptedSize()
|
||||
}
|
||||
|
||||
if isTargetEncrypted {
|
||||
var encReader io.Reader
|
||||
kind, _ := crypto.IsRequested(r.Header)
|
||||
encReader, objEncKey, err = newEncryptReader(ctx, srcInfo.Reader, kind, newKeyID, newKey, dstBucket, dstObject, encMetadata, kmsCtx)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
reader = etag.Wrap(encReader, srcInfo.Reader)
|
||||
}
|
||||
|
||||
for k, v := range srcInfo.UserDefined {
|
||||
if strings.HasPrefix(strings.ToLower(k), ReservedMetadataPrefixLower) {
|
||||
encMetadata[k] = v
|
||||
}
|
||||
}
|
||||
if isSourceEncrypted {
|
||||
// Remove all source encrypted related metadata to
|
||||
// avoid copying them in target object.
|
||||
crypto.RemoveInternalEntries(srcInfo.UserDefined)
|
||||
}
|
||||
|
||||
if err = rotateKey(ctx, oldKey, newKeyID, newKey, srcBucket, srcObject, encMetadata, kmsCtx); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
// do not try to verify encrypted content
|
||||
srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Since we are rotating the keys, make sure to update the metadata.
|
||||
srcInfo.metadataOnly = true
|
||||
srcInfo.keyRotation = true
|
||||
} else {
|
||||
if isSourceEncrypted || isTargetEncrypted {
|
||||
// We are not only copying just metadata instead
|
||||
// we are creating a new object at this point, even
|
||||
// if source and destination are same objects.
|
||||
if !srcInfo.keyRotation {
|
||||
srcInfo.metadataOnly = false
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the size of the target object
|
||||
var targetSize int64
|
||||
|
||||
switch {
|
||||
case isDstCompressed:
|
||||
targetSize = -1
|
||||
case !isSourceEncrypted && !isTargetEncrypted:
|
||||
targetSize, _ = srcInfo.GetActualSize()
|
||||
case isSourceEncrypted && isTargetEncrypted:
|
||||
objInfo := ObjectInfo{Size: actualSize}
|
||||
targetSize = objInfo.EncryptedSize()
|
||||
case !isSourceEncrypted && isTargetEncrypted:
|
||||
targetSize = srcInfo.EncryptedSize()
|
||||
case isSourceEncrypted && !isTargetEncrypted:
|
||||
targetSize, _ = srcInfo.DecryptedSize()
|
||||
}
|
||||
|
||||
if isTargetEncrypted {
|
||||
var encReader io.Reader
|
||||
kind, _ := crypto.IsRequested(r.Header)
|
||||
encReader, objEncKey, err = newEncryptReader(ctx, srcInfo.Reader, kind, newKeyID, newKey, dstBucket, dstObject, encMetadata, kmsCtx)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
reader = etag.Wrap(encReader, srcInfo.Reader)
|
||||
}
|
||||
|
||||
if isSourceEncrypted {
|
||||
// Remove all source encrypted related metadata to
|
||||
// avoid copying them in target object.
|
||||
crypto.RemoveInternalEntries(srcInfo.UserDefined)
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", actualSize)
|
||||
if isTargetEncrypted {
|
||||
pReader, err = pReader.WithEncryption(srcInfo.Reader, &objEncKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if isTargetEncrypted {
|
||||
pReader, err = pReader.WithEncryption(srcInfo.Reader, &objEncKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if dstOpts.IndexCB != nil {
|
||||
dstOpts.IndexCB = compressionIndexEncrypter(objEncKey, dstOpts.IndexCB)
|
||||
}
|
||||
if dstOpts.IndexCB != nil {
|
||||
dstOpts.IndexCB = compressionIndexEncrypter(objEncKey, dstOpts.IndexCB)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1630,13 +1590,6 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) {
|
||||
if !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object, err := unescapePath(vars["object"])
|
||||
@@ -1699,11 +1652,6 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
if objTags := r.Header.Get(xhttp.AmzObjectTagging); objTags != "" {
|
||||
if !objectAPI.IsTaggingSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := tags.ParseObjectTags(objTags); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -1773,7 +1721,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
actualSize := size
|
||||
var idxCb func() []byte
|
||||
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > minCompressibleSize {
|
||||
if isCompressible(r.Header, object) && size > minCompressibleSize {
|
||||
// Storing the compression metadata.
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10)
|
||||
@@ -1789,7 +1737,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
// Set compression metrics.
|
||||
var s2c io.ReadCloser
|
||||
wantEncryption := objectAPI.IsEncryptionSupported() && crypto.Requested(r.Header)
|
||||
wantEncryption := crypto.Requested(r.Header)
|
||||
s2c, idxCb = newS2CompressReader(actualReader, actualSize, wantEncryption)
|
||||
defer s2c.Close()
|
||||
|
||||
@@ -1822,11 +1770,9 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
|
||||
if !opts.MTime.IsZero() && opts.PreserveETag != "" {
|
||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
return checkPreconditionsPUT(ctx, w, r, oi, opts)
|
||||
}
|
||||
@@ -1863,41 +1809,44 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
metadata[ReservedMetadataPrefixLower+ReplicationStatus] = dsc.PendingStatus()
|
||||
}
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.Requested(r.Header) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
wantSize := int64(-1)
|
||||
if size >= 0 {
|
||||
info := ObjectInfo{Size: size}
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if opts.IndexCB != nil {
|
||||
opts.IndexCB = compressionIndexEncrypter(objectEncryptionKey, opts.IndexCB)
|
||||
}
|
||||
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||
if crypto.Requested(r.Header) {
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParameters), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
wantSize := int64(-1)
|
||||
if size >= 0 {
|
||||
info := ObjectInfo{Size: size}
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if opts.IndexCB != nil {
|
||||
opts.IndexCB = compressionIndexEncrypter(objectEncryptionKey, opts.IndexCB)
|
||||
}
|
||||
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||
}
|
||||
|
||||
// Ensure that metadata does not contain sensitive information
|
||||
@@ -1919,7 +1868,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if r.Header.Get(xMinIOExtract) == "true" && strings.HasSuffix(object, archiveExt) {
|
||||
if r.Header.Get(xMinIOExtract) == "true" && HasSuffix(object, archiveExt) {
|
||||
opts := ObjectOptions{VersionID: objInfo.VersionID, MTime: objInfo.ModTime}
|
||||
if _, err := updateObjectMetadataWithZipInfo(ctx, objectAPI, bucket, object, opts); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
@@ -2001,13 +1950,6 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) {
|
||||
if !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object, err := unescapePath(vars["object"])
|
||||
@@ -2145,7 +2087,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
|
||||
actualSize := size
|
||||
var idxCb func() []byte
|
||||
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > minCompressibleSize {
|
||||
if isCompressible(r.Header, object) && size > minCompressibleSize {
|
||||
// Storing the compression metadata.
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10)
|
||||
@@ -2156,7 +2098,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
// Set compression metrics.
|
||||
wantEncryption := objectAPI.IsEncryptionSupported() && crypto.Requested(r.Header)
|
||||
wantEncryption := crypto.Requested(r.Header)
|
||||
s2c, cb := newS2CompressReader(actualReader, actualSize, wantEncryption)
|
||||
defer s2c.Close()
|
||||
idxCb = cb
|
||||
@@ -2212,37 +2154,39 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.Requested(r.Header) && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
return errInvalidEncryptionParameters
|
||||
}
|
||||
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wantSize := int64(-1)
|
||||
if size >= 0 {
|
||||
info := ObjectInfo{Size: size}
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if crypto.Requested(r.Header) {
|
||||
if crypto.SSECopy.IsRequested(r.Header) {
|
||||
return errInvalidEncryptionParameters
|
||||
}
|
||||
if opts.IndexCB != nil {
|
||||
opts.IndexCB = compressionIndexEncrypter(objectEncryptionKey, opts.IndexCB)
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
return errInvalidEncryptionParametersSSEC
|
||||
}
|
||||
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wantSize := int64(-1)
|
||||
if size >= 0 {
|
||||
info := ObjectInfo{Size: size}
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if opts.IndexCB != nil {
|
||||
opts.IndexCB = compressionIndexEncrypter(objectEncryptionKey, opts.IndexCB)
|
||||
}
|
||||
|
||||
// Ensure that metadata does not contain sensitive information
|
||||
@@ -2261,6 +2205,7 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var opts untarOptions
|
||||
opts.ignoreDirs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreDirs), "true")
|
||||
opts.ignoreErrs = strings.EqualFold(r.Header.Get(xhttp.MinIOSnowballIgnoreErrors), "true")
|
||||
@@ -2827,11 +2772,6 @@ func (api objectAPIHandlers) GetObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if !objAPI.IsTaggingSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Allow getObjectTagging if policy action is set.
|
||||
if s3Error := checkRequestAuthType(ctx, r, policy.GetObjectTaggingAction, bucket, object); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
|
||||
@@ -2889,10 +2829,6 @@ func (api objectAPIHandlers) PutObjectTaggingHandler(w http.ResponseWriter, r *h
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
if !objAPI.IsTaggingSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Tags XML will not be bigger than 1MiB in size, fail if its bigger.
|
||||
tags, err := tags.ParseObjectXML(io.LimitReader(r.Body, 1<<20))
|
||||
@@ -2971,10 +2907,6 @@ func (api objectAPIHandlers) DeleteObjectTaggingHandler(w http.ResponseWriter, r
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
if !objAPI.IsTaggingSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
@@ -3119,7 +3051,7 @@ func (api objectAPIHandlers) PostRestoreObjectHandler(w http.ResponseWriter, r *
|
||||
}
|
||||
}
|
||||
// set or upgrade restore expiry
|
||||
restoreExpiry := lifecycle.ExpectedExpiryTime(time.Now(), rreq.Days)
|
||||
restoreExpiry := lifecycle.ExpectedExpiryTime(time.Now().UTC(), rreq.Days)
|
||||
metadata := cloneMSS(objInfo.UserDefined)
|
||||
|
||||
// update self with restore metadata
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/v7/pkg/encrypt"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/amztime"
|
||||
@@ -47,6 +46,7 @@ import (
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
"github.com/minio/sio"
|
||||
@@ -71,13 +71,6 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) {
|
||||
if !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object, err := unescapePath(vars["object"])
|
||||
@@ -107,16 +100,19 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
|
||||
encMetadata := map[string]string{}
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if crypto.Requested(r.Header) {
|
||||
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
// Set this for multipart only operations, we need to differentiate during
|
||||
// decryption if the file was actually multipart or not.
|
||||
encMetadata[ReservedMetadataPrefix+"Encrypted-Multipart"] = ""
|
||||
if crypto.Requested(r.Header) {
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
// Set this for multipart only operations, we need to differentiate during
|
||||
// decryption if the file was actually multipart or not.
|
||||
encMetadata[ReservedMetadataPrefix+"Encrypted-Multipart"] = ""
|
||||
}
|
||||
|
||||
// Extract metadata that needs to be saved.
|
||||
@@ -127,11 +123,6 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
}
|
||||
|
||||
if objTags := r.Header.Get(xhttp.AmzObjectTagging); objTags != "" {
|
||||
if !objectAPI.IsTaggingSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := tags.ParseObjectTags(objTags); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
@@ -176,7 +167,7 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
// Ensure that metadata does not contain sensitive information
|
||||
crypto.RemoveSensitiveEntries(metadata)
|
||||
|
||||
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) {
|
||||
if isCompressible(r.Header, object) {
|
||||
// Storing the compression metadata.
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
}
|
||||
@@ -189,11 +180,9 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
|
||||
if !opts.MTime.IsZero() && opts.PreserveETag != "" {
|
||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
return checkPreconditionsPUT(ctx, w, r, oi, opts)
|
||||
}
|
||||
@@ -245,11 +234,6 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
dstBucket := vars["bucket"]
|
||||
dstObject, err := unescapePath(vars["object"])
|
||||
@@ -351,11 +335,9 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
}
|
||||
|
||||
checkCopyPartPrecondFn := func(o ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&o, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if _, err := DecryptObjectInfo(&o, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if checkCopyObjectPartPreconditions(ctx, w, r, o) {
|
||||
return true
|
||||
@@ -462,7 +444,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
// Compress only if the compression is enabled during initial multipart.
|
||||
var idxCb func() []byte
|
||||
if isCompressed {
|
||||
wantEncryption := objectAPI.IsEncryptionSupported() && crypto.Requested(r.Header)
|
||||
wantEncryption := crypto.Requested(r.Header)
|
||||
s2c, cb := newS2CompressReader(reader, actualPartSize, wantEncryption)
|
||||
idxCb = cb
|
||||
defer s2c.Close()
|
||||
@@ -488,7 +470,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
|
||||
_, isEncrypted := crypto.IsEncrypted(mi.UserDefined)
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if objectAPI.IsEncryptionSupported() && isEncrypted {
|
||||
if isEncrypted {
|
||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL)
|
||||
return
|
||||
@@ -578,13 +560,6 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.Requested(r.Header) {
|
||||
if !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucket := vars["bucket"]
|
||||
object, err := unescapePath(vars["object"])
|
||||
@@ -711,7 +686,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
_, isCompressed := mi.UserDefined[ReservedMetadataPrefix+"compression"]
|
||||
|
||||
var idxCb func() []byte
|
||||
if objectAPI.IsCompressionSupported() && isCompressed {
|
||||
if isCompressed {
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
@@ -723,7 +698,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
// Set compression metrics.
|
||||
wantEncryption := objectAPI.IsEncryptionSupported() && crypto.Requested(r.Header)
|
||||
wantEncryption := crypto.Requested(r.Header)
|
||||
s2c, cb := newS2CompressReader(actualReader, actualSize, wantEncryption)
|
||||
idxCb = cb
|
||||
defer s2c.Close()
|
||||
@@ -747,7 +722,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
|
||||
_, isEncrypted := crypto.IsEncrypted(mi.UserDefined)
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if objectAPI.IsEncryptionSupported() && isEncrypted {
|
||||
if isEncrypted {
|
||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL)
|
||||
return
|
||||
@@ -1023,7 +998,7 @@ func (api objectAPIHandlers) CompleteMultipartUploadHandler(w http.ResponseWrite
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if r.Header.Get(xMinIOExtract) == "true" && strings.HasSuffix(object, archiveExt) {
|
||||
if r.Header.Get(xMinIOExtract) == "true" && HasSuffix(object, archiveExt) {
|
||||
opts := ObjectOptions{VersionID: objInfo.VersionID, MTime: objInfo.ModTime}
|
||||
if _, err := updateObjectMetadataWithZipInfo(ctx, objectAPI, bucket, object, opts); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
@@ -1157,7 +1132,7 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
|
||||
// Due to AWS S3, SSE-S3 encrypted parts return the plaintext ETag
|
||||
// being the content MD5 of that particular part. This is not the
|
||||
// case for SSE-C and SSE-KMS objects.
|
||||
if kind, ok := crypto.IsEncrypted(listPartsInfo.UserDefined); ok && objectAPI.IsEncryptionSupported() {
|
||||
if kind, ok := crypto.IsEncrypted(listPartsInfo.UserDefined); ok {
|
||||
var objectEncryptionKey []byte
|
||||
if kind == crypto.S3 {
|
||||
objectEncryptionKey, err = decryptObjectMeta(nil, bucket, object, listPartsInfo.UserDefined)
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/event"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -814,7 +815,7 @@ func newPeerRESTClient(peer *xnet.Host) *peerRESTClient {
|
||||
}
|
||||
|
||||
// MonitorBandwidth - send http trace request to peer nodes
|
||||
func (client *peerRESTClient) MonitorBandwidth(ctx context.Context, buckets []string) (*madmin.BucketBandwidthReport, error) {
|
||||
func (client *peerRESTClient) MonitorBandwidth(ctx context.Context, buckets []string) (*bandwidth.BucketBandwidthReport, error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTBuckets, strings.Join(buckets, ","))
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodGetBandwidth, values, nil, -1)
|
||||
@@ -824,7 +825,7 @@ func (client *peerRESTClient) MonitorBandwidth(ctx context.Context, buckets []st
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
dec := gob.NewDecoder(respBody)
|
||||
var bandwidthReport madmin.BucketBandwidthReport
|
||||
var bandwidthReport bandwidth.BucketBandwidthReport
|
||||
err = dec.Decode(&bandwidthReport)
|
||||
return &bandwidthReport, err
|
||||
}
|
||||
|
||||
@@ -30,13 +30,13 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
b "github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/logger/message/log"
|
||||
"github.com/minio/minio/internal/pubsub"
|
||||
"github.com/minio/mux"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
@@ -1029,7 +1029,7 @@ func (s *peerRESTServer) BackgroundHealStatusHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
ctx := newContext(r, w, "BackgroundHealStatus")
|
||||
|
||||
state, ok := getBackgroundHealStatus(ctx, newObjectLayerFn())
|
||||
state, ok := getLocalBackgroundHealStatus(ctx, newObjectLayerFn())
|
||||
if !ok {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
|
||||
@@ -24,16 +24,16 @@ import (
|
||||
"net/http"
|
||||
"sort"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/sync/errgroup"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
const (
|
||||
peerS3Version = "v1" // First implementation
|
||||
|
||||
peerS3VersionPrefix = SlashSeparator + peerS3Version
|
||||
peerS3Prefix = minioReservedBucketPath + "/peer"
|
||||
peerS3Prefix = minioReservedBucketPath + "/peer-s3"
|
||||
peerS3Path = peerS3Prefix + peerS3VersionPrefix
|
||||
)
|
||||
|
||||
|
||||
@@ -61,6 +61,11 @@ func rebalanceStatus(ctx context.Context, z *erasureServerPools) (r rebalanceAdm
|
||||
TotalSpace uint64
|
||||
}, len(z.serverPools))
|
||||
for _, disk := range si.Disks {
|
||||
// Ignore invalid.
|
||||
if disk.PoolIndex < 0 || len(diskStats) <= disk.PoolIndex {
|
||||
// https://github.com/minio/minio/issues/16500
|
||||
continue
|
||||
}
|
||||
diskStats[disk.PoolIndex].AvailableSpace += disk.AvailableSpace
|
||||
diskStats[disk.PoolIndex].TotalSpace += disk.TotalSpace
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ package cmd
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/mux"
|
||||
)
|
||||
|
||||
// Composed function registering routers for only distributed Erasure setup.
|
||||
@@ -73,7 +73,7 @@ var globalHandlers = []mux.MiddlewareFunc{
|
||||
|
||||
// configureServer handler returns final handler for the http server.
|
||||
func configureServerHandler(endpointServerPools EndpointServerPools) (http.Handler, error) {
|
||||
// Initialize router. `SkipClean(true)` stops gorilla/mux from
|
||||
// Initialize router. `SkipClean(true)` stops minio/mux from
|
||||
// normalizing URL path minio/minio#3256
|
||||
router := mux.NewRouter().SkipClean(true).UseEncodedPath()
|
||||
|
||||
|
||||
+3
-13
@@ -67,10 +67,6 @@ func (api objectAPIHandlers) getObjectInArchiveFileHandler(ctx context.Context,
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
zipPath, object, err := splitZipExtensionPath(object)
|
||||
if err != nil {
|
||||
@@ -135,11 +131,9 @@ func (api objectAPIHandlers) getObjectInArchiveFileHandler(ctx context.Context,
|
||||
|
||||
// Validate pre-conditions if any.
|
||||
opts.CheckPrecondFn = func(oi ObjectInfo) bool {
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
if _, err := DecryptObjectInfo(&oi, r); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return true
|
||||
}
|
||||
|
||||
return checkPreconditions(ctx, w, r, oi, opts)
|
||||
@@ -381,10 +375,6 @@ func (api objectAPIHandlers) headObjectInArchiveFileHandler(ctx context.Context,
|
||||
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrBadRequest))
|
||||
return
|
||||
}
|
||||
if crypto.Requested(r.Header) && !objectAPI.IsEncryptionSupported() {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrBadRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
zipPath, object, err := splitZipExtensionPath(object)
|
||||
if err != nil {
|
||||
|
||||
+14
-2
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -19,6 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -42,6 +43,7 @@ import (
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/hash/sha256"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/certs"
|
||||
@@ -221,6 +223,8 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
logger.FatalIf(err, "Invalid command line arguments")
|
||||
|
||||
globalLocalNodeName = GetLocalPeer(globalEndpoints, globalMinioHost, globalMinioPort)
|
||||
nodeNameSum := sha256.Sum256([]byte(globalLocalNodeNameHex))
|
||||
globalLocalNodeNameHex = hex.EncodeToString(nodeNameSum[:])
|
||||
|
||||
globalRemoteEndpoints = make(map[string]Endpoint)
|
||||
for _, z := range globalEndpoints {
|
||||
@@ -585,7 +589,7 @@ func serverMain(ctx *cli.Context) {
|
||||
UseIdleTimeout(ctx.Duration("idle-timeout")).
|
||||
UseReadHeaderTimeout(ctx.Duration("read-header-timeout")).
|
||||
UseBaseContext(GlobalContext).
|
||||
UseCustomLogger(log.New(io.Discard, "", 0)) // Turn-off random logging by Go stdlib
|
||||
UseCustomLogger(log.New(&goHTTPLogger{}, "", 0))
|
||||
|
||||
go func() {
|
||||
globalHTTPServerErrorCh <- httpServer.Start(GlobalContext)
|
||||
@@ -720,8 +724,16 @@ func serverMain(ctx *cli.Context) {
|
||||
setCacheObjectLayer(cacheAPI)
|
||||
}
|
||||
|
||||
// Initialize the license update job
|
||||
initLicenseUpdateJob(GlobalContext, newObject)
|
||||
|
||||
// Prints the formatted startup message, if err is not nil then it prints additional information as well.
|
||||
printStartupMessage(getAPIEndpoints(), err)
|
||||
|
||||
// Print a warning at the end of the startup banner so it is more noticeable
|
||||
if globalStorageClass.GetParityForSC("") == 0 {
|
||||
logger.Error("Warning: The standard parity is set to 0. This can lead to data loss.")
|
||||
}
|
||||
}()
|
||||
|
||||
region := globalSite.Region
|
||||
|
||||
@@ -280,7 +280,7 @@ func parseSignV4(v4Auth string, region string, stype serviceType) (sv signValues
|
||||
signV4Values := signValues{}
|
||||
|
||||
var s3Err APIErrorCode
|
||||
// Save credentail values.
|
||||
// Save credential values.
|
||||
signV4Values.Credential, s3Err = parseCredentialHeader(strings.TrimSpace(credElement), region, stype)
|
||||
if s3Err != ErrNone {
|
||||
return sv, s3Err
|
||||
|
||||
+28
-39
@@ -26,7 +26,6 @@ import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
@@ -1185,6 +1184,7 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change
|
||||
secretKey: change.Create.SecretKey,
|
||||
sessionPolicy: sp,
|
||||
claims: change.Create.Claims,
|
||||
comment: change.Create.Comment,
|
||||
}
|
||||
_, _, err = globalIAMSys.NewServiceAccount(ctx, change.Create.Parent, change.Create.Groups, opts)
|
||||
if err != nil {
|
||||
@@ -1209,6 +1209,7 @@ func (c *SiteReplicationSys) PeerSvcAccChangeHandler(ctx context.Context, change
|
||||
opts := updateServiceAccountOpts{
|
||||
secretKey: change.Update.SecretKey,
|
||||
status: change.Update.Status,
|
||||
comment: change.Update.Comment,
|
||||
sessionPolicy: sp,
|
||||
}
|
||||
|
||||
@@ -1870,6 +1871,7 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context) error {
|
||||
Claims: claims,
|
||||
SessionPolicy: json.RawMessage(policyJSON),
|
||||
Status: acc.Credentials.Status,
|
||||
Comment: acc.Credentials.Comment,
|
||||
},
|
||||
},
|
||||
UpdatedAt: acc.UpdatedAt,
|
||||
@@ -3618,20 +3620,6 @@ func (c *SiteReplicationSys) PeerEditReq(ctx context.Context, arg madmin.PeerInf
|
||||
const siteHealTimeInterval = 10 * time.Second
|
||||
|
||||
func (c *SiteReplicationSys) startHealRoutine(ctx context.Context, objAPI ObjectLayer) {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
// Run the site replication healing in a loop
|
||||
for {
|
||||
c.healRoutine(ctx, objAPI)
|
||||
duration := time.Duration(r.Float64() * float64(time.Minute))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SiteReplicationSys) healRoutine(ctx context.Context, objAPI ObjectLayer) {
|
||||
ctx, cancel := globalLeaderLock.GetLock(ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -3809,7 +3797,7 @@ func (c *SiteReplicationSys) healTagMetadata(ctx context.Context, objAPI ObjectL
|
||||
}
|
||||
if dID == globalDeploymentID {
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, latestTaggingConfigBytes); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing tagging metadata from peer site %s : %w", latestPeerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal tagging metadata from peer site %s : %w", latestPeerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -3826,7 +3814,7 @@ func (c *SiteReplicationSys) healTagMetadata(ctx context.Context, objAPI ObjectL
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, c.annotatePeerErr(peerName, replicateBucketMetadata,
|
||||
fmt.Errorf("Error healing tagging metadata for peer %s from peer %s : %w", peerName, latestPeerName, err)))
|
||||
fmt.Errorf("Unable to heal tagging metadata for peer %s from peer %s : %w", peerName, latestPeerName, err)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -3873,7 +3861,7 @@ func (c *SiteReplicationSys) healBucketPolicies(ctx context.Context, objAPI Obje
|
||||
}
|
||||
if dID == globalDeploymentID {
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, latestIAMPolicy); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing bucket policy metadata from peer site %s : %w", latestPeerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal bucket policy metadata from peer site %s : %w", latestPeerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -3890,7 +3878,7 @@ func (c *SiteReplicationSys) healBucketPolicies(ctx context.Context, objAPI Obje
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, c.annotatePeerErr(peerName, replicateBucketMetadata,
|
||||
fmt.Errorf("Error healing bucket policy metadata for peer %s from peer %s : %w",
|
||||
fmt.Errorf("Unable to heal bucket policy metadata for peer %s from peer %s : %w",
|
||||
peerName, latestPeerName, err)))
|
||||
}
|
||||
}
|
||||
@@ -3948,7 +3936,7 @@ func (c *SiteReplicationSys) healBucketQuotaConfig(ctx context.Context, objAPI O
|
||||
}
|
||||
if dID == globalDeploymentID {
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, latestQuotaConfigBytes); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing quota metadata from peer site %s : %w", latestPeerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal quota metadata from peer site %s : %w", latestPeerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -3966,7 +3954,7 @@ func (c *SiteReplicationSys) healBucketQuotaConfig(ctx context.Context, objAPI O
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, c.annotatePeerErr(peerName, replicateBucketMetadata,
|
||||
fmt.Errorf("Error healing quota config metadata for peer %s from peer %s : %w",
|
||||
fmt.Errorf("Unable to heal quota config metadata for peer %s from peer %s : %w",
|
||||
peerName, latestPeerName, err)))
|
||||
}
|
||||
}
|
||||
@@ -4023,7 +4011,7 @@ func (c *SiteReplicationSys) healVersioningMetadata(ctx context.Context, objAPI
|
||||
}
|
||||
if dID == globalDeploymentID {
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, latestVersioningConfigBytes); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing versioning metadata from peer site %s : %w", latestPeerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal versioning metadata from peer site %s : %w", latestPeerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -4041,7 +4029,7 @@ func (c *SiteReplicationSys) healVersioningMetadata(ctx context.Context, objAPI
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, c.annotatePeerErr(peerName, replicateBucketMetadata,
|
||||
fmt.Errorf("Error healing versioning config metadata for peer %s from peer %s : %w",
|
||||
fmt.Errorf("Unable to heal versioning config metadata for peer %s from peer %s : %w",
|
||||
peerName, latestPeerName, err)))
|
||||
}
|
||||
}
|
||||
@@ -4098,7 +4086,7 @@ func (c *SiteReplicationSys) healSSEMetadata(ctx context.Context, objAPI ObjectL
|
||||
}
|
||||
if dID == globalDeploymentID {
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, latestSSEConfigBytes); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing sse metadata from peer site %s : %w", latestPeerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal sse metadata from peer site %s : %w", latestPeerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -4116,7 +4104,7 @@ func (c *SiteReplicationSys) healSSEMetadata(ctx context.Context, objAPI ObjectL
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, c.annotatePeerErr(peerName, replicateBucketMetadata,
|
||||
fmt.Errorf("Error healing SSE config metadata for peer %s from peer %s : %w",
|
||||
fmt.Errorf("Unable to heal SSE config metadata for peer %s from peer %s : %w",
|
||||
peerName, latestPeerName, err)))
|
||||
}
|
||||
}
|
||||
@@ -4173,7 +4161,7 @@ func (c *SiteReplicationSys) healOLockConfigMetadata(ctx context.Context, objAPI
|
||||
}
|
||||
if dID == globalDeploymentID {
|
||||
if _, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, latestObjLockConfigBytes); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing objectlock config metadata from peer site %s : %w", latestPeerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal objectlock config metadata from peer site %s : %w", latestPeerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -4191,7 +4179,7 @@ func (c *SiteReplicationSys) healOLockConfigMetadata(ctx context.Context, objAPI
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, c.annotatePeerErr(peerName, replicateBucketMetadata,
|
||||
fmt.Errorf("Error healing object lock config metadata for peer %s from peer %s : %w",
|
||||
fmt.Errorf("Unable to heal object lock config metadata for peer %s from peer %s : %w",
|
||||
peerName, latestPeerName, err)))
|
||||
}
|
||||
}
|
||||
@@ -4203,7 +4191,7 @@ func (c *SiteReplicationSys) purgeDeletedBucket(ctx context.Context, objAPI Obje
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
z.purgeDelete(context.Background(), minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix, bucket))
|
||||
z.s3Peer.DeleteBucket(context.Background(), pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket), DeleteBucketOptions{})
|
||||
}
|
||||
|
||||
// healBucket creates/deletes the bucket according to latest state across clusters participating in site replication.
|
||||
@@ -4493,7 +4481,7 @@ func (c *SiteReplicationSys) healPolicies(ctx context.Context, objAPI ObjectLaye
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -4553,7 +4541,7 @@ func (c *SiteReplicationSys) healUserPolicies(ctx context.Context, objAPI Object
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -4615,7 +4603,7 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -4676,13 +4664,13 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
if creds.IsServiceAccount() {
|
||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
continue
|
||||
}
|
||||
|
||||
_, policy, err := globalIAMSys.GetServiceAccount(ctx, creds.AccessKey)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -4690,7 +4678,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
if policy != nil {
|
||||
policyJSON, err = json.Marshal(policy)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -4706,18 +4694,19 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
Claims: claims,
|
||||
SessionPolicy: json.RawMessage(policyJSON),
|
||||
Status: creds.Status,
|
||||
Comment: creds.Comment,
|
||||
},
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if creds.IsTemp() && !creds.IsExpired() {
|
||||
u, err := globalIAMSys.GetUserInfo(ctx, creds.ParentUser)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
continue
|
||||
}
|
||||
// Call hook for site replication.
|
||||
@@ -4732,7 +4721,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -4748,7 +4737,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -4812,7 +4801,7 @@ func (c *SiteReplicationSys) healGroups(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Error healing group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -38,13 +38,13 @@ import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
|
||||
jwtreq "github.com/golang-jwt/jwt/v4/request"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/config"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
xjwt "github.com/minio/minio/internal/jwt"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/mux"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
)
|
||||
|
||||
|
||||
+11
-22
@@ -29,13 +29,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/config/identity/openid"
|
||||
"github.com/minio/minio/internal/hash/sha256"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
"github.com/minio/pkg/wildcard"
|
||||
)
|
||||
@@ -282,7 +282,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Call hook for site replication.
|
||||
if cred.ParentUser != globalActiveCred.AccessKey {
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
@@ -291,9 +291,7 @@ func (sts *stsAPIHandlers) AssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
ParentUser: cred.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
assumeRoleResponse := &AssumeRoleResponse{
|
||||
@@ -484,7 +482,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
@@ -494,9 +492,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
|
||||
ParentPolicyMapping: policyName,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}))
|
||||
|
||||
var encodedSuccessResponse []byte
|
||||
switch action {
|
||||
@@ -657,7 +653,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
@@ -666,9 +662,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
|
||||
ParentUser: cred.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}))
|
||||
|
||||
ldapIdentityResponse := &AssumeRoleWithLDAPResponse{
|
||||
Result: LDAPIdentityResult{
|
||||
@@ -820,7 +814,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: tmpCredentials.AccessKey,
|
||||
@@ -830,9 +824,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCertificate(w http.ResponseWriter, r *h
|
||||
ParentPolicyMapping: policyName,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}))
|
||||
|
||||
response := new(AssumeRoleWithCertificateResponse)
|
||||
response.Result.Credentials = tmpCredentials
|
||||
@@ -943,7 +935,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: tmpCredentials.AccessKey,
|
||||
@@ -952,10 +944,7 @@ func (sts *stsAPIHandlers) AssumeRoleWithCustomToken(w http.ResponseWriter, r *h
|
||||
ParentUser: tmpCredentials.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}))
|
||||
|
||||
response := new(AssumeRoleWithCustomTokenResponse)
|
||||
response.Result.Credentials = tmpCredentials
|
||||
|
||||
@@ -58,7 +58,6 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/signer"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
@@ -66,6 +65,7 @@ import (
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/bucket/policy"
|
||||
)
|
||||
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
iampolicy "github.com/minio/pkg/iam/policy"
|
||||
)
|
||||
|
||||
|
||||
+1
-4
@@ -102,10 +102,7 @@ var errTooManyPolicies = errors.New("Only a single policy may be specified here.
|
||||
var errIAMActionNotAllowed = errors.New("Specified IAM action is not allowed")
|
||||
|
||||
// error returned in IAM service account
|
||||
var errIAMServiceAccount = errors.New("Specified service account cannot be updated in this API call")
|
||||
|
||||
// error returned in IAM service account is already used.
|
||||
var errIAMServiceAccountUsed = errors.New("Specified service account is used by another user")
|
||||
var errIAMServiceAccountNotAllowed = errors.New("Specified service account action is not allowed")
|
||||
|
||||
// error returned in IAM subsystem when IAM sub-system is still being initialized.
|
||||
var errIAMNotInitialized = errors.New("IAM sub-system is being initialized, please try again")
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"path"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cosnicolaou/pbzip2"
|
||||
"github.com/klauspost/compress/s2"
|
||||
@@ -259,6 +260,12 @@ func untar(ctx context.Context, r io.Reader, putObject func(reader io.Reader, in
|
||||
continue
|
||||
}
|
||||
|
||||
// If zero or earlier modtime, set to current.
|
||||
// Otherwise the resulting objects will be invalid.
|
||||
if header.ModTime.UnixNano() <= 0 {
|
||||
header.ModTime = time.Now()
|
||||
}
|
||||
|
||||
// Sync upload.
|
||||
rc := disconnectReader{r: tarReader}
|
||||
if err := putObject(&rc, header.FileInfo(), name); err != nil {
|
||||
|
||||
@@ -41,6 +41,7 @@ import (
|
||||
"github.com/minio/pkg/env"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
"github.com/minio/selfupdate"
|
||||
gopsutilcpu "github.com/shirou/gopsutil/v3/cpu"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -277,6 +278,18 @@ func getUserAgent(mode string) string {
|
||||
}
|
||||
}
|
||||
|
||||
if cpus, err := gopsutilcpu.Info(); err == nil && len(cpus) > 0 {
|
||||
cpuMap := make(map[string]struct{}, len(cpus))
|
||||
coreMap := make(map[string]struct{}, len(cpus))
|
||||
for i := range cpus {
|
||||
cpuMap[cpus[i].PhysicalID] = struct{}{}
|
||||
coreMap[cpus[i].CoreID] = struct{}{}
|
||||
}
|
||||
cpu := cpus[0]
|
||||
uaAppend(" CPU ", fmt.Sprintf("(total_cpus:%d, total_cores:%d; vendor:%s; family:%s; model:%s; stepping:%d; model_name:%s)",
|
||||
len(cpuMap), len(coreMap), cpu.VendorID, cpu.Family, cpu.Model, cpu.Stepping, cpu.ModelName))
|
||||
}
|
||||
|
||||
return strings.Join(userAgentParts, "")
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -179,7 +179,7 @@ func TestUserAgent(t *testing.T) {
|
||||
if IsDocker() {
|
||||
expectedStr = strings.ReplaceAll(expectedStr, "; source", "; docker; source")
|
||||
}
|
||||
if str != expectedStr {
|
||||
if !strings.Contains(str, expectedStr) {
|
||||
t.Errorf("Test %d: expected: %s, got: %s", i+1, expectedStr, str)
|
||||
}
|
||||
globalIsCICD = sci
|
||||
|
||||
+13
-3
@@ -43,7 +43,6 @@ import (
|
||||
"github.com/coreos/go-oidc"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/felixge/fgprof"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/madmin-go/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
miniogopolicy "github.com/minio/minio-go/v7/pkg/policy"
|
||||
@@ -60,6 +59,7 @@ import (
|
||||
"github.com/minio/minio/internal/logger/message/audit"
|
||||
"github.com/minio/minio/internal/mcontext"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/certs"
|
||||
"github.com/minio/pkg/env"
|
||||
xnet "github.com/minio/pkg/net"
|
||||
@@ -608,7 +608,7 @@ func NewCustomHTTPProxyTransport() func() *http.Transport {
|
||||
func NewHTTPTransportWithClientCerts(clientCert, clientKey string) *http.Transport {
|
||||
s := xhttp.ConnSettings{
|
||||
DNSCache: globalDNSCache,
|
||||
DialTimeout: 1 * time.Minute,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
EnableHTTP2: false,
|
||||
}
|
||||
@@ -676,7 +676,7 @@ func NewRemoteTargetHTTPTransport() func() *http.Transport {
|
||||
DialContext: newCustomDialContext(),
|
||||
RootCAs: globalRootCAs,
|
||||
EnableHTTP2: false,
|
||||
}.NewCustomHTTPProxyTransport()
|
||||
}.NewRemoteTargetHTTPTransport()
|
||||
}
|
||||
|
||||
// Load the json (typically from disk file).
|
||||
@@ -1245,3 +1245,13 @@ func MockOpenIDTestUserInteraction(ctx context.Context, pro OpenIDClientAppParam
|
||||
// fmt.Printf("TOKEN: %s\n", rawIDToken)
|
||||
return rawIDToken, nil
|
||||
}
|
||||
|
||||
// Custom logger for golang http logs
|
||||
type goHTTPLogger struct{}
|
||||
|
||||
func (goHTTPLogger) Write(p []byte) (int, error) {
|
||||
// Golang std logger ensures that the error message will be sent
|
||||
// in one Write call so it is okay to print it directly.
|
||||
logger.LogOnceIf(context.Background(), fmt.Errorf("%s", string(p)), "go-http-logging")
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
@@ -2129,3 +2129,34 @@ func (x xlMetaBuf) IsLatestDeleteMarker() bool {
|
||||
})
|
||||
return isDeleteMarker
|
||||
}
|
||||
|
||||
// AllHidden returns true are no versions that would show up in a listing (ie all free markers)
|
||||
// Optionally also return early if top is a delete marker.
|
||||
func (x xlMetaBuf) AllHidden(topDeleteMarker bool) bool {
|
||||
vers, headerV, _, buf, err := decodeXLHeaders(x)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if vers == 0 {
|
||||
return true
|
||||
}
|
||||
hidden := true
|
||||
|
||||
var xl xlMetaV2VersionHeader
|
||||
_ = decodeVersions(buf, vers, func(idx int, hdr, _ []byte) error {
|
||||
if _, err := xl.unmarshalV(headerV, hdr); err != nil {
|
||||
return errDoneForNow
|
||||
}
|
||||
if topDeleteMarker && idx == 0 && xl.Type == DeleteType {
|
||||
hidden = true
|
||||
return errDoneForNow
|
||||
}
|
||||
if !xl.FreeVersion() {
|
||||
hidden = false
|
||||
return errDoneForNow
|
||||
}
|
||||
// Check next version
|
||||
return nil
|
||||
})
|
||||
return hidden
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user