Compare commits

...

69 Commits

Author SHA1 Message Date
Aditya Manthramurthy 7bc95c47a3 Update console to 0.22.5 (#16400) 2023-01-11 18:06:16 -08:00
Anis Elleuch 475a88b555 fix: error out if an object is found after a full decom (#16277) 2023-01-12 05:52:51 +05:30
Allan Roger Reid 9815dac48f fix: allow bind on ipv6 loopback failures (#16388) 2023-01-11 08:47:39 +05:30
Anis Elleuch 1ece3d1dfe Add comment field to service accounts (#16380) 2023-01-10 21:57:52 +04:00
Anis Elleuch 2146ed4033 xl: Quit early when EC config is incorrect (#16390)
Co-authored-by: Anis Elleuch <anis@min.io>
2023-01-09 23:07:45 -08:00
Minio Trusted 52b88b52f0 Update yaml files to latest version RELEASE.2023-01-06T18-11-18Z 2023-01-08 07:51:31 +00:00
Anis Elleuch ebd4388cca s3: Return XMinioInvalidObjectName if the object contains null char (#16372) 2023-01-06 10:11:18 -08:00
Harshavardhana 57fd02ee57 update console v0.22.4 (#16374)
Signed-off-by: Harshavardhana <harsha@minio.io>
2023-01-05 22:15:51 -08:00
Anis Elleuch 0333412148 fix: heal only once per disk per set among multiple disks (#16358) 2023-01-05 20:41:19 -08:00
Anis Elleuch 1c85652cff lint: Fix in darwin environment (#16368) 2023-01-05 10:12:01 -08:00
Harshavardhana e0086c1be7 reduce startup delays on kubernetes (#16356) 2023-01-05 02:32:43 -08:00
Poorna b29e159604 docs: Update replication setup commands (#16361) 2023-01-04 13:39:37 -08:00
Anis Elleuch 7883e55da2 Merge buckets list from different nodes in ListBuckets() call (#16357) 2023-01-04 08:53:58 -08:00
Harshavardhana b197623ed2 remove unnecessary kernel-tuning docs (#16354) 2023-01-04 01:33:40 -08:00
Harshavardhana a15a2556c3 converge listBuckets() as a peer call (#16346) 2023-01-03 23:39:40 -08:00
Harshavardhana 14d29b77ae update replication tests with latest 'mc' (#16348) 2023-01-03 22:54:39 -08:00
Harshavardhana a2514ffeed update klauspost/compress dependency (#16343) 2023-01-03 10:41:14 -08:00
Harshavardhana f1bbb7fef5 vectorize cluster-wide calls such as bucket operations (#16313) 2023-01-03 08:16:39 -08:00
Minio Trusted 72394a8319 Update yaml files to latest version RELEASE.2023-01-02T09-40-09Z 2023-01-03 10:16:34 +00:00
Harshavardhana 1cd8e1d8b6 remove the startup jitter before locks() (#16340) 2023-01-02 01:40:09 -08:00
jiuker 62cd918061 fix: close helmInfo file descriptor (#16319) 2023-01-01 23:26:59 -08:00
Klaus Post 6a04067514 fix: tweak read buffer size to reduce over-reading (#16338) 2023-01-01 08:14:20 -08:00
Taran Pelkey 49b3908635 fix: misplaced write response command in DetachPolicy() (#16333) 2022-12-30 20:04:03 -08:00
Harshavardhana 75faef888e disable builds for go1.18 (#16332) 2022-12-30 11:37:07 -08:00
Harshavardhana b67d97b1ba add missing fields in audit logs for non-compressed handlers (#16328) 2022-12-30 10:20:19 -08:00
Anis Elleuch b8943fdf19 doc: Update prometheus metrics list (#16329) 2022-12-29 15:08:22 -08:00
Harshavardhana f93183f66e fix: a deadlock by refactoring listBuckets() under site replication (#16323) 2022-12-29 00:08:31 -08:00
Harshavardhana 2937711390 fix: DeleteObject() API with versionId under replication (#16325) 2022-12-28 22:48:33 -08:00
Wojtek Czekalski aa56c6d51d helm: Make bucket existence check faster (#16321) 2022-12-27 10:32:39 -08:00
Anis Elleuch 27417459fb metrics: Show healing info for all nodes (#16315) 2022-12-26 08:35:32 -08:00
Harshavardhana 5b8fe2e89a allow locks with object affinity to spread across pools (#16312) 2022-12-23 20:55:45 -08:00
Anis Elleuch acc9c033ed debug: Add X-Amz-Request-ID to lock/unlock calls (#16309) 2022-12-23 19:49:07 -08:00
Poorna 8528b265a9 Validate replication target update to avoid duplicate endpoints (#16311) 2022-12-23 15:44:48 -08:00
Han Cen 44250f1a52 helm: disallow empty containers in post job template (#16281) 2022-12-23 12:32:18 -08:00
Minio Trusted f7560670d9 update helm v5.0.4
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-12-23 12:29:40 -08:00
Russell Sim 3891885800 helm: fix creating users, via proper secretKey (#16310) 2022-12-23 12:28:43 -08:00
Harshavardhana b882310e2b avoid locks for internal and invalid buckets in MakeBucket() (#16302) 2022-12-23 07:46:00 -08:00
Poorna de0b43de32 persist replication stats with leader lock (#16282) 2022-12-22 14:25:13 -08:00
Harshavardhana 48152a56ac upgrade UBI image to 8.7 (#16301) 2022-12-22 10:56:05 -08:00
jiuker 29dd7f1d68 tier verification leaks fd, that must be closed (#16296)
Co-authored-by: Harshavardhana <harsha@minio.io>
2022-12-22 10:35:54 -08:00
Poorna 6423e4c767 Remove site replication config if it succeeded locally (#16279) 2022-12-22 01:31:20 -08:00
Harshavardhana 1dd8f0e8f3 update console v0.22.3 (#16292)
Signed-off-by: Harshavardhana <harsha@minio.io>
2022-12-21 23:47:51 -08:00
Krishnan Parthasarathi 2fa35def2c Fix DeleteObject when only free versions remain (#16289) 2022-12-21 16:24:07 -08:00
Anis Elleuch 34167c51d5 trace: Add bootstrap tracing events (#16286) 2022-12-21 15:52:29 -08:00
Harshavardhana a5f8af4efb serialize replication stats() only when needed (#16280) 2022-12-20 00:07:53 -08:00
Harshavardhana 5a218f38a1 allow retries for transaction lock on startup (#16273) 2022-12-19 22:00:00 -08:00
Anis Elleuch e57e946206 Do not save credentials in config.json (#16275) 2022-12-19 12:27:06 -08:00
Klaus Post b4f71362e9 Avoid config migration on every startup (#16278) 2022-12-19 11:10:14 -08:00
Taran Pelkey ed37b7a9d5 Add API to fetch policy user/group associations (#16239) 2022-12-19 10:37:03 -08:00
Minio Trusted 6511021fbe update helm v5.0.3 2022-12-19 00:53:02 -08:00
mruzicka 6197ba851b helm: Fix post job template (#16236) 2022-12-18 08:01:22 -08:00
Minio Trusted 3ae1f9d852 update helm v5.0.2 2022-12-17 23:57:10 -08:00
orblazer 0db1930f48 helm: add policy to svcacct (#16272) 2022-12-17 22:50:37 -08:00
Anis Elleuch 89db3fdb5d Do not return an error when version disparity is detected (#16269) 2022-12-16 08:52:12 -08:00
Harshavardhana 80fc3a8a52 use newDynamicTimeoutWithOpts() when appropriate (#16266) 2022-12-15 13:11:37 -08:00
Klaus Post 988a2e8fed Faster startup of large distributed systems with latency (#16259) 2022-12-15 08:31:21 -08:00
Harshavardhana 2433698372 fix: remove unnecessary logs for client conn errors (#16261) 2022-12-15 08:25:05 -08:00
Harshavardhana 5d7e8f79ed fix: remove scanner healing with unnecessary logs (#16260) 2022-12-14 16:39:18 -08:00
Harshavardhana bad229e16e fix: support event name s3:Restore:* (#16257) 2022-12-14 05:12:07 -08:00
Poorna d37e514733 Cleanup remote targets automatically on replication config removal. (#16221) 2022-12-14 03:24:06 -08:00
Harshavardhana c73ea27ed7 do not log checksum mismatch error, client received the error (#16246) 2022-12-14 01:57:40 -08:00
Krishnan Parthasarathi 0159b56717 fix: rebalance to account for object's on-disk size (#16240) 2022-12-14 00:15:14 -08:00
Aditya Manthramurthy 9e6cc847f8 Add HTTP2 config option for policy plugin (#16225) 2022-12-13 14:28:48 -08:00
Taran Pelkey 709eb283d9 Add endpoints for managing IAM policies (#15897)
Co-authored-by: Taran <taran@minio.io>
Co-authored-by: ¨taran-p¨ <¨taran@minio.io¨>
Co-authored-by: Aditya Manthramurthy <donatello@users.noreply.github.com>
2022-12-13 12:13:23 -08:00
Anis Elleuch 76dde82b41 Implement STS account info API (#16115) 2022-12-13 08:38:50 -08:00
Anis Elleuch 939c0100a6 log: Do not interpret verbs in object names in console output (#16233) 2022-12-13 08:27:40 -08:00
Aditya Manthramurthy 2d60bf8c50 Refactor HTTP transports (#16222) 2022-12-12 20:31:21 -08:00
Harshavardhana 37e20f6ef2 feat: allow listening specific addrs for API port (#16223) 2022-12-12 18:48:46 -08:00
Minio Trusted 76905b7a67 Update yaml files to latest version RELEASE.2022-12-12T19-27-27Z 2022-12-13 01:10:31 +00:00
141 changed files with 4046 additions and 2483 deletions
+3 -2
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [1.18.5b7]
go-version: [1.19.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
@@ -30,9 +30,10 @@ jobs:
- name: Setup dockerfile for build test
run: |
echo "FROM us-docker.pkg.dev/google.com/api-project-999119582588/go-boringcrypto/golang:${{ matrix.go-version }}" > Dockerfile.fips.test
echo "FROM golang:1.19.4" >> Dockerfile.fips.test
echo "COPY . /minio" >> Dockerfile.fips.test
echo "WORKDIR /minio" >> Dockerfile.fips.test
echo "ENV GOEXPERIMENT=boringcrypto" >> Dockerfile.fips.test
echo "RUN make" >> Dockerfile.fips.test
- name: Build
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
ARG RELEASE
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
ARG TARGETARCH
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.7
ARG TARGETARCH
+2 -1
View File
@@ -20,7 +20,7 @@ help: ## print this help
getdeps: ## fetch necessary dependencies
@mkdir -p ${GOPATH}/bin
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@f3635b96e4838a6c773babb65ef35297fe5fe2f9
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.7
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
@echo "Installing staticcheck" && go install honnef.co/go/tools/cmd/staticcheck@latest
@@ -68,6 +68,7 @@ test-replication: install ## verify multi site replication
@echo "Running tests for replicating three sites"
@(env bash $(PWD)/docs/bucket/replication/setup_3site_replication.sh)
@(env bash $(PWD)/docs/bucket/replication/setup_2site_existing_replication.sh)
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
test-site-replication-ldap: install ## verify automatic site replication
@echo "Running tests for automatic site replication of IAM (with LDAP)"
+1 -1
View File
@@ -125,7 +125,7 @@ You can also connect using any S3-compatible tool, such as the MinIO Client `mc`
## Install from Source
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.18](https://golang.org/dl/#stable)
Use the following commands to compile and run a standalone MinIO server from source. Source installation is only intended for developers and advanced users. If you do not have a working Golang environment, please follow [How to install Golang](https://golang.org/doc/install). Minimum version required is [go1.19](https://golang.org/dl/#stable)
```sh
go install github.com/minio/minio@latest
+19 -6
View File
@@ -201,7 +201,18 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
if update {
ops = madmin.GetTargetUpdateOps(r.Form)
} else {
target.Arn = globalBucketTargetSys.getRemoteARN(bucket, &target)
var exists bool // true if arn exists
target.Arn, exists = globalBucketTargetSys.getRemoteARN(bucket, &target)
if exists && target.Arn != "" { // return pre-existing ARN
data, err := json.Marshal(target.Arn)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Write success response.
writeSuccessResponseJSON(w, data)
return
}
}
if target.Arn == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
@@ -217,8 +228,10 @@ func (a adminAPIHandlers) SetRemoteTargetHandler(w http.ResponseWriter, r *http.
for _, op := range ops {
switch op {
case madmin.CredentialsUpdateType:
tgt.Credentials = target.Credentials
tgt.TargetBucket = target.TargetBucket
if !globalSiteReplicationSys.isEnabled() {
tgt.Credentials = target.Credentials
tgt.TargetBucket = target.TargetBucket
}
tgt.Secure = target.Secure
tgt.Endpoint = target.Endpoint
case madmin.SyncUpdateType:
@@ -696,7 +709,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
opts := MakeBucketOptions{
LockEnabled: config.ObjectLockEnabled == "Enabled",
}
err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts)
err = objectAPI.MakeBucket(ctx, bucket, opts)
if err != nil {
if _, ok := err.(BucketExists); !ok {
rpt.SetStatus(bucket, fileName, err)
@@ -757,7 +770,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
continue
}
if _, ok := bucketMap[bucket]; !ok {
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}); err != nil {
if err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil {
if _, ok := err.(BucketExists); !ok {
rpt.SetStatus(bucket, fileName, err)
continue
@@ -809,7 +822,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
bucket, fileName := slc[0], slc[1]
// create bucket if it does not exist yet.
if _, ok := bucketMap[bucket]; !ok {
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
if _, ok := err.(BucketExists); !ok {
rpt.SetStatus(bucket, "", err)
+3 -3
View File
@@ -71,7 +71,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
return
}
cfg, err := readServerConfig(ctx, objectAPI)
cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -142,7 +142,7 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
return
}
cfg, err := readServerConfig(ctx, objectAPI)
cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -296,7 +296,7 @@ func (a adminAPIHandlers) RestoreConfigHistoryKVHandler(w http.ResponseWriter, r
return
}
cfg, err := readServerConfig(ctx, objectAPI)
cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
+2 -2
View File
@@ -107,7 +107,7 @@ func (a adminAPIHandlers) addOrUpdateIDPHandler(ctx context.Context, w http.Resp
cfgData = subSys + tgtSuffix + config.KvSpaceSeparator + string(reqBytes)
}
cfg, err := readServerConfig(ctx, objectAPI)
cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -408,7 +408,7 @@ func (a adminAPIHandlers) DeleteIdentityProviderCfg(w http.ResponseWriter, r *ht
return
}
cfg, err := readServerConfig(ctx, objectAPI)
cfg, err := readServerConfig(ctx, objectAPI, nil)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
+6 -20
View File
@@ -114,37 +114,23 @@ func (a adminAPIHandlers) SRPeerBucketOps(w http.ResponseWriter, r *http.Request
default:
err = errSRInvalidRequest(errInvalidArgument)
case madmin.MakeWithVersioningBktOp:
_, isLockEnabled := r.Form["lockEnabled"]
_, isVersioningEnabled := r.Form["versioningEnabled"]
_, isForceCreate := r.Form["forceCreate"]
createdAtStr := strings.TrimSpace(r.Form.Get("createdAt"))
createdAt, cerr := time.Parse(time.RFC3339Nano, createdAtStr)
createdAt, cerr := time.Parse(time.RFC3339Nano, strings.TrimSpace(r.Form.Get("createdAt")))
if cerr != nil {
createdAt = timeSentinel
}
opts := MakeBucketOptions{
Location: r.Form.Get("location"),
LockEnabled: isLockEnabled,
VersioningEnabled: isVersioningEnabled,
ForceCreate: isForceCreate,
LockEnabled: r.Form.Get("lockEnabled") == "true",
VersioningEnabled: r.Form.Get("versioningEnabled") == "true",
ForceCreate: r.Form.Get("forceCreate") == "true",
CreatedAt: createdAt,
}
err = globalSiteReplicationSys.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
case madmin.ConfigureReplBktOp:
err = globalSiteReplicationSys.PeerBucketConfigureReplHandler(ctx, bucket)
case madmin.DeleteBucketBktOp:
_, noRecreate := r.Form["noRecreate"]
case madmin.DeleteBucketBktOp, madmin.ForceDeleteBucketBktOp:
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
Force: false,
NoRecreate: noRecreate,
SRDeleteOp: getSRBucketDeleteOp(true),
})
case madmin.ForceDeleteBucketBktOp:
_, noRecreate := r.Form["noRecreate"]
err = globalSiteReplicationSys.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
Force: true,
NoRecreate: noRecreate,
Force: operation == madmin.ForceDeleteBucketBktOp,
SRDeleteOp: getSRBucketDeleteOp(true),
})
case madmin.PurgeDeletedBucketOp:
+394
View File
@@ -25,6 +25,7 @@ import (
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/gorilla/mux"
@@ -528,6 +529,89 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
}
}
// TemporaryAccountInfo - GET /minio/admin/v3/temporary-account-info
func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "TemporaryAccountInfo")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
// Get current object layer instance.
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
cred, claims, owner, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
accessKey := mux.Vars(r)["accessKey"]
if accessKey == "" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Action: iampolicy.ListTemporaryAccountsAdminAction,
ConditionValues: getConditionValues(r, "", cred.AccessKey, claims),
IsOwner: owner,
Claims: claims,
}) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
return
}
stsAccount, policy, err := globalIAMSys.GetTemporaryAccount(ctx, accessKey)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
var stsAccountPolicy iampolicy.Policy
if policy != nil {
stsAccountPolicy = *policy
} else {
policiesNames, err := globalIAMSys.PolicyDBGet(stsAccount.ParentUser, false)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
stsAccountPolicy = globalIAMSys.GetCombinedPolicy(policiesNames...)
}
policyJSON, err := json.MarshalIndent(stsAccountPolicy, "", " ")
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
infoResp := madmin.TemporaryAccountInfoResp{
ParentUser: stsAccount.ParentUser,
AccountStatus: stsAccount.Status,
ImpliedPolicy: policy == nil,
Policy: string(policyJSON),
}
data, err := json.Marshal(infoResp)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, encryptedData)
}
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AddServiceAccount")
@@ -581,6 +665,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{}),
}
@@ -721,6 +806,7 @@ 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,
@@ -808,6 +894,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)
@@ -825,6 +912,7 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
AccessKey: accessKey,
SecretKey: opts.secretKey,
Status: opts.status,
Comment: opts.comment,
SessionPolicy: updateReq.NewPolicy,
},
},
@@ -909,6 +997,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),
@@ -1583,6 +1672,309 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
}
}
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "ListPolicyMappingEntities")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
// Check authorization.
objectAPI, cred := validateAdminReq(ctx, w, r,
iampolicy.ListGroupsAdminAction, iampolicy.ListUsersAdminAction, iampolicy.ListUserPoliciesAdminAction)
if objectAPI == nil {
return
}
// Validate API arguments.
q := madmin.PolicyEntitiesQuery{
Users: r.Form["user"],
Groups: r.Form["group"],
Policy: r.Form["policy"],
}
// Query IAM
res, err := globalIAMSys.QueryPolicyEntities(r.Context(), q)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Encode result and send response.
data, err := json.Marshal(res)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
password := cred.SecretKey
econfigData, err := madmin.EncryptData(password, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, econfigData)
}
// AttachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/attach
func (a adminAPIHandlers) AttachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "AttachPolicyBuiltin")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
if objectAPI == nil {
return
}
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
var par madmin.PolicyAssociationReq
if err = json.Unmarshal(reqBytes, &par); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if err = par.IsValid(); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
userOrGroup := par.User
isGroup := false
if userOrGroup == "" {
userOrGroup = par.Group
isGroup = true
}
if isGroup {
_, err := globalIAMSys.GetGroupDescription(userOrGroup)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
} else {
ok, _, err := globalIAMSys.IsTempUser(userOrGroup)
if err != nil && err != errNoSuchUser {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if ok {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
return
}
// Validate that user exists.
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType {
_, ok := globalIAMSys.GetUser(ctx, userOrGroup)
if !ok {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
return
}
}
}
userType := regUser
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
userType = stsUser
}
var existingPolicies []string
if isGroup {
existingPolicies, err = globalIAMSys.PolicyDBGet(userOrGroup, true)
} else {
existingPolicies, err = globalIAMSys.GetUserPolicies(userOrGroup)
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
policyMap := make(map[string]bool)
for _, p := range existingPolicies {
policyMap[p] = true
}
policiesToAttach := par.Policies
// Check if policy is already attached to user.
for _, p := range policiesToAttach {
if _, ok := policyMap[p]; ok {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyAlreadyAttached), r.URL)
return
}
}
existingPolicies = append(existingPolicies, policiesToAttach...)
newPolicies := strings.Join(existingPolicies, ",")
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, userOrGroup, newPolicies, userType, isGroup)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemPolicyMapping,
PolicyMapping: &madmin.SRPolicyMapping{
UserOrGroup: userOrGroup,
UserType: int(userType),
IsGroup: isGroup,
Policy: strings.Join(policiesToAttach, ","),
},
UpdatedAt: updatedAt,
}); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeResponse(w, http.StatusCreated, nil, mimeNone)
}
// DetachPolicyBuiltin - POST /minio/admin/v3/idp/builtin/detach
func (a adminAPIHandlers) DetachPolicyBuiltin(w http.ResponseWriter, r *http.Request) {
ctx := newContext(r, w, "DetachPolicyBuiltin")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.AttachPolicyAdminAction)
if objectAPI == nil {
return
}
cred, _, _, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
password := cred.SecretKey
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
var par madmin.PolicyAssociationReq
if err = json.Unmarshal(reqBytes, &par); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if err = par.IsValid(); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
userOrGroup := par.User
isGroup := false
if userOrGroup == "" {
userOrGroup = par.Group
isGroup = true
}
if isGroup {
_, err := globalIAMSys.GetGroupDescription(userOrGroup)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
} else {
ok, _, err := globalIAMSys.IsTempUser(userOrGroup)
if err != nil && err != errNoSuchUser {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if ok {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
return
}
// Validate that user exists.
if globalIAMSys.GetUsersSysType() == MinIOUsersSysType {
_, ok := globalIAMSys.GetUser(ctx, userOrGroup)
if !ok {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
return
}
}
}
userType := regUser
if globalIAMSys.GetUsersSysType() == LDAPUsersSysType {
userType = stsUser
}
var existingPolicies []string
if isGroup {
existingPolicies, err = globalIAMSys.PolicyDBGet(userOrGroup, true)
} else {
existingPolicies, err = globalIAMSys.GetUserPolicies(userOrGroup)
}
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
policyMap := make(map[string]bool)
for _, p := range existingPolicies {
policyMap[p] = true
}
policiesToDetach := par.Policies
// Check if policy is already attached to user.
for _, p := range policiesToDetach {
if _, ok := policyMap[p]; ok {
delete(policyMap, p)
} else {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyNotAttached), r.URL)
return
}
}
newPoliciesSl := []string{}
for p := range policyMap {
newPoliciesSl = append(newPoliciesSl, p)
}
newPolicies := strings.Join(newPoliciesSl, ",")
updatedAt, err := globalIAMSys.PolicyDBSet(ctx, userOrGroup, newPolicies, userType, isGroup)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if err := globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
Type: madmin.SRIAMItemPolicyMapping,
PolicyMapping: &madmin.SRPolicyMapping{
UserOrGroup: userOrGroup,
UserType: int(userType),
IsGroup: isGroup,
Policy: strings.Join(policiesToDetach, ","),
},
UpdatedAt: updatedAt,
}); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Return successful JSON response
writeSuccessNoContent(w)
}
const (
allPoliciesFile = "policies.json"
allUsersFile = "users.json"
@@ -2080,6 +2472,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)
@@ -2094,6 +2487,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
+4 -5
View File
@@ -1170,7 +1170,7 @@ func (a adminAPIHandlers) NetperfHandler(w http.ResponseWriter, r *http.Request)
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(toAPIErrorCode(ctx, err)), r.URL)
return
}
defer nsLock.Unlock(lkctx.Cancel)
defer nsLock.Unlock(lkctx)
durationStr := r.Form.Get(peerRESTDuration)
duration, err := time.ParseDuration(durationStr)
@@ -1319,7 +1319,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
}
func makeObjectPerfBucket(ctx context.Context, objectAPI ObjectLayer, bucketName string) (bucketExists bool, err error) {
if err = objectAPI.MakeBucketWithLocation(ctx, bucketName, MakeBucketOptions{}); err != nil {
if err = objectAPI.MakeBucket(ctx, bucketName, MakeBucketOptions{}); err != nil {
if _, ok := err.(BucketExists); !ok {
// Only BucketExists error can be ignored.
return false, err
@@ -1332,7 +1332,6 @@ func makeObjectPerfBucket(ctx context.Context, objectAPI ObjectLayer, bucketName
func deleteObjectPerfBucket(objectAPI ObjectLayer) {
objectAPI.DeleteBucket(context.Background(), globalObjectPerfBucket, DeleteBucketOptions{
Force: true,
NoRecreate: true,
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
})
}
@@ -2135,7 +2134,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
getAndWriteMinioConfig := func() {
if query.Get("minioconfig") == "true" {
config, err := readServerConfig(healthCtx, objectAPI)
config, err := readServerConfig(healthCtx, objectAPI, nil)
if err != nil {
healthInfo.Minio.Config = madmin.MinioConfig{
Error: err.Error(),
@@ -2286,7 +2285,7 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
return
}
defer nsLock.Unlock(lkctx.Cancel)
defer nsLock.Unlock(lkctx)
healthCtx, healthCancel := context.WithTimeout(lkctx.Context(), deadline)
defer healthCancel()
+12
View File
@@ -142,12 +142,18 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
// STS accounts ops
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/temporary-account-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.TemporaryAccountInfo))).Queries("accessKey", "{accessKey:.*}")
// Info policy IAM latest
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
// List policies latest
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
// Builtin IAM policy associations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/builtin/policy-entities").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListPolicyMappingEntities)))
// Remove policy IAM
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
@@ -156,6 +162,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
// Attach policies to user or group
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/attach").HandlerFunc(gz(httpTraceHdrs(adminAPI.AttachPolicyBuiltin)))
// Detach policies from user or group
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/builtin/policy/detach").HandlerFunc(gz(httpTraceHdrs(adminAPI.DetachPolicyBuiltin)))
// Remove user IAM
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
+13 -1
View File
@@ -195,6 +195,8 @@ const (
ErrBucketTaggingNotFound
ErrObjectLockInvalidHeaders
ErrInvalidTagDirective
ErrPolicyAlreadyAttached
ErrPolicyNotAttached
// Add new error codes here.
// SSE-S3/SSE-KMS related API errors
@@ -1382,7 +1384,7 @@ var errorCodes = errorCodeMap{
ErrSiteReplicationPeerResp: {
Code: "XMinioSiteReplicationPeerResp",
Description: "Error received when contacting a peer site",
HTTPStatusCode: http.StatusServiceUnavailable,
HTTPStatusCode: http.StatusBadRequest,
},
ErrSiteReplicationBackendIssue: {
Code: "XMinioSiteReplicationBackendIssue",
@@ -1939,6 +1941,16 @@ var errorCodes = errorCodeMap{
Description: "Invalid checksum provided.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrPolicyAlreadyAttached: {
Code: "XMinioPolicyAlreadyAttached",
Description: "The specified policy is already attached.",
HTTPStatusCode: http.StatusConflict,
},
ErrPolicyNotAttached: {
Code: "XMinioPolicyNotAttached",
Description: "The specified policy is not found.",
HTTPStatusCode: http.StatusNotFound,
},
// Add your error structure here.
}
+185 -183
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -151,9 +151,6 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
s3Err = isReqAuthenticated(ctx, r, region, serviceS3)
}
if s3Err != ErrNone {
reqInfo := (&logger.ReqInfo{}).AppendTags("requestHeaders", dumpRequest(r))
ctx := logger.SetReqInfo(ctx, reqInfo)
logger.LogIf(ctx, errors.New(getAPIError(s3Err).Description), logger.Application)
return cred, nil, owner, s3Err
}
+40 -14
View File
@@ -79,6 +79,10 @@ type healingTracker struct {
// Filled during heal.
HealedBuckets []string
// ID of the current healing operation
HealID string
// Add future tracking capabilities
// Be sure that they are included in toHealingDisk
}
@@ -112,11 +116,12 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
}
// newHealingTracker will create a new healing tracker for the disk.
func newHealingTracker(disk StorageAPI) *healingTracker {
func newHealingTracker(disk StorageAPI, healID string) *healingTracker {
diskID, _ := disk.GetDiskID()
h := healingTracker{
disk: disk,
ID: diskID,
HealID: healID,
Path: disk.String(),
Endpoint: disk.Endpoint().String(),
Started: time.Now().UTC(),
@@ -227,6 +232,7 @@ func (h *healingTracker) printTo(writer io.Writer) {
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
return madmin.HealingDisk{
ID: h.ID,
HealID: h.HealID,
Endpoint: h.Endpoint,
PoolIndex: h.PoolIndex,
SetIndex: h.SetIndex,
@@ -286,8 +292,6 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
disk, format, err := connectEndpoint(endpoint)
if err != nil {
return fmt.Errorf("Error: %w, %s", err, endpoint)
@@ -316,7 +320,21 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
return err
}
ctx = lkctx.Context()
defer locker.Unlock(lkctx.Cancel)
defer locker.Unlock(lkctx)
// Load healing tracker in this disk
tracker, err := loadHealingTracker(ctx, disk)
if err != nil {
// A healing track can be not found when another disk in the same
// erasure set and same healing-id successfully finished healing.
if err == errFileNotFound {
return nil
}
logger.LogIf(ctx, fmt.Errorf("Unable to load a healing tracker on '%s': %w", disk, err))
tracker = newHealingTracker(disk, mustGetUUID())
}
logger.Info(fmt.Sprintf("Proceeding to heal '%s' - 'mc admin heal alias/ --verbose' to check the status.", endpoint))
buckets, _ := z.ListBuckets(ctx, BucketOptions{})
// Buckets data are dispersed in multiple zones/sets, make
@@ -340,15 +358,6 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
}
// Load healing tracker in this disk
tracker, err := loadHealingTracker(ctx, disk)
if err != nil {
// So someone changed the drives underneath, healing tracker missing.
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', drive was swapped again on %s pool: %w",
disk, humanize.Ordinal(poolIdx+1), err))
tracker = newHealingTracker(disk)
}
// Load bucket totals
cache := dataUsageCache{}
if err := cache.load(ctx, z.serverPools[poolIdx].sets[setIdx], dataUsageCacheName); err == nil {
@@ -379,7 +388,24 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
logger.Info("\n")
}
logger.LogIf(ctx, tracker.delete(ctx))
if tracker.HealID == "" { // HealID is empty only before Feb 2023
logger.LogIf(ctx, tracker.delete(ctx))
return nil
}
// Remove .healing.bin from all disks with similar heal-id
for _, disk := range z.serverPools[poolIdx].sets[setIdx].getDisks() {
t, err := loadHealingTracker(ctx, disk)
if err != nil {
if err != errFileNotFound {
logger.LogIf(ctx, err)
}
continue
}
if t.HealID == tracker.HealID {
t.delete(ctx)
}
}
return nil
}
+30 -4
View File
@@ -182,6 +182,12 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
return
}
}
case "HealID":
z.HealID, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "HealID")
return
}
default:
err = dc.Skip()
if err != nil {
@@ -195,9 +201,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 22
// map header, size 23
// write "ID"
err = en.Append(0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
err = en.Append(0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
if err != nil {
return
}
@@ -430,15 +436,25 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
return
}
}
// write "HealID"
err = en.Append(0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
if err != nil {
return
}
err = en.WriteString(z.HealID)
if err != nil {
err = msgp.WrapError(err, "HealID")
return
}
return
}
// MarshalMsg implements msgp.Marshaler
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize())
// map header, size 22
// map header, size 23
// string "ID"
o = append(o, 0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
o = append(o, 0xde, 0x0, 0x17, 0xa2, 0x49, 0x44)
o = msgp.AppendString(o, z.ID)
// string "PoolIndex"
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
@@ -509,6 +525,9 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
for za0002 := range z.HealedBuckets {
o = msgp.AppendString(o, z.HealedBuckets[za0002])
}
// string "HealID"
o = append(o, 0xa6, 0x48, 0x65, 0x61, 0x6c, 0x49, 0x44)
o = msgp.AppendString(o, z.HealID)
return
}
@@ -688,6 +707,12 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
return
}
}
case "HealID":
z.HealID, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "HealID")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
@@ -710,5 +735,6 @@ func (z *healingTracker) Msgsize() (s int) {
for za0002 := range z.HealedBuckets {
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
}
s += 7 + msgp.StringPrefixSize + len(z.HealID)
return
}
+3 -3
View File
@@ -35,7 +35,7 @@ func runPutObjectBenchmark(b *testing.B, obj ObjectLayer, objSize int) {
// obtains random bucket name.
bucket := getRandomBucketName()
// create bucket.
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
b.Fatal(err)
}
@@ -76,7 +76,7 @@ func runPutObjectPartBenchmark(b *testing.B, obj ObjectLayer, partSize int) {
object := getRandomObjectName()
// create bucket.
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
b.Fatal(err)
}
@@ -196,7 +196,7 @@ func runPutObjectBenchmarkParallel(b *testing.B, obj ObjectLayer, objSize int) {
// obtains random bucket name.
bucket := getRandomBucketName()
// create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
b.Fatal(err)
}
+13 -14
View File
@@ -431,7 +431,6 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
// Unmarshal list of keys to be deleted.
deleteObjectsReq := &DeleteObjectsRequest{}
if err := xmlDecoder(r.Body, deleteObjectsReq, maxBodySize); err != nil {
logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
@@ -511,11 +510,10 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
}
if object.VersionID != "" && object.VersionID != nullVersionID {
if _, err := uuid.Parse(object.VersionID); err != nil {
logger.LogIf(ctx, fmt.Errorf("invalid version-id specified %w", err))
apiErr := errorCodes.ToAPIErr(ErrNoSuchVersion)
deleteResults[index].errInfo = DeleteError{
Code: apiErr.Code,
Message: apiErr.Description,
Message: fmt.Sprintf("%s (%s)", apiErr.Description, err),
Key: object.ObjectName,
VersionID: object.VersionID,
}
@@ -775,7 +773,6 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
}
opts := MakeBucketOptions{
Location: location,
LockEnabled: objectLockEnabled,
ForceCreate: forceCreate,
}
@@ -787,15 +784,14 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
// exists elsewhere
if err == dns.ErrNoEntriesFound || err == dns.ErrNotImplemented {
// Proceed to creating a bucket.
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil {
if err = objectAPI.MakeBucket(ctx, bucket, opts); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
if err = globalDNSConfig.Put(bucket); err != nil {
objectAPI.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
Force: false,
NoRecreate: true,
Force: true,
SRDeleteOp: getSRBucketDeleteOp(globalSiteReplicationSys.isEnabled()),
})
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
@@ -839,7 +835,7 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
}
// Proceed to creating a bucket.
if err := objectAPI.MakeBucketWithLocation(ctx, bucket, opts); err != nil {
if err := objectAPI.MakeBucket(ctx, bucket, opts); err != nil {
if _, ok := err.(BucketExists); ok {
// Though bucket exists locally, we send the site-replication
// hook to ensure all sites have this bucket. If the hook
@@ -924,16 +920,18 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// be loaded in memory, the remaining being put in temporary files.
reader, err := r.MultipartReader()
if err != nil {
logger.LogIf(ctx, err)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
writeErrorResponse(ctx, w, apiErr, r.URL)
return
}
// Read multipart data and save in memory and in the disk if needed
form, err := reader.ReadForm(maxFormMemory)
if err != nil {
logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
writeErrorResponse(ctx, w, apiErr, r.URL)
return
}
@@ -943,8 +941,9 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
// Extract all form fields
fileBody, fileName, fileSize, formValues, err := extractPostPolicyFormValues(ctx, form)
if err != nil {
logger.LogIf(ctx, err, logger.Application)
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
writeErrorResponse(ctx, w, apiErr, r.URL)
return
}
+4 -4
View File
@@ -66,7 +66,7 @@ func (sys *BucketMetadataSys) Remove(bucket string) {
// so they should be replaced atomically and not appended to, etc.
// Data is not persisted to disk.
func (sys *BucketMetadataSys) Set(bucket string, meta BucketMetadata) {
if bucket != minioMetaBucket {
if !isMinioMetaBucketName(bucket) {
sys.Lock()
sys.metadataMap[bucket] = meta
sys.Unlock()
@@ -79,7 +79,7 @@ func (sys *BucketMetadataSys) updateAndParse(ctx context.Context, bucket string,
return updatedAt, errServerNotInitialized
}
if bucket == minioMetaBucket {
if isMinioMetaBucketName(bucket) {
return updatedAt, errInvalidArgument
}
@@ -164,7 +164,7 @@ func (sys *BucketMetadataSys) Update(ctx context.Context, bucket string, configF
// For all other bucket specific metadata, use the relevant
// calls implemented specifically for each of those features.
func (sys *BucketMetadataSys) Get(bucket string) (BucketMetadata, error) {
if bucket == minioMetaBucket {
if isMinioMetaBucketName(bucket) {
return newBucketMetadata(bucket), errConfigNotFound
}
@@ -345,7 +345,7 @@ func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (Buc
return newBucketMetadata(bucket), errServerNotInitialized
}
if bucket == minioMetaBucket {
if isMinioMetaBucketName(bucket) {
return newBucketMetadata(bucket), errInvalidArgument
}
+1 -1
View File
@@ -135,7 +135,7 @@ func (b *BucketMetadata) SetCreatedAt(createdAt time.Time) {
func (b *BucketMetadata) Load(ctx context.Context, api ObjectLayer, name string) error {
if name == "" {
logger.LogIf(ctx, errors.New("bucket name cannot be empty"))
return errors.New("bucket name cannot be empty")
return errInvalidArgument
}
configFile := path.Join(bucketMetaPrefix, name, bucketMetadataFile)
data, err := readConfig(ctx, api, configFile)
+3 -3
View File
@@ -107,7 +107,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.Policy {
// Wrapper for calling Create Bucket and ensure we get one and only one success.
func TestCreateBucket(t *testing.T) {
ExecObjectLayerAPITest(t, testCreateBucket, []string{"MakeBucketWithLocation"})
ExecObjectLayerAPITest(t, testCreateBucket, []string{"MakeBucket"})
}
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
@@ -127,7 +127,7 @@ func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRoute
defer wg.Done()
// Sync start.
<-start
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
if err := obj.MakeBucket(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
if _, ok := err.(BucketExists); !ok {
t.Logf("unexpected error: %T: %v", err, err)
return
@@ -162,7 +162,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
credentials auth.Credentials, t *testing.T,
) {
bucketName1 := fmt.Sprintf("%s-1", bucketName)
if err := obj.MakeBucketWithLocation(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
if err := obj.MakeBucket(GlobalContext, bucketName1, MakeBucketOptions{}); err != nil {
t.Fatal(err)
}
+15
View File
@@ -169,6 +169,21 @@ func (api objectAPIHandlers) DeleteBucketReplicationConfigHandler(w http.Respons
return
}
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
for _, tgt := range targets.Targets {
if err := globalBucketTargetSys.RemoveTarget(ctx, bucket, tgt.Arn); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
}
if _, err := globalBucketMetadataSys.Delete(ctx, bucket, bucketTargetsFile); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
// Write success response.
writeSuccessResponseHeadersOnly(w)
}
+6 -4
View File
@@ -254,11 +254,10 @@ func (r *ReplicationStats) serializeStats() ([]byte, error) {
}
r.mostRecentStatsMu.Lock()
defer r.mostRecentStatsMu.Unlock()
bsm := r.mostRecentStats
if len(bsm.Stats) == 0 {
if len(r.mostRecentStats.Stats) == 0 {
return nil, nil
}
data := make([]byte, 4, 4+bsm.Msgsize())
data := make([]byte, 4, 4+r.mostRecentStats.Msgsize())
// Add the replication stats meta header.
binary.LittleEndian.PutUint16(data[0:2], replStatsMetaFormat)
binary.LittleEndian.PutUint16(data[2:4], replStatsVersion)
@@ -333,6 +332,7 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u Buck
s = BucketReplicationStats{
Stats: make(map[string]*BucketReplicationStat, len(stats)),
}
var latestTotReplicatedSize int64
for _, st := range u.ReplicationInfo {
latestTotReplicatedSize += int64(st.ReplicatedSize)
@@ -372,7 +372,9 @@ func (r *ReplicationStats) calculateBucketReplicationStats(bucket string, u Buck
if len(r.mostRecentStats.Stats) == 0 {
r.mostRecentStats = BucketStatsMap{Stats: make(map[string]BucketStats, 1), Timestamp: UTCNow()}
}
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
if len(s.Stats) > 0 {
r.mostRecentStats.Stats[bucket] = BucketStats{ReplicationStats: s}
}
r.mostRecentStats.Timestamp = UTCNow()
r.mostRecentStatsMu.Unlock()
return s
+3 -4
View File
@@ -638,7 +638,7 @@ type replicationResyncer struct {
}
const (
replicationDir = "replication"
replicationDir = ".replication"
resyncFileName = "resync.bin"
resyncMetaFormat = 1
resyncMetaVersionV1 = 1
@@ -797,9 +797,8 @@ func (ri ReplicateObjectInfo) ToMRFEntry() MRFReplicateEntry {
}
}
func getReplicationStatsPath(nodeName string) string {
nodeStr := strings.ReplaceAll(nodeName, ":", "_")
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + nodeStr + ".stats"
func getReplicationStatsPath() string {
return bucketMetaPrefix + SlashSeparator + replicationDir + SlashSeparator + "replication.stats"
}
const (
+19 -24
View File
@@ -131,8 +131,8 @@ func validateReplicationDestination(ctx context.Context, bucket string, rCfg *re
}
}
// validate replication ARN against target endpoint
c, ok := globalBucketTargetSys.arnRemotesMap[arnStr]
if ok {
c := globalBucketTargetSys.GetRemoteTargetClient(ctx, arnStr)
if c != nil {
if c.EndpointURL().String() == clnt.EndpointURL().String() {
selfTarget, _ := isLocalHost(clnt.EndpointURL().Hostname(), clnt.EndpointURL().Port(), globalMinioPort)
if !sameTarget {
@@ -309,7 +309,7 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
for _, tgtArn := range tgtArns {
opts.TargetArn = tgtArn
replicate = rcfg.Replicate(opts)
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
// when incoming delete is removal of a delete marker(a.k.a versioned delete),
// GetObjectInfo returns extra information even though it returns errFileNotFound
if gerr != nil {
validReplStatus := false
@@ -425,7 +425,7 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
return
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
var wg sync.WaitGroup
var rinfos replicatedInfos
@@ -937,7 +937,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
return
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
var wg sync.WaitGroup
var rinfos replicatedInfos
@@ -1526,6 +1526,7 @@ type ReplicationPool struct {
mrfWorkerKillCh chan struct{}
mrfReplicaCh chan ReplicationWorkerOperation
mrfSaveCh chan MRFReplicateEntry
mrfStopCh chan struct{}
mrfWorkerSize int
saveStateCh chan struct{}
mrfWorkerWg sync.WaitGroup
@@ -1582,6 +1583,7 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
resyncer: newresyncer(),
mrfSaveCh: make(chan MRFReplicateEntry, 100000),
mrfStopCh: make(chan struct{}, 1),
saveStateCh: make(chan struct{}, 1),
ctx: ctx,
objLayer: o,
@@ -2831,6 +2833,7 @@ func (p *ReplicationPool) persistMRF() {
saveMRFToDisk(false)
mTimer.Reset(mrfTimeInterval)
case <-p.ctx.Done():
p.mrfStopCh <- struct{}{}
close(p.mrfSaveCh)
saveMRFToDisk(true)
return
@@ -2860,8 +2863,13 @@ func (p *ReplicationPool) queueMRFSave(entry MRFReplicateEntry) {
select {
case <-GlobalContext.Done():
return
case p.mrfSaveCh <- entry:
case <-p.mrfStopCh:
return
default:
select {
case p.mrfSaveCh <- entry:
default:
}
}
}
@@ -3005,7 +3013,7 @@ func (p *ReplicationPool) loadStatsFromDisk() (rs map[string]BucketReplicationSt
return map[string]BucketReplicationStats{}, nil
}
data, err := readConfig(p.ctx, p.objLayer, getReplicationStatsPath(globalLocalNodeName))
data, err := readConfig(p.ctx, p.objLayer, getReplicationStatsPath())
if err != nil {
if !errors.Is(err, errConfigNotFound) {
return rs, nil
@@ -3048,6 +3056,8 @@ func (p *ReplicationPool) saveStatsToDisk() {
if !p.initialized() {
return
}
ctx, cancel := globalLeaderLock.GetLock(p.ctx)
defer cancel()
sTimer := time.NewTimer(replStatsSaveInterval)
defer sTimer.Stop()
for {
@@ -3059,7 +3069,7 @@ func (p *ReplicationPool) saveStatsToDisk() {
}
p.saveStats(p.ctx)
sTimer.Reset(replStatsSaveInterval)
case <-p.ctx.Done():
case <-ctx.Done():
return
}
}
@@ -3075,20 +3085,5 @@ func (p *ReplicationPool) saveStats(ctx context.Context) error {
if data == nil {
return err
}
return saveConfig(ctx, p.objLayer, getReplicationStatsPath(globalLocalNodeName), data)
}
// SaveState saves replication stats and mrf data before server restart
func (p *ReplicationPool) SaveState(ctx context.Context) error {
if !p.initialized() {
return nil
}
go func() {
select {
case p.saveStateCh <- struct{}{}:
case <-p.ctx.Done():
return
}
}()
return p.saveStats(ctx)
return saveConfig(ctx, p.objLayer, getReplicationStatsPath(), data)
}
+16 -9
View File
@@ -217,10 +217,13 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
// validate if target credentials are ok
exists, err := clnt.BucketExists(ctx, tgt.TargetBucket)
if err != nil {
if minio.ToErrorResponse(err).Code == "NoSuchBucket" {
switch minio.ToErrorResponse(err).Code {
case "NoSuchBucket":
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
case "AccessDenied":
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
}
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, AccessKey: tgt.Credentials.AccessKey, Err: err}
}
if !exists {
return BucketRemoteTargetNotFound{Bucket: tgt.TargetBucket}
@@ -231,7 +234,7 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
}
vcfg, err := clnt.GetBucketVersioning(ctx, tgt.TargetBucket)
if err != nil {
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err}
return RemoteTargetConnectionErr{Bucket: tgt.TargetBucket, Err: err, AccessKey: tgt.Credentials.AccessKey}
}
if !vcfg.Enabled() {
return BucketRemoteTargetNotVersioned{Bucket: tgt.TargetBucket}
@@ -253,6 +256,10 @@ func (sys *BucketTargetSys) SetTarget(ctx context.Context, bucket string, tgt *m
found = true
continue
}
// fail if endpoint is already present in list of targets and not a matching ARN
if t.Endpoint == tgt.Endpoint {
return BucketRemoteAlreadyExists{Bucket: t.TargetBucket}
}
}
newtgts[idx] = t
}
@@ -470,20 +477,20 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
}
// getRemoteARN gets existing ARN for an endpoint or generates a new one.
func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget) string {
func (sys *BucketTargetSys) getRemoteARN(bucket string, target *madmin.BucketTarget) (arn string, exists bool) {
if target == nil {
return ""
return
}
tgts := sys.targetsMap[bucket]
for _, tgt := range tgts {
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL().String() == tgt.URL().String() {
return tgt.Arn
if tgt.Type == target.Type && tgt.TargetBucket == target.TargetBucket && target.URL().String() == tgt.URL().String() && tgt.Credentials.AccessKey == target.Credentials.AccessKey {
return tgt.Arn, true
}
}
if !target.Type.IsValid() {
return ""
return
}
return generateARN(target)
return generateARN(target), false
}
// getRemoteARNForPeer returns the remote target for a peer site in site replication
+1 -1
View File
@@ -79,7 +79,7 @@ func runCallhome(ctx context.Context, objAPI ObjectLayer) bool {
}
ctx = lkctx.Context()
defer locker.Unlock(lkctx.Cancel)
defer locker.Unlock(lkctx)
callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
defer callhomeTimer.Stop()
+5 -2
View File
@@ -117,7 +117,8 @@ func init() {
go func() {
var t *time.Ticker
if containers {
t = time.NewTicker(1 * time.Minute)
// k8s DNS TTL is 30s (Attempt a refresh only after)
t = time.NewTicker(30 * time.Second)
} else {
t = time.NewTicker(10 * time.Minute)
}
@@ -134,7 +135,7 @@ func init() {
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
PassHost: true,
RoundTripper: newHTTPTransport(1 * time.Hour),
RoundTripper: NewHTTPTransportWithTimeout(1 * time.Hour),
Logger: func(err error) {
if err != nil && !errors.Is(err, context.Canceled) {
logger.LogIf(GlobalContext, err)
@@ -773,6 +774,8 @@ func handleCommonEnvVars() {
logger.Info(color.RedBold(msg))
}
globalActiveCred = cred
} else {
globalActiveCred = auth.DefaultCredentials
}
}
+10 -24
View File
@@ -25,7 +25,6 @@ import (
"sync"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/api"
"github.com/minio/minio/internal/config/cache"
@@ -65,7 +64,6 @@ func initHelp() {
config.SiteSubSys: config.DefaultSiteKVS,
config.RegionSubSys: config.DefaultRegionKVS,
config.APISubSys: api.DefaultKVS,
config.CredentialsSubSys: config.DefaultCredentialKVS,
config.LoggerWebhookSubSys: logger.DefaultLoggerWebhookKVS,
config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS,
config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS,
@@ -374,8 +372,7 @@ func validateSubSysConfig(s config.Config, subSys string, objAPI ObjectLayer) er
subSys = config.PolicyPluginSubSys
fallthrough
case config.PolicyPluginSubSys:
if ppargs, err := polplugin.LookupConfig(s[config.PolicyPluginSubSys][config.Default],
NewHTTPTransport(), xhttp.DrainBody); err != nil {
if ppargs, err := polplugin.LookupConfig(s, GetDefaultConnSettings(), xhttp.DrainBody); err != nil {
return err
} else if ppargs.URL == nil {
// Check if legacy opa is configured.
@@ -428,15 +425,6 @@ func validateConfig(s config.Config, subSys string) error {
func lookupConfigs(s config.Config, objAPI ObjectLayer) {
ctx := GlobalContext
var err error
if !globalActiveCred.IsValid() {
// Env doesn't seem to be set, we fallback to lookup creds from the config.
globalActiveCred, err = config.LookupCreds(s[config.CredentialsSubSys][config.Default])
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Invalid credentials configuration: %w", err))
}
}
dnsURL, dnsUser, dnsPass, err := env.LookupEnv(config.EnvDNSWebhook)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize remote webhook DNS config %w", err))
@@ -523,11 +511,13 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
transport := NewHTTPTransport()
bootstrapTrace("lookup the event notification targets")
globalConfigTargetList, err = notify.FetchEnabledTargets(GlobalContext, s, transport)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
}
bootstrapTrace("applying the dynamic configuration")
// Apply dynamic config values
if err := applyDynamicConfig(ctx, objAPI, s); err != nil {
logger.LogIf(ctx, err)
@@ -551,7 +541,7 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
// Initialize remote instance transport once.
getRemoteInstanceTransportOnce.Do(func() {
getRemoteInstanceTransport = newHTTPTransport(apiConfig.RemoteTransportDeadline)
getRemoteInstanceTransport = NewHTTPTransportWithTimeout(apiConfig.RemoteTransportDeadline)
})
case config.CompressionSubSys:
cmpCfg, err := compress.LookupConfig(s[config.CompressionSubSys][config.Default])
@@ -766,13 +756,6 @@ func newSrvConfig(objAPI ObjectLayer) error {
// Initialize server config.
srvCfg := newServerConfig()
if globalActiveCred.IsValid() && !globalActiveCred.Equal(auth.DefaultCredentials) {
kvs := srvCfg[config.CredentialsSubSys][config.Default]
kvs.Set(config.AccessKey, globalActiveCred.AccessKey)
kvs.Set(config.SecretKey, globalActiveCred.SecretKey)
srvCfg[config.CredentialsSubSys][config.Default] = kvs
}
// hold the mutex lock before a new config is assigned.
globalServerConfigMu.Lock()
globalServerConfig = srvCfg
@@ -783,17 +766,20 @@ func newSrvConfig(objAPI ObjectLayer) error {
}
func getValidConfig(objAPI ObjectLayer) (config.Config, error) {
return readServerConfig(GlobalContext, objAPI)
return readServerConfig(GlobalContext, objAPI, nil)
}
// loadConfig - loads a new config from disk, overrides params
// from env if found and valid
func loadConfig(objAPI ObjectLayer) error {
srvCfg, err := getValidConfig(objAPI)
// data is optional. If nil it will be loaded from backend.
func loadConfig(objAPI ObjectLayer, data []byte) error {
bootstrapTrace("load the configuration")
srvCfg, err := readServerConfig(GlobalContext, objAPI, data)
if err != nil {
return err
}
bootstrapTrace("lookup the configuration")
// Override any values from ENVs.
lookupConfigs(srvCfg, objAPI)
+1 -1
View File
@@ -61,7 +61,7 @@ func TestServerConfig(t *testing.T) {
}
// Initialize server config.
if err := loadConfig(objLayer); err != nil {
if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err)
}
}
+14 -9
View File
@@ -37,7 +37,10 @@ func handleEncryptedConfigBackend(objAPI ObjectLayer) error {
if err != nil {
return fmt.Errorf("Unable to encrypt config %w", err)
}
if err = migrateConfigPrefixToEncrypted(objAPI, encrypted); err != nil {
if !encrypted {
return nil
}
if err = migrateConfigPrefixToEncrypted(objAPI); err != nil {
return fmt.Errorf("Unable to migrate all config at .minio.sys/config/: %w", err)
}
return nil
@@ -48,6 +51,7 @@ const backendEncryptedFile = "backend-encrypted"
var backendEncryptedMigrationComplete = []byte("encrypted")
func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool, error) {
bootstrapTrace("check if etcd backend is encrypted")
data, err := readKeyEtcd(ctx, client, backendEncryptedFile)
if err != nil && err != errConfigNotFound {
return false, err
@@ -56,6 +60,7 @@ func checkBackendEtcdEncrypted(ctx context.Context, client *etcd.Client) (bool,
}
func checkBackendEncrypted(objAPI ObjectLayer) (bool, error) {
bootstrapTrace("check if the config backend is encrypted")
data, err := readConfig(GlobalContext, objAPI, backendEncryptedFile)
if err != nil && err != errConfigNotFound {
return false, err
@@ -75,7 +80,9 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
return nil
}
if encrypted && GlobalKMS != nil {
bootstrapTrace("encrypt etcd config")
if GlobalKMS != nil {
stat, err := GlobalKMS.Stat(ctx)
if err != nil {
return err
@@ -136,18 +143,16 @@ func migrateIAMConfigsEtcdToEncrypted(ctx context.Context, client *etcd.Client)
}
}
if encrypted && GlobalKMS != nil {
if GlobalKMS != nil {
logger.Info("Migration of encrypted IAM config data completed. All data is now encrypted on etcd.")
}
return deleteKeyEtcd(ctx, client, backendEncryptedFile)
}
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
if !encrypted {
return nil
}
if encrypted && GlobalKMS != nil {
func migrateConfigPrefixToEncrypted(objAPI ObjectLayer) error {
bootstrapTrace("migrating config prefix to encrypted")
if GlobalKMS != nil {
stat, err := GlobalKMS.Stat(context.Background())
if err != nil {
return err
@@ -202,7 +207,7 @@ func migrateConfigPrefixToEncrypted(objAPI ObjectLayer, encrypted bool) error {
}
}
if encrypted && GlobalKMS != nil {
if GlobalKMS != nil {
logger.Info("Migration of encrypted config data completed. All config data is now encrypted.")
}
+18 -2
View File
@@ -2425,6 +2425,7 @@ func migrateV27ToV28() error {
// Migrates ${HOME}/.minio/config.json to '<export_path>/.minio.sys/config/config.json'
// if etcd is configured then migrates /config/config.json to '<export_path>/.minio.sys/config/config.json'
func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
bootstrapTrace("migrate config to .minio.sys/config/config.json")
// Construct path to config.json for the given bucket.
configFile := path.Join(minioConfigPrefix, minioConfigFile)
@@ -2472,6 +2473,7 @@ func migrateConfigToMinioSys(objAPI ObjectLayer) (err error) {
// Migrates '.minio.sys/config.json' to v33.
func migrateMinioSysConfig(objAPI ObjectLayer) error {
bootstrapTrace("migrate .minio.sys/config/config.json to latest version")
// Construct path to config.json for the given bucket.
configFile := path.Join(minioConfigPrefix, minioConfigFile)
@@ -2533,6 +2535,21 @@ func checkConfigVersion(objAPI ObjectLayer, configFile string, version string) (
}
}
if version == "kvs" {
// Check api config values are present.
var vcfg struct {
API struct {
E []struct {
Key string `json:"key"`
Value string `json:"value"`
} `json:"_"`
} `json:"api"`
}
if err = json.Unmarshal(data, &vcfg); err != nil {
return false, nil, nil
}
return len(vcfg.API.E) > 0, data, nil
}
var versionConfig struct {
Version string `json:"version"`
}
@@ -2722,6 +2739,7 @@ func migrateV32ToV33MinioSys(objAPI ObjectLayer) error {
}
func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
bootstrapTrace("migrate config to KV style")
configFile := path.Join(minioConfigPrefix, minioConfigFile)
// Check if the config version is latest, if not migrate.
@@ -2740,9 +2758,7 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
newCfg := newServerConfig()
config.SetCredentials(newCfg, cfg.Credential)
config.SetRegion(newCfg, cfg.Region)
storageclass.SetStorageClass(newCfg, cfg.StorageClass)
for k, loggerArgs := range cfg.Logger.HTTP {
+2 -15
View File
@@ -22,8 +22,6 @@ import (
"fmt"
"os"
"testing"
"github.com/minio/minio/internal/config"
)
// Test if config v1 is purged
@@ -65,7 +63,7 @@ func TestServerConfigMigrateV1(t *testing.T) {
}
// Initialize server config and check again if everything is fine
if err := loadConfig(objLayer); err != nil {
if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err)
}
}
@@ -207,20 +205,9 @@ func TestServerConfigMigrateV2toV33(t *testing.T) {
}
// Initialize server config and check again if everything is fine
if err := loadConfig(objLayer); err != nil {
if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err)
}
// Check if accessKey and secretKey are not altered during migration
caccessKey := globalServerConfig[config.CredentialsSubSys][config.Default].Get(config.AccessKey)
if caccessKey != accessKey {
t.Fatalf("Access key lost during migration, expected: %v, found:%v", accessKey, caccessKey)
}
csecretKey := globalServerConfig[config.CredentialsSubSys][config.Default].Get(config.SecretKey)
if csecretKey != secretKey {
t.Fatalf("Secret key lost during migration, expected: %v, found: %v", secretKey, csecretKey)
}
}
// Test if all migrate code returns error with corrupted config files
+33 -18
View File
@@ -152,30 +152,34 @@ func saveServerConfig(ctx context.Context, objAPI ObjectLayer, cfg interface{})
return saveConfig(ctx, objAPI, configFile, data)
}
func readServerConfig(ctx context.Context, objAPI ObjectLayer) (config.Config, error) {
// data is optional. If nil it will be loaded from backend.
func readServerConfig(ctx context.Context, objAPI ObjectLayer, data []byte) (config.Config, error) {
srvCfg := config.New()
configFile := path.Join(minioConfigPrefix, minioConfigFile)
data, err := readConfig(ctx, objAPI, configFile)
if err != nil {
if errors.Is(err, errConfigNotFound) {
lookupConfigs(srvCfg, objAPI)
return srvCfg, nil
}
return nil, err
}
if GlobalKMS != nil && !utf8.Valid(data) {
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
minioMetaBucket: path.Join(minioMetaBucket, configFile),
})
var err error
if len(data) == 0 {
configFile := path.Join(minioConfigPrefix, minioConfigFile)
data, err = readConfig(ctx, objAPI, configFile)
if err != nil {
lookupConfigs(srvCfg, objAPI)
if errors.Is(err, errConfigNotFound) {
lookupConfigs(srvCfg, objAPI)
return srvCfg, nil
}
return nil, err
}
if GlobalKMS != nil && !utf8.Valid(data) {
data, err = config.DecryptBytes(GlobalKMS, data, kms.Context{
minioMetaBucket: path.Join(minioMetaBucket, configFile),
})
if err != nil {
lookupConfigs(srvCfg, objAPI)
return nil, err
}
}
}
json := jsoniter.ConfigCompatibleWithStandardLibrary
if err = json.Unmarshal(data, &srvCfg); err != nil {
if err := json.Unmarshal(data, &srvCfg); err != nil {
return nil, err
}
@@ -202,6 +206,8 @@ func NewConfigSys() *ConfigSys {
// Initialize and load config from remote etcd or local config directory
func initConfig(objAPI ObjectLayer) error {
bootstrapTrace("load the configuration")
if objAPI == nil {
return errServerNotInitialized
}
@@ -212,6 +218,15 @@ func initConfig(objAPI ObjectLayer) error {
}
}
// Check if the config version is latest (kvs), if not migrate.
ok, data, err := checkConfigVersion(objAPI, path.Join(minioConfigPrefix, minioConfigFile), "kvs")
if err != nil && !errors.Is(err, errConfigNotFound) {
return err
}
if ok {
return loadConfig(objAPI, data)
}
// Migrates ${HOME}/.minio/config.json or config.json.deprecated
// to '<export_path>/.minio.sys/config/config.json'
// ignore if the file doesn't exist.
@@ -232,5 +247,5 @@ func initConfig(objAPI ObjectLayer) error {
return fmt.Errorf("migrateMinioSysConfigToKV: %w", err)
}
return loadConfig(objAPI)
return loadConfig(objAPI, nil)
}
+8 -3
View File
@@ -761,7 +761,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
object: entry.name,
versionID: "",
}, madmin.HealItemObject)
logger.LogIf(ctx, err)
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
foundObjs = foundObjs || err == nil
return
}
@@ -776,7 +778,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
object: fiv.Name,
versionID: ver.VersionID,
}, madmin.HealItemObject)
logger.LogIf(ctx, err)
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
logger.LogIf(ctx, err)
}
foundObjs = foundObjs || err == nil
}
},
@@ -993,7 +997,8 @@ func (i *scannerItem) applyTierObjSweep(ctx context.Context, o ObjectLayer, oi O
// Remove this free version
_, err = o.DeleteObject(ctx, oi.Bucket, oi.Name, ObjectOptions{
VersionID: oi.VersionID,
VersionID: oi.VersionID,
InclFreeVersions: true,
})
if err == nil {
auditLogLifecycle(ctx, oi, ILMFreeVersionDelete)
+8 -8
View File
@@ -512,7 +512,7 @@ func (c *diskCache) statCachedMeta(ctx context.Context, cacheObjPath string) (me
return
}
ctx = lkctx.Context()
defer cLock.RUnlock(lkctx.Cancel)
defer cLock.RUnlock(lkctx)
return c.statCache(ctx, cacheObjPath)
}
@@ -597,7 +597,7 @@ func (c *diskCache) SaveMetadata(ctx context.Context, bucket, object string, met
return err
}
ctx = lkctx.Context()
defer cLock.Unlock(lkctx.Cancel)
defer cLock.Unlock(lkctx)
if err = c.saveMetadata(ctx, bucket, object, meta, actualSize, rs, rsFileName, incHitsOnly); err != nil {
return err
}
@@ -842,7 +842,7 @@ func (c *diskCache) Put(ctx context.Context, bucket, object string, data io.Read
return oi, err
}
ctx = lkctx.Context()
defer cLock.Unlock(lkctx.Cancel)
defer cLock.Unlock(lkctx)
return c.put(ctx, bucket, object, data, size, rs, opts, incHitsOnly, writeback)
}
@@ -1076,7 +1076,7 @@ func (c *diskCache) Get(ctx context.Context, bucket, object string, rs *HTTPRang
return nil, numHits, err
}
ctx = lkctx.Context()
defer cLock.RUnlock(lkctx.Cancel)
defer cLock.RUnlock(lkctx)
var objInfo ObjectInfo
var rngInfo RangeInfo
@@ -1213,7 +1213,7 @@ func (c *diskCache) Delete(ctx context.Context, bucket, object string) (err erro
if err != nil {
return err
}
defer cLock.Unlock(lkctx.Cancel)
defer cLock.Unlock(lkctx)
return removeAll(cacheObjPath)
}
@@ -1324,7 +1324,7 @@ func (c *diskCache) PutObjectPart(ctx context.Context, bucket, object, uploadID
}
ctx = lkctx.Context()
defer partIDLock.Unlock(lkctx.Cancel)
defer partIDLock.Unlock(lkctx)
meta, _, _, err := c.statCache(ctx, uploadIDDir)
// Case where object not yet cached
if err != nil {
@@ -1381,7 +1381,7 @@ func (c *diskCache) SavePartMetadata(ctx context.Context, bucket, object, upload
if err != nil {
return err
}
defer uploadLock.Unlock(ulkctx.Cancel)
defer uploadLock.Unlock(ulkctx)
metaPath := pathJoin(uploadDir, cacheMetaJSONFile)
f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666)
@@ -1480,7 +1480,7 @@ func (c *diskCache) CompleteMultipartUpload(ctx context.Context, bucket, object,
}
ctx = lkctx.Context()
defer cLock.Unlock(lkctx.Cancel)
defer cLock.Unlock(lkctx)
mpartCachePath := getMultipartCacheSHADir(c.dir, bucket, object)
uploadIDDir := path.Join(mpartCachePath, uploadID)
+1 -1
View File
@@ -728,7 +728,7 @@ func (c *cacheObjects) PutObject(ctx context.Context, bucket, object string, r *
if cerr != nil {
return putObjectFn(ctx, bucket, object, r, opts)
}
defer cLock.Unlock(lkctx.Cancel)
defer cLock.Unlock(lkctx)
// Initialize pipe to stream data to backend
pipeReader, pipeWriter := io.Pipe()
hashReader, err := hash.NewReader(pipeReader, size, "", "", r.ActualSize())
+43 -2
View File
@@ -417,6 +417,27 @@ func (endpoints Endpoints) atleastOneEndpointLocal() bool {
return false
}
func hostResolveToLocalhost(endpoint Endpoint) bool {
hostIPs, err := getHostIP(endpoint.Hostname())
if err != nil {
// Log the message to console about the host resolving
reqInfo := (&logger.ReqInfo{}).AppendTags(
"host",
endpoint.Hostname(),
)
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
return false
}
var loopback int
for _, hostIP := range hostIPs.ToSlice() {
if net.ParseIP(hostIP).IsLoopback() {
loopback++
}
}
return loopback == len(hostIPs)
}
// UpdateIsLocal - resolves the host and discovers the local host.
func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
orchestrated := IsDocker() || IsKubernetes()
@@ -452,6 +473,26 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
endpoints[i].Hostname(),
)
if orchestrated && hostResolveToLocalhost(endpoints[i]) {
// time elapsed
timeElapsed := time.Since(startTime)
// log error only if more than a second has elapsed
if timeElapsed > time.Second {
reqInfo.AppendTags("elapsedTime",
humanize.RelTime(startTime,
startTime.Add(timeElapsed),
"elapsed",
"",
))
ctx := logger.SetReqInfo(GlobalContext,
reqInfo)
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.Application)
}
continue
}
// return err if not Docker or Kubernetes
// We use IsDocker() to check for Docker environment
// We use IsKubernetes() to check for Kubernetes environment
@@ -465,7 +506,7 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
if err != nil {
// time elapsed
timeElapsed := time.Since(startTime)
// log error only if more than 1s elapsed
// log error only if more than a second has elapsed
if timeElapsed > time.Second {
reqInfo.AppendTags("elapsedTime",
humanize.RelTime(startTime,
@@ -475,7 +516,7 @@ func (endpoints Endpoints) UpdateIsLocal(foundPrevLocal bool) error {
))
ctx := logger.SetReqInfo(GlobalContext,
reqInfo)
logger.LogIf(ctx, err, logger.Application)
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.Application)
}
} else {
resolvedList[i] = true
-177
View File
@@ -21,8 +21,6 @@ import (
"context"
"errors"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/sync/errgroup"
)
@@ -32,181 +30,6 @@ var bucketOpIgnoredErrs = append(baseIgnoredErrs, errDiskAccessDenied, errUnform
// list all errors that can be ignored in a bucket metadata operation.
var bucketMetadataOpIgnoredErrs = append(bucketOpIgnoredErrs, errVolumeNotFound)
// Bucket operations
// MakeBucket - make a bucket.
func (er erasureObjects) MakeBucketWithLocation(ctx context.Context, bucket string, opts MakeBucketOptions) error {
defer NSUpdated(bucket, slashSeparator)
// Verify if bucket is valid.
if !isMinioMetaBucketName(bucket) {
if err := s3utils.CheckValidBucketNameStrict(bucket); err != nil {
return BucketNameInvalid{Bucket: bucket}
}
}
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 {
if err := storageDisks[index].MakeVol(ctx, bucket); err != nil {
if opts.ForceCreate && errors.Is(err, errVolumeExists) {
// No need to return error when force create was
// requested.
return nil
}
if !errors.Is(err, errVolumeExists) {
logger.LogIf(ctx, err)
}
return err
}
return nil
}
return errDiskNotFound
}, index)
}
err := reduceWriteQuorumErrs(ctx, g.Wait(), bucketOpIgnoredErrs, er.defaultWQuorum())
return toObjectErr(err, bucket)
}
func undoDeleteBucket(storageDisks []StorageAPI, bucket string) {
g := errgroup.WithNErrs(len(storageDisks))
// Undo previous make bucket entry on all underlying storage disks.
for index := range storageDisks {
if storageDisks[index] == nil {
continue
}
index := index
g.Go(func() error {
_ = storageDisks[index].MakeVol(context.Background(), bucket)
return nil
}, index)
}
// Wait for all make vol to finish.
g.Wait()
}
// getBucketInfo - returns the BucketInfo from one of the load balanced disks.
func (er erasureObjects) getBucketInfo(ctx context.Context, bucketName string, opts BucketOptions) (bucketInfo BucketInfo, err error) {
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
bucketsInfo := make([]BucketInfo, len(storageDisks))
// Undo previous make bucket entry on all underlying storage disks.
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] == nil {
return errDiskNotFound
}
volInfo, err := storageDisks[index].StatVol(ctx, bucketName)
if err != nil {
if opts.Deleted {
dvi, derr := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucketName))
if derr != nil {
return err
}
bucketsInfo[index] = BucketInfo{Name: bucketName, Deleted: dvi.Created}
return nil
}
return err
}
bucketsInfo[index] = BucketInfo{Name: volInfo.Name, Created: volInfo.Created}
return nil
}, index)
}
errs := g.Wait()
for i, err := range errs {
if err == nil {
return bucketsInfo[i], nil
}
}
// If all our errors were ignored, then we try to
// reduce to one error based on read quorum.
// `nil` is deliberately passed for ignoredErrs
// because these errors were already ignored.
return BucketInfo{}, reduceReadQuorumErrs(ctx, errs, nil, er.defaultRQuorum())
}
// GetBucketInfo - returns BucketInfo for a bucket.
func (er erasureObjects) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bi BucketInfo, e error) {
bucketInfo, err := er.getBucketInfo(ctx, bucket, opts)
if err != nil {
return bi, toObjectErr(err, bucket)
}
return bucketInfo, nil
}
// DeleteBucket - deletes a bucket.
func (er erasureObjects) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
// Collect if all disks report volume not found.
defer NSUpdated(bucket, slashSeparator)
storageDisks := er.getDisks()
g := errgroup.WithNErrs(len(storageDisks))
for index := range storageDisks {
index := index
g.Go(func() error {
if storageDisks[index] != nil {
return storageDisks[index].DeleteVol(ctx, bucket, opts.Force)
}
return errDiskNotFound
}, index)
}
// Wait for all the delete vols to finish.
dErrs := g.Wait()
if opts.Force {
for _, err := range dErrs {
if err != nil {
undoDeleteBucket(storageDisks, bucket)
return toObjectErr(err, bucket)
}
}
return nil
}
err := reduceWriteQuorumErrs(ctx, dErrs, bucketOpIgnoredErrs, er.defaultWQuorum())
if err == errErasureWriteQuorum && !opts.NoRecreate {
undoDeleteBucket(storageDisks, bucket)
}
if err == nil || errors.Is(err, errVolumeNotFound) {
var purgedDangling bool
// At this point we have `err == nil` but some errors might be `errVolumeNotEmpty`
// we should proceed to attempt a force delete of such buckets.
for index, err := range dErrs {
if err == errVolumeNotEmpty && storageDisks[index] != nil {
storageDisks[index].RenameFile(ctx, bucket, "", minioMetaTmpDeletedBucket, mustGetUUID())
purgedDangling = true
}
}
// if we purged dangling buckets, ignore errVolumeNotFound error.
if purgedDangling {
err = nil
}
if opts.SRDeleteOp == MarkDelete {
er.markDelete(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix, bucket))
}
}
return toObjectErr(err, bucket)
}
// 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 {
+11 -3
View File
@@ -22,6 +22,7 @@ import (
"io"
"sync"
"github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger"
)
@@ -83,9 +84,16 @@ func (e *Erasure) Encode(ctx context.Context, src io.Reader, writers []io.Writer
for {
var blocks [][]byte
n, err := io.ReadFull(src, buf)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
logger.LogIf(ctx, err)
return 0, err
if err != nil {
if !IsErrIgnored(err, []error{
io.EOF,
io.ErrUnexpectedEOF,
}...) {
if !hash.IsChecksumMismatch(err) {
logger.LogIf(ctx, err)
}
return 0, err
}
}
eof := err == io.EOF || err == io.ErrUnexpectedEOF
if n == 0 && total != 0 {
+3 -3
View File
@@ -223,7 +223,7 @@ func TestListOnlineDisks(t *testing.T) {
}
bucket := "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket %v", err)
}
@@ -398,7 +398,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
}
bucket := "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket %v", err)
}
@@ -519,7 +519,7 @@ func TestDisksWithAllParts(t *testing.T) {
z := obj.(*erasureServerPools)
s := z.serverPools[0].sets[0]
erasureDisks := s.getDisks()
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket %v", err)
}
+6 -2
View File
@@ -235,6 +235,10 @@ func listAllBuckets(ctx context.Context, storageDisks []StorageAPI, healBuckets
// we ignore disk not found errors
return nil
}
if storageDisks[index].Healing() != nil {
// we ignore disks under healing
return nil
}
volsInfo, err := storageDisks[index].ListVols(ctx)
if err != nil {
return err
@@ -324,7 +328,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
return result, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
}
// Re-read when we have lock...
@@ -695,7 +699,7 @@ func (er *erasureObjects) checkAbandonedParts(ctx context.Context, bucket string
return err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
}
var wg sync.WaitGroup
for _, disk := range er.getDisks() {
+10 -10
View File
@@ -244,7 +244,7 @@ func TestHealing(t *testing.T) {
er := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -398,7 +398,7 @@ func TestHealingVersioned(t *testing.T) {
er := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{VersioningEnabled: true})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{VersioningEnabled: true})
if err != nil {
t.Fatal(err)
}
@@ -581,7 +581,7 @@ func TestHealingDanglingObject(t *testing.T) {
object := getRandomObjectName()
data := bytes.Repeat([]byte("a"), 128*1024)
err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -776,7 +776,7 @@ func TestHealCorrectQuorum(t *testing.T) {
data := bytes.Repeat([]byte("a"), 5*1024*1024)
var opts ObjectOptions
err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -907,7 +907,7 @@ func TestHealObjectCorruptedPools(t *testing.T) {
data := bytes.Repeat([]byte("a"), 5*1024*1024)
var opts ObjectOptions
err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -1084,7 +1084,7 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
data := bytes.Repeat([]byte("a"), 5*1024*1024)
var opts ObjectOptions
err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -1227,7 +1227,7 @@ func TestHealObjectCorruptedParts(t *testing.T) {
data := bytes.Repeat([]byte("a"), 5*1024*1024)
var opts ObjectOptions
err = objLayer.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -1384,7 +1384,7 @@ func TestHealObjectErasure(t *testing.T) {
data := bytes.Repeat([]byte("a"), 5*1024*1024)
var opts ObjectOptions
err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = obj.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -1481,7 +1481,7 @@ func TestHealEmptyDirectoryErasure(t *testing.T) {
object := "empty-dir/"
var opts ObjectOptions
err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = obj.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
@@ -1582,7 +1582,7 @@ func TestHealLastDataShard(t *testing.T) {
}
var opts ObjectOptions
err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = obj.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket - %v", err)
}
+7 -3
View File
@@ -147,9 +147,9 @@ func (fi FileInfo) ToObjectInfo(bucket, object string, versioned bool) ObjectInf
// Add replication status to the object info
objInfo.ReplicationStatusInternal = fi.ReplicationState.ReplicationStatusInternal
objInfo.VersionPurgeStatusInternal = fi.ReplicationState.VersionPurgeStatusInternal
objInfo.ReplicationStatus = fi.ReplicationState.CompositeReplicationStatus()
objInfo.ReplicationStatus = fi.ReplicationStatus()
objInfo.VersionPurgeStatus = fi.VersionPurgeStatus()
objInfo.VersionPurgeStatus = fi.ReplicationState.CompositeVersionPurgeStatus()
objInfo.TransitionedObject = TransitionedObject{
Name: fi.TransitionedObjName,
VersionID: fi.TransitionVersionID,
@@ -176,7 +176,6 @@ func (fi FileInfo) ToObjectInfo(bucket, object string, versioned bool) ObjectInf
objInfo.StorageClass = globalMinioDefaultStorageClass
}
objInfo.VersionPurgeStatus = fi.VersionPurgeStatus()
// set restore status for transitioned object
restoreHdr, ok := fi.Metadata[xhttp.AmzRestore]
if ok {
@@ -534,6 +533,11 @@ func (fi *FileInfo) VersionPurgeStatus() VersionPurgeStatusType {
return fi.ReplicationState.CompositeVersionPurgeStatus()
}
// ReplicationStatus returns overall version replication status for this object version across targets
func (fi *FileInfo) ReplicationStatus() replication.StatusType {
return fi.ReplicationState.CompositeReplicationStatus()
}
// DeleteMarkerReplicationStatus returns overall replication status for this delete marker version across targets
func (fi *FileInfo) DeleteMarkerReplicationStatus() replication.StatusType {
if fi.Deleted {
+15 -11
View File
@@ -349,7 +349,7 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
}
rctx := lkctx.Context()
obj, err := er.getObjectInfo(rctx, bucket, object, opts)
lk.RUnlock(lkctx.Cancel)
lk.RUnlock(lkctx)
if err != nil && !isErrVersionNotFound(err) {
return nil, err
}
@@ -557,7 +557,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
return PartInfo{}, err
}
rctx := rlkctx.Context()
defer uploadIDRLock.RUnlock(rlkctx.Cancel)
defer uploadIDRLock.RUnlock(rlkctx)
uploadIDPath := er.getUploadIDDir(bucket, object, uploadID)
// Validates if upload ID exists.
@@ -583,7 +583,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
return PartInfo{}, err
}
pctx := plkctx.Context()
defer partIDLock.Unlock(plkctx.Cancel)
defer partIDLock.Unlock(plkctx)
onlineDisks := er.getDisks()
writeQuorum := fi.WriteQuorum(er.defaultWQuorum())
@@ -755,7 +755,7 @@ func (er erasureObjects) GetMultipartInfo(ctx context.Context, bucket, object, u
return MultipartInfo{}, err
}
ctx = lkctx.Context()
defer uploadIDLock.RUnlock(lkctx.Cancel)
defer uploadIDLock.RUnlock(lkctx)
fi, _, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, false)
if err != nil {
@@ -782,7 +782,7 @@ func (er erasureObjects) ListObjectParts(ctx context.Context, bucket, object, up
return ListPartsInfo{}, err
}
ctx = lkctx.Context()
defer uploadIDLock.RUnlock(lkctx.Cancel)
defer uploadIDLock.RUnlock(lkctx)
fi, _, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, false)
if err != nil {
@@ -915,7 +915,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
return oi, err
}
wctx := wlkctx.Context()
defer uploadIDLock.Unlock(wlkctx.Cancel)
defer uploadIDLock.Unlock(wlkctx)
fi, partsMetadata, err := er.checkUploadIDExists(wctx, bucket, object, uploadID, true)
if err != nil {
@@ -1184,7 +1184,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
return oi, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
// Write final `xl.meta` at uploadID location
onlineDisks, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath, partsMetadata, writeQuorum)
@@ -1211,13 +1211,17 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
}
// Rename the multipart object to final location.
if onlineDisks, err = renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
partsMetadata, bucket, object, writeQuorum); err != nil {
onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath,
partsMetadata, bucket, object, writeQuorum)
if err != nil {
return oi, toObjectErr(err, bucket, object)
}
defer NSUpdated(bucket, object)
if !opts.Speedtest && versionsDisparity {
listAndHeal(ctx, bucket, object, &er, healObjectVersionsDisparity)
}
// Check if there is any offline disk and add it to the MRF list
for _, disk := range onlineDisks {
if disk != nil && disk.IsOnline() {
@@ -1259,7 +1263,7 @@ func (er erasureObjects) AbortMultipartUpload(ctx context.Context, bucket, objec
return err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
// Validates if upload ID exists.
if _, _, err = er.checkUploadIDExists(ctx, bucket, object, uploadID, false); err != nil {
+100 -79
View File
@@ -81,7 +81,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
return oi, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
}
// Read metadata associated with the object from all disks.
storageDisks := er.getDisks()
@@ -93,7 +93,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
if srcOpts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true)
} else {
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true)
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false)
}
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -206,14 +206,14 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.Unlock(lkctx.Cancel) }
nsUnlocker = func() { lock.Unlock(lkctx) }
case readLock:
lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.RUnlock(lkctx.Cancel) }
nsUnlocker = func() { lock.RUnlock(lkctx) }
}
unlockOnDefer = true
}
@@ -434,7 +434,7 @@ func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object strin
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer lk.RUnlock(lkctx.Cancel)
defer lk.RUnlock(lkctx)
}
return er.getObjectInfo(ctx, bucket, object, opts)
@@ -501,7 +501,7 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
return m, err
}
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData bool) ([]FileInfo, []error) {
func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, readData, inclFreeVers bool) ([]FileInfo, []error) {
metadataArray := make([]*xlMetaV2, len(disks))
metaFileInfos := make([]FileInfo, len(metadataArray))
metadataShallowVersions := make([][]xlMetaV2ShallowVersion, len(disks))
@@ -566,7 +566,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
readQuorum := (len(disks) + 1) / 2
meta := &xlMetaV2{versions: mergeXLV2Versions(readQuorum, false, 1, metadataShallowVersions...)}
lfi, err := meta.ToFileInfo(bucket, object, "")
lfi, err := meta.ToFileInfo(bucket, object, "", inclFreeVers)
if err != nil {
for i := range errs {
if errs[i] == nil {
@@ -596,7 +596,7 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
// make sure to preserve this for diskmtime based healing bugfix.
diskMTime := metaFileInfos[index].DiskMTime
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID)
metaFileInfos[index], errs[index] = metadataArray[index].ToFileInfo(bucket, object, versionID, inclFreeVers)
if errs[index] != nil {
continue
}
@@ -620,7 +620,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, readData)
} else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData)
metaArr, errs = readAllXL(ctx, disks, bucket, object, readData, opts.InclFreeVersions)
}
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -729,7 +729,7 @@ func (er erasureObjects) getObjectInfoAndQuorum(ctx context.Context, bucket, obj
}
// Similar to rename but renames data from srcEntry to dstEntry at dataDir
func renameData(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry string, metadata []FileInfo, dstBucket, dstEntry string, writeQuorum int) ([]StorageAPI, error) {
func renameData(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry string, metadata []FileInfo, dstBucket, dstEntry string, writeQuorum int) ([]StorageAPI, bool, error) {
g := errgroup.WithNErrs(len(disks))
fvID := mustGetUUID()
@@ -768,20 +768,22 @@ func renameData(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry str
// Wait for all renames to finish.
errs := g.Wait()
var versionsDisparity bool
err := reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
if err == nil {
versions := reduceCommonVersions(diskVersions, writeQuorum)
for index, dversions := range diskVersions {
for _, dversions := range diskVersions {
if versions != dversions {
errs[index] = errFileNeedsHealing
versionsDisparity = true
break
}
}
err = reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
}
// We can safely allow RenameData errors up to len(er.getDisks()) - writeQuorum
// otherwise return failure.
return evalDisks(disks, errs), err
return evalDisks(disks, errs), versionsDisparity, err
}
func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
@@ -934,6 +936,47 @@ func (er erasureObjects) PutObject(ctx context.Context, bucket string, object st
return er.putObject(ctx, bucket, object, data, opts)
}
// Heal up to two versions of one object when there is disparity between disks
func healObjectVersionsDisparity(bucket string, entry metaCacheEntry) error {
if entry.isDir() {
return nil
}
// We might land at .metacache, .trash, .multipart
// no need to heal them skip, only when bucket
// is '.minio.sys'
if bucket == minioMetaBucket {
if wildcard.Match("buckets/*/.metacache/*", entry.name) {
return nil
}
if wildcard.Match("tmp/*", entry.name) {
return nil
}
if wildcard.Match("multipart/*", entry.name) {
return nil
}
if wildcard.Match("tmp-old/*", entry.name) {
return nil
}
}
fivs, err := entry.fileInfoVersions(bucket)
if err != nil {
healObject(bucket, entry.name, "", madmin.HealNormalScan)
return err
}
if len(fivs.Versions) > 2 {
return fmt.Errorf("bucket(%s)/object(%s) object with %d versions needs healing, allowing lazy healing via scanner instead here for versions greater than 2",
bucket, entry.name, len(fivs.Versions))
}
for _, version := range fivs.Versions {
healObject(bucket, entry.name, version.VersionID, madmin.HealNormalScan)
}
return nil
}
// putObject wrapper for erasureObjects PutObject
func (er erasureObjects) putObject(ctx context.Context, bucket string, object string, r *PutObjReader, opts ObjectOptions) (objInfo ObjectInfo, err error) {
auditObjectErasureSet(ctx, object, &er)
@@ -1152,7 +1195,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
}
modTime := opts.MTime
@@ -1205,7 +1248,17 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
}
// Rename the successfully written temporary object to final location.
onlineDisks, err = renameData(ctx, onlineDisks, minioMetaTmpBucket, tempObj, partsMetadata, bucket, object, writeQuorum)
onlineDisks, versionsDisparity, err := renameData(ctx, onlineDisks, minioMetaTmpBucket, tempObj, partsMetadata, bucket, object, writeQuorum)
if err != nil {
if errors.Is(err, errFileNotFound) {
return ObjectInfo{}, toObjectErr(errErasureWriteQuorum, bucket, object)
}
logger.LogIf(ctx, err)
return ObjectInfo{}, toObjectErr(err, bucket, object)
}
defer NSUpdated(bucket, object)
for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
// Object info is the same in all disks, so we can pick
@@ -1227,63 +1280,12 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
er.addPartial(bucket, object, fi.VersionID, fi.Size)
break
}
}
healEntry := func(entry metaCacheEntry) error {
if entry.isDir() {
return nil
}
// We might land at .metacache, .trash, .multipart
// no need to heal them skip, only when bucket
// is '.minio.sys'
if bucket == minioMetaBucket {
if wildcard.Match("buckets/*/.metacache/*", entry.name) {
return nil
}
if wildcard.Match("tmp/*", entry.name) {
return nil
}
if wildcard.Match("multipart/*", entry.name) {
return nil
}
if wildcard.Match("tmp-old/*", entry.name) {
return nil
}
}
fivs, err := entry.fileInfoVersions(bucket)
if err != nil {
healObject(bucket, entry.name, "", madmin.HealNormalScan)
return err
}
if len(fivs.Versions) > 2 {
return fmt.Errorf("bucket(%s)/object(%s) object with %d versions needs healing, allowing lazy healing via scanner instead here for versions greater than 2",
bucket, entry.name,
len(fivs.Versions))
}
for _, version := range fivs.Versions {
healObject(bucket, entry.name, version.VersionID, madmin.HealNormalScan)
}
return nil
}
if err != nil {
if errors.Is(err, errFileNotFound) {
return ObjectInfo{}, toObjectErr(errErasureWriteQuorum, bucket, object)
}
if errors.Is(err, errFileNeedsHealing) && !opts.Speedtest {
listAndHeal(ctx, bucket, object, &er, healEntry)
} else {
logger.LogIf(ctx, err)
return ObjectInfo{}, toObjectErr(err, bucket, object)
if versionsDisparity {
listAndHeal(ctx, bucket, object, &er, healObjectVersionsDisparity)
}
}
defer NSUpdated(bucket, object)
fi.ReplicationState = opts.PutReplicationState()
online = countOnlineDisks(onlineDisks)
@@ -1602,11 +1604,9 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
markDelete := goi.VersionID != ""
// Default deleteMarker to true if object is under versioning
// versioning suspended means we add `null` version as
// delete marker, if its not decided already.
deleteMarker := (opts.Versioned || opts.VersionSuspended) && opts.VersionID == "" || (opts.DeleteMarker && opts.VersionID != "")
deleteMarker := opts.Versioned
if markDelete && opts.VersionID != "" {
if opts.VersionID != "" {
// case where replica version needs to be deleted on target cluster
if versionFound && opts.DeleteMarkerReplicationStatus() == replication.Replica {
markDelete = false
@@ -1617,6 +1617,22 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
if opts.VersionPurgeStatus() == Complete {
markDelete = false
}
// now, since VersionPurgeStatus() is already set, we can let the
// lower layers decide this. This fixes a regression that was introduced
// in PR #14555 where !VersionPurgeStatus.Empty() is automatically
// considered as Delete marker true to avoid listing such objects by
// regular ListObjects() calls. However for delete replication this
// ends up being a problem because "upon" a successful delete this
// ends up creating a new delete marker that is spurious and unnecessary.
//
// Regression introduced by #14555 was reintroduced in #15564
if versionFound {
if !goi.VersionPurgeStatus.Empty() {
deleteMarker = false
} else if !goi.DeleteMarker { // implies a versioned delete of object
deleteMarker = false
}
}
}
modTime := opts.MTime
@@ -1626,9 +1642,14 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
fvID := mustGetUUID()
if markDelete && (opts.Versioned || opts.VersionSuspended) {
if !deleteMarker {
// versioning suspended means we add `null` version as
// delete marker, if its not decided already.
deleteMarker = opts.VersionSuspended && opts.VersionID == ""
}
fi := FileInfo{
Name: object,
Deleted: true,
Deleted: deleteMarker,
MarkDeleted: markDelete,
ModTime: modTime,
ReplicationState: opts.DeleteReplication,
@@ -1700,7 +1721,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
}
disks := er.getDisks()
@@ -1712,7 +1733,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
} else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, false)
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false)
}
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -1774,7 +1795,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
disks := er.getDisks()
@@ -1785,7 +1806,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
if opts.VersionID != "" {
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false)
} else {
metaArr, errs = readAllXL(ctx, disks, bucket, object, false)
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false)
}
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
@@ -1884,7 +1905,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
return err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
fi, metaArr, onlineDisks, err := er.getObjectFileInfo(ctx, bucket, object, opts, true)
if err != nil {
+29 -33
View File
@@ -53,7 +53,7 @@ func TestRepeatPutObjectPart(t *testing.T) {
defer objLayer.Shutdown(context.Background())
defer removeRoots(disks)
err = objLayer.MakeBucketWithLocation(ctx, "bucket1", MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, "bucket1", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -101,7 +101,7 @@ func TestErasureDeleteObjectBasic(t *testing.T) {
}
defer xl.Shutdown(context.Background())
err = xl.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = xl.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -155,7 +155,7 @@ func TestDeleteObjectsVersioned(t *testing.T) {
{bucketName, "dir/obj1"},
}
err = obj.MakeBucketWithLocation(ctx, bucketName, MakeBucketOptions{
err = obj.MakeBucket(ctx, bucketName, MakeBucketOptions{
VersioningEnabled: true,
})
if err != nil {
@@ -214,22 +214,19 @@ func TestDeleteObjectsVersioned(t *testing.T) {
func TestErasureDeleteObjectsErasureSet(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var objs []*erasureObjects
for i := 0; i < 32; i++ {
obj, fsDirs, err := prepareErasure(ctx, 16)
if err != nil {
t.Fatal("Unable to initialize 'Erasure' object layer.", err)
}
// Remove all dirs.
for _, dir := range fsDirs {
defer os.RemoveAll(dir)
}
z := obj.(*erasureServerPools)
xl := z.serverPools[0].sets[0]
objs = append(objs, xl)
obj, fsDirs, err := prepareErasureSets32(ctx)
if err != nil {
t.Fatal("Unable to initialize 'Erasure' object layer.", err)
}
erasureSets := &erasureSets{sets: objs, distributionAlgo: "CRCMOD"}
setObjectLayer(obj)
initConfigSubsystem(ctx, obj)
// Remove all dirs.
for _, dir := range fsDirs {
defer os.RemoveAll(dir)
}
type testCaseType struct {
bucket string
@@ -244,13 +241,12 @@ func TestErasureDeleteObjectsErasureSet(t *testing.T) {
{bucketName, "obj_4"},
}
err := erasureSets.MakeBucketWithLocation(ctx, bucketName, MakeBucketOptions{})
if err != nil {
if err = obj.MakeBucket(ctx, bucketName, MakeBucketOptions{}); err != nil {
t.Fatal(err)
}
for _, testCase := range testCases {
_, err = erasureSets.PutObject(ctx, testCase.bucket, testCase.object,
_, err = obj.PutObject(ctx, testCase.bucket, testCase.object,
mustGetPutObjReader(t, bytes.NewReader([]byte("abcd")), int64(len("abcd")), "", ""), ObjectOptions{})
if err != nil {
t.Fatalf("Erasure Object upload failed: <ERROR> %s", err)
@@ -270,7 +266,7 @@ func TestErasureDeleteObjectsErasureSet(t *testing.T) {
}
objectNames := toObjectNames(testCases)
_, delErrs := erasureSets.DeleteObjects(ctx, bucketName, objectNames, ObjectOptions{})
_, delErrs := obj.DeleteObjects(ctx, bucketName, objectNames, ObjectOptions{})
for i := range delErrs {
if delErrs[i] != nil {
@@ -279,7 +275,7 @@ func TestErasureDeleteObjectsErasureSet(t *testing.T) {
}
for _, test := range testCases {
_, statErr := erasureSets.GetObjectInfo(ctx, test.bucket, test.object, ObjectOptions{})
_, statErr := obj.GetObjectInfo(ctx, test.bucket, test.object, ObjectOptions{})
switch statErr.(type) {
case ObjectNotFound:
default:
@@ -305,7 +301,7 @@ func TestErasureDeleteObjectDiskNotFound(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -374,7 +370,7 @@ func TestErasureDeleteObjectDiskNotFoundErasure4(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -434,7 +430,7 @@ func TestErasureDeleteObjectDiskNotFoundErr(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -505,7 +501,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -614,7 +610,7 @@ func TestHeadObjectNoQuorum(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -692,7 +688,7 @@ func TestPutObjectNoQuorum(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -755,7 +751,7 @@ func TestPutObjectNoQuorumSmall(t *testing.T) {
xl := z.serverPools[0].sets[0]
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, "bucket", MakeBucketOptions{})
err = obj.MakeBucket(ctx, "bucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -824,7 +820,7 @@ func TestPutObjectSmallInlineData(t *testing.T) {
object := "object"
// Create "bucket"
err = obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err = obj.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -892,7 +888,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
ctx, cancel := context.WithCancel(GlobalContext)
defer cancel()
err := obj.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
err := obj.MakeBucket(ctx, bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("Failed to make a bucket %v", err)
}
@@ -1091,7 +1087,7 @@ func TestGetObjectInlineNotInline(t *testing.T) {
defer removeRoots(fsDirs)
// Create a testbucket
err = objLayer.MakeBucketWithLocation(ctx, "testbucket", MakeBucketOptions{})
err = objLayer.MakeBucket(ctx, "testbucket", MakeBucketOptions{})
if err != nil {
t.Fatal(err)
}
@@ -1161,7 +1157,7 @@ func TestGetObjectWithOutdatedDisks(t *testing.T) {
for i, testCase := range testCases {
// Step 1: create a bucket
err = z.MakeBucketWithLocation(ctx, testCase.bucket, MakeBucketOptions{VersioningEnabled: testCase.versioned})
err = z.MakeBucket(ctx, testCase.bucket, MakeBucketOptions{VersioningEnabled: testCase.versioned})
if err != nil {
t.Fatalf("Test %d: Failed to create a bucket: %v", i+1, err)
}
+102 -56
View File
@@ -686,6 +686,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 +733,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 +906,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 +996,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
@@ -1011,6 +1042,15 @@ func (z *erasureServerPools) doDecommissionInRoutine(ctx context.Context, idx in
failed := z.poolMeta.Pools[idx].Decommission.ItemsDecommissionFailed > 0
z.poolMetaMutex.Unlock()
if !failed {
logger.Info("Decommissioning almost complete - checking for left over objects")
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))
@@ -1193,6 +1233,33 @@ func (z *erasureServerPools) CompleteDecommission(ctx context.Context, idx int)
return nil
}
func (z *erasureServerPools) getBucketsToDecommission(ctx context.Context) ([]decomBucketInfo, error) {
buckets, err := z.ListBuckets(ctx, BucketOptions{})
if err != nil {
return nil, err
}
decomBuckets := make([]decomBucketInfo, len(buckets))
for i := range buckets {
decomBuckets[i] = decomBucketInfo{
Name: buckets[i].Name,
}
}
// 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, idx int) (err error) {
if idx < 0 {
return errInvalidArgument
@@ -1202,25 +1269,15 @@ func (z *erasureServerPools) StartDecommission(ctx context.Context, idx int) (er
return errInvalidArgument
}
buckets, err := z.ListBuckets(ctx, BucketOptions{})
decomBuckets, err := z.getBucketsToDecommission(ctx)
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 {
for _, bucket := range decomBuckets {
z.HealBucket(ctx, bucket.Name, madmin.HealOpts{})
}
decomBuckets := make([]decomBucketInfo, len(buckets))
for i := range buckets {
decomBuckets[i] = decomBucketInfo{
Name: buckets[i].Name,
}
}
// TODO: Support decommissioning transition tiers.
for _, bucket := range decomBuckets {
if lc, err := globalLifecycleSys.Get(bucket.Name); err == nil {
@@ -1240,24 +1297,13 @@ func (z *erasureServerPools) StartDecommission(ctx context.Context, idx int) (er
pathJoin(minioMetaBucket, bucketMetaPrefix),
} {
var bucketExists BucketExists
if err = z.MakeBucketWithLocation(ctx, metaBucket, MakeBucketOptions{}); err != nil {
if err = z.MakeBucket(ctx, metaBucket, MakeBucketOptions{}); err != nil {
if !errors.As(err, &bucketExists) {
return err
}
}
}
// 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 {
+10 -11
View File
@@ -57,15 +57,16 @@ type rebalanceStats struct {
Info rebalanceInfo `json:"info" msg:"inf"`
}
func (rs *rebalanceStats) update(bucket string, oi ObjectInfo) {
if oi.IsLatest {
func (rs *rebalanceStats) update(bucket string, fi FileInfo) {
if fi.IsLatest {
rs.NumObjects++
}
rs.NumVersions++
rs.Bytes += uint64(oi.Size)
onDiskSz := fi.Size * int64(fi.Erasure.DataBlocks+fi.Erasure.ParityBlocks) / int64(fi.Erasure.DataBlocks)
rs.Bytes += uint64(onDiskSz)
rs.Bucket = bucket
rs.Object = oi.Name
rs.Object = fi.Name
}
type rstats []*rebalanceStats
@@ -168,7 +169,7 @@ func (z *erasureServerPools) initRebalanceMeta(ctx context.Context, buckets []st
return r.ID, nil
}
func (z *erasureServerPools) updatePoolStats(poolIdx int, bucket string, oi ObjectInfo) {
func (z *erasureServerPools) updatePoolStats(poolIdx int, bucket string, fi FileInfo) {
z.rebalMu.Lock()
defer z.rebalMu.Unlock()
@@ -177,7 +178,7 @@ func (z *erasureServerPools) updatePoolStats(poolIdx int, bucket string, oi Obje
return
}
r.PoolStats[poolIdx].update(bucket, oi)
r.PoolStats[poolIdx].update(bucket, fi)
}
const (
@@ -534,14 +535,13 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
}
if !failure {
z.updatePoolStats(poolIdx, bucket, version.ToObjectInfo(bucket, version.Name, vc.PrefixEnabled(version.Name)))
z.updatePoolStats(poolIdx, bucket, version)
rebalanced++
}
continue
}
var failure bool
var oi ObjectInfo
for try := 0; try < 3; try++ {
// GetObjectReader.Close is called by rebalanceObject
gr, err := set.GetObjectNInfo(ctx,
@@ -574,14 +574,13 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
stopFn(nil)
failure = false
oi = gr.ObjInfo
break
}
if failure {
break // break out on first error
}
z.updatePoolStats(poolIdx, bucket, oi)
z.updatePoolStats(poolIdx, bucket, version)
rebalanced++
}
@@ -656,7 +655,7 @@ func (z *erasureServerPools) saveRebalanceStats(ctx context.Context, poolIdx int
logger.LogIf(ctx, fmt.Errorf("failed to acquire write lock on %s/%s: %w", minioMetaBucket, rebalMetaName, err))
return err
}
defer lock.Unlock(lkCtx.Cancel)
defer lock.Unlock(lkCtx)
ctx = lkCtx.Context()
noLockOpts := ObjectOptions{NoLock: true}
+112 -139
View File
@@ -32,7 +32,9 @@ import (
"time"
"github.com/dustin/go-humanize"
"github.com/google/uuid"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7/pkg/s3utils"
"github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/tags"
"github.com/minio/minio/internal/bucket/lifecycle"
@@ -49,6 +51,9 @@ type erasureServerPools struct {
rebalMu sync.RWMutex
rebalMeta *rebalanceMeta
deploymentID [16]byte
distributionAlgo string
serverPools []*erasureSets
// Shut down async operations
@@ -56,6 +61,8 @@ type erasureServerPools struct {
// Active decommission canceler
decommissionCancelers []context.CancelFunc
s3Peer *S3PeerSys
}
func (z *erasureServerPools) SinglePool() bool {
@@ -74,6 +81,7 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
storageDisks = make([][]StorageAPI, len(endpointServerPools))
z = &erasureServerPools{
serverPools: make([]*erasureSets, len(endpointServerPools)),
s3Peer: NewS3PeerSys(endpointServerPools),
}
)
@@ -86,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,
@@ -123,6 +134,14 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
if err != nil {
return nil, err
}
if deploymentID != "" && bytes.Equal(z.deploymentID[:], []byte{}) {
z.deploymentID = uuid.MustParse(deploymentID)
}
if distributionAlgo != "" && z.distributionAlgo == "" {
z.distributionAlgo = distributionAlgo
}
}
z.decommissionCancelers = make([]context.CancelFunc, len(z.serverPools))
@@ -153,7 +172,11 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
}
func (z *erasureServerPools) NewNSLock(bucket string, objects ...string) RWLocker {
return z.serverPools[0].NewNSLock(bucket, objects...)
poolID := hashKey(z.distributionAlgo, "", len(z.serverPools), z.deploymentID)
if len(objects) >= 1 {
poolID = hashKey(z.distributionAlgo, objects[0], len(z.serverPools), z.deploymentID)
}
return z.serverPools[poolID].NewNSLock(bucket, objects...)
}
// GetDisksID will return disks by their ID.
@@ -687,45 +710,40 @@ func (z *erasureServerPools) NSScanner(ctx context.Context, bf *bloomFilter, upd
return firstErr
}
// MakeBucketWithLocation - creates a new bucket across all serverPools simultaneously
// MakeBucket - creates a new bucket across all serverPools simultaneously
// even if one of the sets fail to create buckets, we proceed all the successful
// operations.
func (z *erasureServerPools) MakeBucketWithLocation(ctx context.Context, bucket string, opts MakeBucketOptions) error {
g := errgroup.WithNErrs(len(z.serverPools))
func (z *erasureServerPools) MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error {
defer NSUpdated(bucket, slashSeparator)
// Lock the bucket name before creating.
lk := z.NewNSLock(minioMetaTmpBucket, bucket+".lck")
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
// Create buckets in parallel across all sets.
for index := range z.serverPools {
index := index
g.Go(func() error {
if z.IsSuspended(index) {
return nil
}
return z.serverPools[index].MakeBucketWithLocation(ctx, bucket, opts)
}, index)
}
errs := g.Wait()
// Return the first encountered error
for _, err := range errs {
if err != nil {
if _, ok := err.(BucketExists); !ok {
// Delete created buckets, ignoring errors.
z.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
Force: false,
NoRecreate: true,
})
}
return err
// Verify if bucket is valid.
if !isMinioMetaBucketName(bucket) {
if err := s3utils.CheckValidBucketNameStrict(bucket); err != nil {
return BucketNameInvalid{Bucket: bucket}
}
if !opts.NoLock {
// Lock the bucket name before creating.
lk := z.NewNSLock(minioMetaTmpBucket, bucket+".lck")
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx)
}
}
if err := z.s3Peer.MakeBucket(ctx, bucket, opts); err != nil {
if _, ok := err.(BucketExists); !ok {
// Delete created buckets, ignoring errors.
z.DeleteBucket(context.Background(), bucket, DeleteBucketOptions{
NoLock: true,
NoRecreate: true,
})
}
return err
}
// If it doesn't exist we get a new, so ignore errors
@@ -779,14 +797,14 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.Unlock(lkctx.Cancel) }
nsUnlocker = func() { lock.Unlock(lkctx) }
case readLock:
lkctx, err := lock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return nil, err
}
ctx = lkctx.Context()
nsUnlocker = func() { lock.RUnlock(lkctx.Cancel) }
nsUnlocker = func() { lock.RUnlock(lkctx) }
}
unlockOnDefer = true
}
@@ -907,7 +925,7 @@ func (z *erasureServerPools) GetObjectInfo(ctx context.Context, bucket, object s
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer lk.RUnlock(lkctx.Cancel)
defer lk.RUnlock(lkctx)
}
objInfo, _, err = z.getLatestObjectInfoWithIdx(ctx, bucket, object, opts)
@@ -936,7 +954,7 @@ func (z *erasureServerPools) PutObject(ctx context.Context, bucket string, objec
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer ns.Unlock(lkctx.Cancel)
defer ns.Unlock(lkctx)
opts.NoLock = true
}
@@ -977,7 +995,7 @@ func (z *erasureServerPools) DeleteObject(ctx context.Context, bucket string, ob
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx.Cancel)
defer lk.Unlock(lkctx)
gopts := opts
gopts.NoLock = true
@@ -1022,7 +1040,7 @@ func (z *erasureServerPools) DeleteObjects(ctx context.Context, bucket string, o
return dobjects, derrs
}
ctx = lkctx.Context()
defer multiDeleteLock.Unlock(lkctx.Cancel)
defer multiDeleteLock.Unlock(lkctx)
// Fetch location of up to 10 objects concurrently.
poolObjIdxMap := map[int][]ObjectToDelete{}
@@ -1122,7 +1140,7 @@ func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObjec
return ObjectInfo{}, err
}
ctx = lkctx.Context()
defer ns.Unlock(lkctx.Cancel)
defer ns.Unlock(lkctx)
dstOpts.NoLock = true
}
@@ -1582,38 +1600,18 @@ func (z *erasureServerPools) CompleteMultipartUpload(ctx context.Context, bucket
// GetBucketInfo - returns bucket info from one of the erasure coded serverPools.
func (z *erasureServerPools) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bucketInfo BucketInfo, err error) {
if z.SinglePool() {
bucketInfo, err = z.serverPools[0].GetBucketInfo(ctx, bucket, opts)
if err != nil {
return bucketInfo, err
}
meta, err := globalBucketMetadataSys.Get(bucket)
if err == nil {
bucketInfo.Created = meta.Created
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
bucketInfo.ObjectLocking = meta.LockEnabled
}
return bucketInfo, nil
bucketInfo, err = z.s3Peer.GetBucketInfo(ctx, bucket, opts)
if err != nil {
return bucketInfo, toObjectErr(err, bucket)
}
for _, pool := range z.serverPools {
bucketInfo, err = pool.GetBucketInfo(ctx, bucket, opts)
if err != nil {
if isErrBucketNotFound(err) {
continue
}
return bucketInfo, err
}
meta, err := globalBucketMetadataSys.Get(bucket)
if err == nil {
bucketInfo.Created = meta.Created
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
bucketInfo.ObjectLocking = meta.LockEnabled
}
return bucketInfo, nil
}
return bucketInfo, BucketNotFound{
Bucket: bucket,
meta, err := globalBucketMetadataSys.Get(bucket)
if err == nil {
bucketInfo.Created = meta.Created
bucketInfo.Versioning = meta.LockEnabled || globalBucketVersioningSys.Enabled(bucket)
bucketInfo.ObjectLocking = meta.LockEnabled
}
return bucketInfo, nil
}
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
@@ -1644,41 +1642,48 @@ func (z *erasureServerPools) IsTaggingSupported() bool {
// even if one of the serverPools fail to delete buckets, we proceed to
// undo a successful operation.
func (z *erasureServerPools) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
g := errgroup.WithNErrs(len(z.serverPools))
// Delete buckets in parallel across all serverPools.
for index := range z.serverPools {
index := index
g.Go(func() error {
if z.IsSuspended(index) {
return nil
}
return z.serverPools[index].DeleteBucket(ctx, bucket, opts)
}, index)
if isMinioMetaBucketName(bucket) {
return BucketNameInvalid{Bucket: bucket}
}
errs := g.Wait()
// Verify if bucket is valid.
if err := s3utils.CheckValidBucketName(bucket); err != nil {
return BucketNameInvalid{Bucket: bucket}
}
// For any write quorum failure, we undo all the delete
// buckets operation by creating all the buckets again.
for _, err := range errs {
defer NSUpdated(bucket, slashSeparator)
if !opts.NoLock {
// Lock the bucket name before creating.
lk := z.NewNSLock(minioMetaTmpBucket, bucket+".lck")
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
if !z.SinglePool() && !opts.NoRecreate {
undoDeleteBucketServerPools(context.Background(), bucket, z.serverPools, errs)
}
return err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx)
}
err := z.s3Peer.DeleteBucket(ctx, bucket, opts)
if err == nil || errors.Is(err, errVolumeNotFound) {
// 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))
}
}
// Purge the entire bucket metadata entirely.
z.deleteAll(context.Background(), minioMetaBucket, pathJoin(bucketMetaPrefix, bucket))
// 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))
if err != nil && !errors.Is(err, errVolumeNotFound) {
if !opts.NoRecreate {
z.s3Peer.MakeBucket(ctx, bucket, MakeBucketOptions{})
}
}
// Success.
return nil
if err == nil {
// Purge the entire bucket metadata entirely.
z.deleteAll(context.Background(), minioMetaBucket, pathJoin(bucketMetaPrefix, bucket))
}
return toObjectErr(err, bucket)
}
// deleteAll will rename bucket+prefix unconditionally across all disks to
@@ -1715,43 +1720,11 @@ func (z *erasureServerPools) purgeDelete(ctx context.Context, bucket, prefix str
}
}
// This function is used to undo a successful DeleteBucket operation.
func undoDeleteBucketServerPools(ctx context.Context, bucket string, serverPools []*erasureSets, errs []error) {
g := errgroup.WithNErrs(len(serverPools))
// Undo previous delete bucket on all underlying serverPools.
for index := range serverPools {
index := index
g.Go(func() error {
if errs[index] == nil {
return serverPools[index].MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
}
return nil
}, index)
}
g.Wait()
}
// 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.
func (z *erasureServerPools) ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
if z.SinglePool() {
buckets, err = z.serverPools[0].ListBuckets(ctx, opts)
} else {
for idx, pool := range z.serverPools {
if z.IsSuspended(idx) {
continue
}
buckets, err = pool.ListBuckets(ctx, opts)
if err != nil {
logger.LogIf(ctx, err)
continue
}
break
}
}
buckets, err = z.s3Peer.ListBuckets(ctx, opts)
if err != nil {
return nil, err
}
@@ -1772,7 +1745,7 @@ func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmi
return madmin.HealResultItem{}, err
}
ctx = lkctx.Context()
defer formatLock.Unlock(lkctx.Cancel)
defer formatLock.Unlock(lkctx)
r := madmin.HealResultItem{
Type: madmin.HealItemMetadata,
@@ -1954,7 +1927,7 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
// HealObjectFn closure function heals the object.
type HealObjectFn func(bucket, object, versionID string) error
func listAndHeal(ctx context.Context, bucket, prefix string, set *erasureObjects, healEntry func(metaCacheEntry) error) error {
func listAndHeal(ctx context.Context, bucket, prefix string, set *erasureObjects, healEntry func(string, metaCacheEntry) error) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
@@ -1987,7 +1960,7 @@ func listAndHeal(ctx context.Context, bucket, prefix string, set *erasureObjects
minDisks: 1,
reportNotFound: false,
agreed: func(entry metaCacheEntry) {
if err := healEntry(entry); err != nil {
if err := healEntry(bucket, entry); err != nil {
logger.LogIf(ctx, err)
cancel()
}
@@ -2000,7 +1973,7 @@ func listAndHeal(ctx context.Context, bucket, prefix string, set *erasureObjects
entry, _ = entries.firstFound()
}
if err := healEntry(*entry); err != nil {
if err := healEntry(bucket, *entry); err != nil {
logger.LogIf(ctx, err)
cancel()
return
@@ -2017,7 +1990,7 @@ func listAndHeal(ctx context.Context, bucket, prefix string, set *erasureObjects
}
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObjectFn HealObjectFn) error {
healEntry := func(entry metaCacheEntry) error {
healEntry := func(bucket string, entry metaCacheEntry) error {
if entry.isDir() {
return nil
}
+11 -145
View File
@@ -26,7 +26,6 @@ import (
"math/rand"
"net/http"
"reflect"
"sort"
"strings"
"sync"
"time"
@@ -355,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),
@@ -677,32 +672,6 @@ func (s *erasureSets) Shutdown(ctx context.Context) error {
return nil
}
// MakeBucketLocation - creates a new bucket across all sets simultaneously,
// then return the first encountered error
func (s *erasureSets) MakeBucketWithLocation(ctx context.Context, bucket string, opts MakeBucketOptions) error {
g := errgroup.WithNErrs(len(s.sets))
// Create buckets in parallel across all sets.
for index := range s.sets {
index := index
g.Go(func() error {
return s.sets[index].MakeBucketWithLocation(ctx, bucket, opts)
}, index)
}
errs := g.Wait()
// Return the first encountered error
for _, err := range errs {
if err != nil {
return err
}
}
// Success.
return nil
}
// hashes the key returning an integer based on the input algorithm.
// This function currently supports
// - CRCMOD
@@ -749,11 +718,6 @@ func (s *erasureSets) getHashedSet(input string) (set *erasureObjects) {
return s.sets[s.getHashedSetIndex(input)]
}
// GetBucketInfo - returns bucket info from one of the erasure coded set.
func (s *erasureSets) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bucketInfo BucketInfo, err error) {
return s.getHashedSet("").GetBucketInfo(ctx, bucket, opts)
}
// IsNotificationSupported returns whether bucket notification is applicable for this layer.
func (s *erasureSets) IsNotificationSupported() bool {
return s.getHashedSet("").IsNotificationSupported()
@@ -778,102 +742,6 @@ func (s *erasureSets) IsTaggingSupported() bool {
return true
}
// DeleteBucket - deletes a bucket on all sets simultaneously,
// even if one of the sets fail to delete buckets, we proceed to
// undo a successful operation.
func (s *erasureSets) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
g := errgroup.WithNErrs(len(s.sets))
// Delete buckets in parallel across all sets.
for index := range s.sets {
index := index
g.Go(func() error {
return s.sets[index].DeleteBucket(ctx, bucket, opts)
}, index)
}
errs := g.Wait()
// For any failure, we attempt undo all the delete buckets operation
// by creating buckets again on all sets which were successfully deleted.
for _, err := range errs {
if err != nil && !opts.NoRecreate {
undoDeleteBucketSets(ctx, bucket, s.sets, errs)
return err
}
}
// Success.
return nil
}
// This function is used to undo a successful DeleteBucket operation.
func undoDeleteBucketSets(ctx context.Context, bucket string, sets []*erasureObjects, errs []error) {
g := errgroup.WithNErrs(len(sets))
// Undo previous delete bucket on all underlying sets.
for index := range sets {
index := index
g.Go(func() error {
if errs[index] == nil {
return sets[index].MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
}
return nil
}, index)
}
g.Wait()
}
// List all buckets from one of the set, we are not doing merge
// sort here just for simplification. As per design it is assumed
// that all buckets are present on all sets.
func (s *erasureSets) ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
var listBuckets []BucketInfo
healBuckets := map[string]VolInfo{}
for _, set := range s.sets {
// lists all unique buckets across drives.
if err := listAllBuckets(ctx, set.getDisks(), healBuckets, s.defaultParityCount); err != nil {
return nil, err
}
}
// include deleted buckets in listBuckets output
deletedBuckets := map[string]VolInfo{}
if opts.Deleted {
for _, set := range s.sets {
// lists all unique buckets across drives.
if err := listDeletedBuckets(ctx, set.getDisks(), deletedBuckets, s.defaultParityCount); err != nil {
return nil, err
}
}
}
for _, v := range healBuckets {
bi := BucketInfo{
Name: v.Name,
Created: v.Created,
}
if vi, ok := deletedBuckets[v.Name]; ok {
bi.Deleted = vi.Created
}
listBuckets = append(listBuckets, bi)
}
for _, v := range deletedBuckets {
if _, ok := healBuckets[v.Name]; !ok {
listBuckets = append(listBuckets, BucketInfo{
Name: v.Name,
Deleted: v.Created,
})
}
}
sort.Slice(listBuckets, func(i, j int) bool {
return listBuckets[i].Name < listBuckets[j].Name
})
return listBuckets, nil
}
// 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))
@@ -887,25 +755,21 @@ func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBucke
}
volsInfo, err := storageDisks[index].ListDir(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix), -1)
if err != nil {
if err == errFileNotFound {
if errors.Is(err, errFileNotFound) {
return nil
}
return err
}
for _, volName := range volsInfo {
mu.Lock()
if _, ok := delBuckets[volName]; !ok {
vi, err := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, volName))
if err != nil {
return err
vi, err := storageDisks[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, volName))
if err == nil {
vi.Name = strings.TrimSuffix(volName, SlashSeparator)
mu.Lock()
if _, ok := delBuckets[volName]; !ok {
delBuckets[volName] = vi
}
bkt := strings.TrimPrefix(vi.Name, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix))
bkt = strings.TrimPrefix(bkt, slashSeparator)
bkt = strings.TrimSuffix(bkt, slashSeparator)
vi.Name = bkt
delBuckets[bkt] = vi
mu.Unlock()
}
mu.Unlock()
}
return nil
}, index)
@@ -1281,6 +1145,8 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
return res, errNoHealRequired
}
formatOpID := mustGetUUID()
// Initialize a new set of set formats which will be written to disk.
newFormatSets := newHealFormatSets(refFormat, s.setCount, s.setDriveCount, formats, sErrs)
@@ -1302,7 +1168,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
if storageDisks[index] == nil || format == nil {
continue
}
if err := saveFormatErasure(storageDisks[index], format, true); err != nil {
if err := saveFormatErasure(storageDisks[index], format, formatOpID); err != nil {
logger.LogIf(ctx, fmt.Errorf("Drive %s failed to write updated 'format.json': %v", storageDisks[index], err))
tmpNewFormats[index] = nil // this disk failed to write new format
}
+6 -1
View File
@@ -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")
}
}
-4
View File
@@ -89,10 +89,6 @@ func (er erasureObjects) defaultWQuorum() int {
return dataCount
}
func (er erasureObjects) defaultRQuorum() int {
return er.setDriveCount - er.defaultParityCount
}
// byDiskTotal is a collection satisfying sort.Interface.
type byDiskTotal []madmin.Disk
+15 -13
View File
@@ -347,7 +347,7 @@ func loadFormatErasureAll(storageDisks []StorageAPI, heal bool) ([]*formatErasur
return formats, g.Wait()
}
func saveFormatErasure(disk StorageAPI, format *formatErasureV3, heal bool) error {
func saveFormatErasure(disk StorageAPI, format *formatErasureV3, healID string) error {
if disk == nil || format == nil {
return errDiskNotFound
}
@@ -383,9 +383,9 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3, heal bool) erro
}
disk.SetDiskID(diskID)
if heal {
if healID != "" {
ctx := context.Background()
ht := newHealingTracker(disk)
ht := newHealingTracker(disk, healID)
return ht.save(ctx)
}
return nil
@@ -541,7 +541,7 @@ func formatErasureFixLocalDeploymentID(endpoints Endpoints, storageDisks []Stora
}
format.ID = refFormat.ID
// Heal the drive if we fixed its deployment ID.
if err := saveFormatErasure(storageDisks[index], format, true); err != nil {
if err := saveFormatErasure(storageDisks[index], format, mustGetUUID()); err != nil {
logger.LogIf(GlobalContext, err)
return fmt.Errorf("Unable to save format.json, %w", err)
}
@@ -642,7 +642,7 @@ func saveFormatErasureAll(ctx context.Context, storageDisks []StorageAPI, format
if formats[index] == nil {
return errDiskNotFound
}
return saveFormatErasure(storageDisks[index], formats[index], false)
return saveFormatErasure(storageDisks[index], formats[index], "")
}, index)
}
@@ -722,7 +722,7 @@ func fixFormatErasureV3(storageDisks []StorageAPI, endpoints Endpoints, formats
if formats[i].Erasure.This == "" {
formats[i].Erasure.This = formats[i].Erasure.Sets[0][i]
// Heal the drive if drive has .This empty.
if err := saveFormatErasure(storageDisks[i], formats[i], true); err != nil {
if err := saveFormatErasure(storageDisks[i], formats[i], mustGetUUID()); err != nil {
return err
}
}
@@ -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.
+11 -1
View File
@@ -340,6 +340,8 @@ func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[str
// Assumes cache is locked by caller.
func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iamCache) error {
bootstrapTrace("loading all IAM items")
listedConfigItems, err := iamOS.listAllIAMConfigItems(ctx)
if err != nil {
return err
@@ -347,6 +349,8 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
// Loads things in the same order as `LoadIAMCache()`
bootstrapTrace("loading policy documents")
policiesList := listedConfigItems[policiesListKey]
for _, item := range policiesList {
policyName := path.Dir(item)
@@ -357,7 +361,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
setDefaultCannedPolicies(cache.iamPolicyDocsMap)
if iamOS.usersSysType == MinIOUsersSysType {
bootstrapTrace("loading regular IAM users")
regUsersList := listedConfigItems[usersListKey]
for _, item := range regUsersList {
userName := path.Dir(item)
@@ -366,6 +370,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
}
}
bootstrapTrace("loading regular IAM groups")
groupsList := listedConfigItems[groupsListKey]
for _, item := range groupsList {
group := path.Dir(item)
@@ -375,6 +380,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
}
}
bootstrapTrace("loading user policy mapping")
userPolicyMappingsList := listedConfigItems[policyDBUsersListKey]
for _, item := range userPolicyMappingsList {
userName := strings.TrimSuffix(item, ".json")
@@ -383,6 +389,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
}
}
bootstrapTrace("loading group policy mapping")
groupPolicyMappingsList := listedConfigItems[policyDBGroupsListKey]
for _, item := range groupPolicyMappingsList {
groupName := strings.TrimSuffix(item, ".json")
@@ -391,6 +398,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
}
}
bootstrapTrace("loading service accounts")
svcAccList := listedConfigItems[svcAccListKey]
for _, item := range svcAccList {
userName := path.Dir(item)
@@ -399,6 +407,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
}
}
bootstrapTrace("loading STS users")
stsUsersList := listedConfigItems[stsListKey]
for _, item := range stsUsersList {
userName := path.Dir(item)
@@ -407,6 +416,7 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
}
}
bootstrapTrace("loading STS policy mapping")
stsPolicyMappingsList := listedConfigItems[policyDBSTSUsersListKey]
for _, item := range stsPolicyMappingsList {
stsName := strings.TrimSuffix(item, ".json")
+172
View File
@@ -104,6 +104,7 @@ func getUserIdentityPath(user string, userType IAMUserType) string {
}
func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error {
bootstrapTrace("Load IAM format file")
var iamFmt iamFormat
path := getIAMFormatFilePath()
if err := store.loadIAMConfig(ctx, &iamFmt, path); err != nil {
@@ -121,6 +122,7 @@ func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error {
return nil
}
bootstrapTrace("Write IAM format file")
// Save iam format to version 1.
if err := store.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
logger.LogIf(ctx, err)
@@ -442,6 +444,8 @@ func setDefaultCannedPolicies(policies map[string]PolicyDoc) {
// LoadIAMCache reads all IAM items and populates a new iamCache object and
// replaces the in-memory cache object.
func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
bootstrapTrace("loading IAM data")
newCache := newIamCache()
loadedAt := time.Now()
@@ -453,6 +457,7 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
}
} else {
bootstrapTrace("loading policy documents")
if err := store.loadPolicyDocs(ctx, newCache.iamPolicyDocsMap); err != nil {
return err
}
@@ -461,34 +466,41 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context) error {
setDefaultCannedPolicies(newCache.iamPolicyDocsMap)
if store.getUsersSysType() == MinIOUsersSysType {
bootstrapTrace("loading regular users")
if err := store.loadUsers(ctx, regUser, newCache.iamUsersMap); err != nil {
return err
}
bootstrapTrace("loading regular groups")
if err := store.loadGroups(ctx, newCache.iamGroupsMap); err != nil {
return err
}
}
bootstrapTrace("loading user policy mapping")
// load polices mapped to users
if err := store.loadMappedPolicies(ctx, regUser, false, newCache.iamUserPolicyMap); err != nil {
return err
}
bootstrapTrace("loading group policy mapping")
// load policies mapped to groups
if err := store.loadMappedPolicies(ctx, regUser, true, newCache.iamGroupPolicyMap); err != nil {
return err
}
bootstrapTrace("loading service accounts")
// load service accounts
if err := store.loadUsers(ctx, svcUser, newCache.iamUsersMap); err != nil {
return err
}
bootstrapTrace("loading STS users")
// load STS temp users
if err := store.loadUsers(ctx, stsUser, newCache.iamUsersMap); err != nil {
return err
}
bootstrapTrace("loading STS policy mapping")
// load STS policy mappings
if err := store.loadMappedPolicies(ctx, stsUser, false, newCache.iamUserPolicyMap); err != nil {
return err
@@ -1552,6 +1564,23 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
}, nil
}
// GetUserPolicies - returns the policies attached to a user.
func (store *IAMStoreSys) GetUserPolicies(name string) ([]string, error) {
if name == "" {
return nil, errInvalidArgument
}
cache := store.rlock()
defer store.runlock()
if cache.iamUserPolicyMap[name].Policies == "" {
return []string{}, nil
}
policies := cache.iamUserPolicyMap[name].toSlice()
return policies, nil
}
// PolicyMappingNotificationHandler - handles updating a policy mapping from storage.
func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context, userOrGroup string, isGroup bool, userType IAMUserType) error {
if userOrGroup == "" {
@@ -1870,6 +1899,145 @@ func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
return res
}
// Assumes store is locked by caller. If users is empty, returns all user mappings.
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string) []madmin.UserPolicyEntities {
var r []madmin.UserPolicyEntities
usersSet := set.CreateStringSet(users...)
for user, mappedPolicy := range cache.iamUserPolicyMap {
if !usersSet.IsEmpty() && !usersSet.Contains(user) {
continue
}
ps := mappedPolicy.toSlice()
sort.Strings(ps)
r = append(r, madmin.UserPolicyEntities{
User: user,
Policies: ps,
})
}
sort.Slice(r, func(i, j int) bool {
return r[i].User < r[j].User
})
return r
}
// Assumes store is locked by caller. If groups is empty, returns all group mappings.
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []string) []madmin.GroupPolicyEntities {
var r []madmin.GroupPolicyEntities
groupsSet := set.CreateStringSet(groups...)
for group, mappedPolicy := range cache.iamGroupPolicyMap {
if !groupsSet.IsEmpty() && !groupsSet.Contains(group) {
continue
}
ps := mappedPolicy.toSlice()
sort.Strings(ps)
r = append(r, madmin.GroupPolicyEntities{
Group: group,
Policies: ps,
})
}
sort.Slice(r, func(i, j int) bool {
return r[i].Group < r[j].Group
})
return r
}
// Assumes store is locked by caller. If policies is empty, returns all policy mappings.
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string) []madmin.PolicyEntities {
queryPolSet := set.CreateStringSet(policies...)
policyToUsersMap := make(map[string]set.StringSet)
for user, mappedPolicy := range cache.iamUserPolicyMap {
commonPolicySet := mappedPolicy.policySet()
if !queryPolSet.IsEmpty() {
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
}
for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToUsersMap[policy]
if !ok {
policyToUsersMap[policy] = set.CreateStringSet(user)
} else {
s.Add(user)
policyToUsersMap[policy] = s
}
}
}
policyToGroupsMap := make(map[string]set.StringSet)
for group, mappedPolicy := range cache.iamGroupPolicyMap {
commonPolicySet := mappedPolicy.policySet()
if !queryPolSet.IsEmpty() {
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
}
for _, policy := range commonPolicySet.ToSlice() {
s, ok := policyToGroupsMap[policy]
if !ok {
policyToGroupsMap[policy] = set.CreateStringSet(group)
} else {
s.Add(group)
policyToGroupsMap[policy] = s
}
}
}
m := make(map[string]madmin.PolicyEntities, len(policyToGroupsMap))
for policy, groups := range policyToGroupsMap {
s := groups.ToSlice()
sort.Strings(s)
m[policy] = madmin.PolicyEntities{
Policy: policy,
Groups: s,
}
}
for policy, users := range policyToUsersMap {
s := users.ToSlice()
sort.Strings(s)
// Update existing value in map
pe := m[policy]
pe.Policy = policy
pe.Users = s
m[policy] = pe
}
policyEntities := make([]madmin.PolicyEntities, 0, len(m))
for _, v := range m {
policyEntities = append(policyEntities, v)
}
sort.Slice(policyEntities, func(i, j int) bool {
return policyEntities[i].Policy < policyEntities[j].Policy
})
return policyEntities
}
// ListPolicyMappings - return builtin users/groups mapped to policies.
func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery) madmin.PolicyEntitiesResult {
cache := store.rlock()
defer store.runlock()
var result madmin.PolicyEntitiesResult
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policy) == 0
if len(q.Users) > 0 {
result.UserMappings = store.listUserPolicyMappings(cache, q.Users)
}
if len(q.Groups) > 0 {
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups)
}
if len(q.Policy) > 0 || isAllPoliciesQuery {
result.PolicyMappings = store.listPolicyMappings(cache, q.Policy)
}
return result
}
// SetUserStatus - sets current user status.
func (store *IAMStoreSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) {
if accessKey != "" && status != madmin.AccountEnabled && status != madmin.AccountDisabled {
@@ -1965,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 "":
+88 -31
View File
@@ -208,6 +208,7 @@ func (sys *IAMSys) Load(ctx context.Context) error {
// Init - initializes config system by reading entries from config/iam
func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etcd.Client, iamRefreshInterval time.Duration) {
bootstrapTrace("IAM initialization started")
globalServerConfigMu.RLock()
s := globalServerConfig
globalServerConfigMu.RUnlock()
@@ -233,8 +234,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
setGlobalAuthNPlugin(idplugin.New(authNPluginCfg))
authZPluginCfg, err := polplugin.LookupConfig(s[config.PolicyPluginSubSys][config.Default],
NewHTTPTransport(), xhttp.DrainBody)
authZPluginCfg, err := polplugin.LookupConfig(s, GetDefaultConnSettings(), xhttp.DrainBody)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to initialize AuthZPlugin: %w", err))
}
@@ -316,6 +316,8 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
break
}
bootstrapTrace("finishing IAM loading")
refreshInterval := sys.iamRefreshInterval
// Set up polling for expired accounts and credentials purging.
@@ -857,6 +859,31 @@ func (sys *IAMSys) GetUserInfo(ctx context.Context, name string) (u madmin.UserI
return sys.store.GetUserInfo(name)
}
// QueryPolicyEntities - queries policy associations for builtin users/groups/policies.
func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntitiesQuery) (*madmin.PolicyEntitiesResult, error) {
if !sys.Initialized() {
return nil, errServerNotInitialized
}
select {
case <-sys.configLoaded:
pe := sys.store.ListPolicyMappings(q)
pe.Timestamp = UTCNow()
return &pe, nil
case <-ctx.Done():
return nil, ctx.Err()
}
}
// GetUserPolicies - get policies attached to a user.
func (sys *IAMSys) GetUserPolicies(name string) (p []string, err error) {
if !sys.Initialized() {
return p, errServerNotInitialized
}
return sys.store.GetUserPolicies(name)
}
// SetUserStatus - sets current user status, supports disabled or enabled.
func (sys *IAMSys) SetUserStatus(ctx context.Context, accessKey string, status madmin.AccountStatus) (updatedAt time.Time, err error) {
if !sys.Initialized() {
@@ -892,6 +919,7 @@ type newServiceAccountOpts struct {
sessionPolicy *iampolicy.Policy
accessKey string
secretKey string
comment string
claims map[string]interface{}
}
@@ -962,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 {
@@ -976,6 +1005,7 @@ type updateServiceAccountOpts struct {
sessionPolicy *iampolicy.Policy
secretKey string
status string
comment string
}
// UpdateServiceAccount - edit a service account
@@ -1034,7 +1064,7 @@ func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (aut
}
func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) {
sa, embeddedPolicy, err := sys.getAccountWithEmbeddedPolicy(ctx, accessKey)
sa, jwtClaims, err := sys.getAccountWithClaims(ctx, accessKey)
if err != nil {
if err == errNoSuchAccount {
return UserIdentity{}, nil, errNoSuchServiceAccount
@@ -1045,11 +1075,38 @@ func (sys *IAMSys) getServiceAccount(ctx context.Context, accessKey string) (Use
return UserIdentity{}, nil, errNoSuchServiceAccount
}
var embeddedPolicy *iampolicy.Policy
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
sp, spok := jwtClaims.Lookup(iampolicy.SessionPolicyName)
if ptok && spok && pt == embeddedPolicyType {
policyBytes, err := base64.StdEncoding.DecodeString(sp)
if err != nil {
return UserIdentity{}, nil, err
}
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes))
if err != nil {
return UserIdentity{}, nil, err
}
}
return sa, embeddedPolicy, nil
}
// GetTemporaryAccount - wrapper method to get information about a temporary account
func (sys *IAMSys) GetTemporaryAccount(ctx context.Context, accessKey string) (auth.Credentials, *iampolicy.Policy, error) {
tmpAcc, embeddedPolicy, err := sys.getTempAccount(ctx, accessKey)
if err != nil {
return auth.Credentials{}, nil, err
}
// Hide secret & session keys
tmpAcc.Credentials.SecretKey = ""
tmpAcc.Credentials.SessionToken = ""
return tmpAcc.Credentials, embeddedPolicy, nil
}
func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserIdentity, *iampolicy.Policy, error) {
tmpAcc, embeddedPolicy, err := sys.getAccountWithEmbeddedPolicy(ctx, accessKey)
tmpAcc, claims, err := sys.getAccountWithClaims(ctx, accessKey)
if err != nil {
if err == errNoSuchAccount {
return UserIdentity{}, nil, errNoSuchTempAccount
@@ -1060,43 +1117,43 @@ func (sys *IAMSys) getTempAccount(ctx context.Context, accessKey string) (UserId
return UserIdentity{}, nil, errNoSuchTempAccount
}
return tmpAcc, embeddedPolicy, nil
}
// getAccountWithEmbeddedPolicy - gets information about an account with its embedded policy if found
func (sys *IAMSys) getAccountWithEmbeddedPolicy(ctx context.Context, accessKey string) (u UserIdentity, p *iampolicy.Policy, err error) {
if !sys.Initialized() {
return u, nil, errServerNotInitialized
}
sa, ok := sys.store.GetUser(accessKey)
if !ok {
return u, nil, errNoSuchAccount
}
var embeddedPolicy *iampolicy.Policy
jwtClaims, err := auth.ExtractClaims(sa.Credentials.SessionToken, sa.Credentials.SecretKey)
if err != nil {
jwtClaims, err = auth.ExtractClaims(sa.Credentials.SessionToken, globalActiveCred.SecretKey)
if err != nil {
return u, nil, err
}
}
pt, ptok := jwtClaims.Lookup(iamPolicyClaimNameSA())
sp, spok := jwtClaims.Lookup(iampolicy.SessionPolicyName)
if ptok && spok && pt == embeddedPolicyType {
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
if spok {
policyBytes, err := base64.StdEncoding.DecodeString(sp)
if err != nil {
return u, nil, err
return UserIdentity{}, nil, err
}
embeddedPolicy, err = iampolicy.ParseConfig(bytes.NewReader(policyBytes))
if err != nil {
return u, nil, err
return UserIdentity{}, nil, err
}
}
return sa, embeddedPolicy, nil
return tmpAcc, embeddedPolicy, nil
}
// getAccountWithClaims - gets information about an account with claims
func (sys *IAMSys) getAccountWithClaims(ctx context.Context, accessKey string) (UserIdentity, *jwt.MapClaims, error) {
if !sys.Initialized() {
return UserIdentity{}, nil, errServerNotInitialized
}
acc, ok := sys.store.GetUser(accessKey)
if !ok {
return UserIdentity{}, nil, errNoSuchAccount
}
jwtClaims, err := auth.ExtractClaims(acc.Credentials.SessionToken, acc.Credentials.SecretKey)
if err != nil {
jwtClaims, err = auth.ExtractClaims(acc.Credentials.SessionToken, globalActiveCred.SecretKey)
if err != nil {
return UserIdentity{}, nil, err
}
}
return acc, jwtClaims, nil
}
// GetClaimsForSvcAcc - gets the claims associated with the service account.
+1 -1
View File
@@ -211,7 +211,7 @@ func (e *metaCacheEntry) fileInfo(bucket string) (FileInfo, error) {
ModTime: timeSentinel1970,
}, nil
}
return e.cached.ToFileInfo(bucket, e.name, "")
return e.cached.ToFileInfo(bucket, e.name, "", false)
}
return getFileInfo(e.metadata, bucket, e.name, "", false)
}
+44 -19
View File
@@ -48,7 +48,6 @@ var (
func init() {
clusterMetricsGroups := []*MetricsGroup{
getBucketUsageMetrics(),
getMinioHealingMetrics(),
getNodeHealthMetrics(),
getClusterStorageMetrics(),
getClusterTierMetrics(),
@@ -69,6 +68,7 @@ func init() {
getScannerNodeMetrics(),
getIAMNodeMetrics(),
getKMSNodeMetrics(),
getMinioHealingMetrics(),
}
allMetricsGroups := func() (allMetrics []*MetricsGroup) {
@@ -1045,7 +1045,9 @@ func getMinIOProcessCPUTime() MetricDescription {
}
func getMinioProcMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
if runtime.GOOS == "windows" {
return nil
@@ -1165,7 +1167,9 @@ func getMinioProcMetrics() *MetricsGroup {
}
func getGoMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
metrics = append(metrics, Metric{
Description: getMinIOGORoutineCountMD(),
@@ -1177,7 +1181,9 @@ func getGoMetrics() *MetricsGroup {
}
func getS3TTFBMetric() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
// Read prometheus metric on this channel
ch := make(chan prometheus.Metric)
@@ -1250,7 +1256,9 @@ func getExpiryPendingTasksMD() MetricDescription {
}
func getILMNodeMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(_ context.Context) []Metric {
expPendingTasks := Metric{
Description: getExpiryPendingTasksMD(),
@@ -1278,7 +1286,9 @@ func getILMNodeMetrics() *MetricsGroup {
}
func getScannerNodeMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(_ context.Context) []Metric {
metrics := []Metric{
{
@@ -1365,7 +1375,9 @@ func getScannerNodeMetrics() *MetricsGroup {
}
func getIAMNodeMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(_ context.Context) (metrics []Metric) {
lastSyncTime := atomic.LoadUint64(&globalIAMSys.LastRefreshTimeUnixNano)
var sinceLastSyncMillis uint64
@@ -1421,7 +1433,9 @@ func getIAMNodeMetrics() *MetricsGroup {
}
func getMinioVersionMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(_ context.Context) (metrics []Metric) {
metrics = append(metrics, Metric{
Description: getMinIOCommitMD(),
@@ -1437,7 +1451,9 @@ func getMinioVersionMetrics() *MetricsGroup {
}
func getNodeHealthMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 1 * time.Minute,
}
mg.RegisterRead(func(_ context.Context) (metrics []Metric) {
metrics = make([]Metric, 0, 16)
nodesUp, nodesDown := globalNotificationSys.GetPeerOnlineCount()
@@ -1455,7 +1471,9 @@ func getNodeHealthMetrics() *MetricsGroup {
}
func getMinioHealingMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(_ context.Context) (metrics []Metric) {
metrics = make([]Metric, 0, 5)
bgSeq, exists := globalBackgroundHealState.getHealSequenceByToken(bgHealingUUID)
@@ -1572,7 +1590,9 @@ func getCacheMetrics() *MetricsGroup {
}
func getNotificationMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(ctx context.Context) []Metric {
stats := globalConfigTargetList.Stats()
metrics := make([]Metric, 0, 1+len(stats.TargetStats))
@@ -1642,7 +1662,9 @@ func getNotificationMetrics() *MetricsGroup {
}
func getHTTPMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
httpStats := globalHTTPStats.toServerHTTPStats()
metrics = make([]Metric, 0, 3+
@@ -1724,7 +1746,9 @@ func getHTTPMetrics() *MetricsGroup {
}
func getNetworkMetrics() *MetricsGroup {
mg := &MetricsGroup{}
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
metrics = make([]Metric, 0, 10)
connStats := globalConnStats.toServerConnStats()
@@ -1766,7 +1790,7 @@ func getNetworkMetrics() *MetricsGroup {
func getBucketUsageMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
cacheInterval: 1 * time.Minute,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
objLayer := newObjectLayerFn()
@@ -1912,7 +1936,7 @@ func getClusterTransitionedVersionsMD() MetricDescription {
func getClusterTierMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
cacheInterval: 1 * time.Minute,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
objLayer := newObjectLayerFn()
@@ -1939,7 +1963,7 @@ func getClusterTierMetrics() *MetricsGroup {
func getLocalStorageMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
cacheInterval: 1 * time.Minute,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
objLayer := newObjectLayerFn()
@@ -2001,7 +2025,7 @@ func getLocalStorageMetrics() *MetricsGroup {
func getLocalDriveStorageMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: 3 * time.Second,
cacheInterval: 1 * time.Minute,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
objLayer := newObjectLayerFn()
@@ -2034,7 +2058,7 @@ func getLocalDriveStorageMetrics() *MetricsGroup {
func getClusterStorageMetrics() *MetricsGroup {
mg := &MetricsGroup{
cacheInterval: 10 * time.Second,
cacheInterval: 1 * time.Minute,
}
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
objLayer := newObjectLayerFn()
@@ -2416,7 +2440,8 @@ func metricsServerHandler() http.Handler {
enc := expfmt.NewEncoder(w, contentType)
for _, mf := range mfs {
if err := enc.Encode(mf); err != nil {
logger.LogIf(r.Context(), err)
// client may disconnect for any reasons
// we do not have to log this.
return
}
}
+2 -1
View File
@@ -590,7 +590,8 @@ func metricsHandler() http.Handler {
enc := expfmt.NewEncoder(w, contentType)
for _, mf := range mfs {
if err := enc.Encode(mf); err != nil {
logger.LogIf(r.Context(), err)
// client may disconnect for any reasons
// we do not have to log this.
return
}
}
+16 -16
View File
@@ -39,9 +39,9 @@ var globalLockServer *localLocker
// RWLocker - locker interface to introduce GetRLock, RUnlock.
type RWLocker interface {
GetLock(ctx context.Context, timeout *dynamicTimeout) (lkCtx LockContext, timedOutErr error)
Unlock(cancel context.CancelFunc)
Unlock(lkCtx LockContext)
GetRLock(ctx context.Context, timeout *dynamicTimeout) (lkCtx LockContext, timedOutErr error)
RUnlock(cancel context.CancelFunc)
RUnlock(lkCtx LockContext)
}
// LockContext lock context holds the lock backed context and canceler for the context.
@@ -182,11 +182,11 @@ func (di *distLockInstance) GetLock(ctx context.Context, timeout *dynamicTimeout
}
// Unlock - block until write lock is released.
func (di *distLockInstance) Unlock(cancel context.CancelFunc) {
if cancel != nil {
cancel()
func (di *distLockInstance) Unlock(lc LockContext) {
if lc.cancel != nil {
lc.cancel()
}
di.rwMutex.Unlock()
di.rwMutex.Unlock(lc.ctx)
}
// RLock - block until read lock is taken or timeout has occurred.
@@ -212,11 +212,11 @@ func (di *distLockInstance) GetRLock(ctx context.Context, timeout *dynamicTimeou
}
// RUnlock - block until read lock is released.
func (di *distLockInstance) RUnlock(cancel context.CancelFunc) {
if cancel != nil {
cancel()
func (di *distLockInstance) RUnlock(lc LockContext) {
if lc.cancel != nil {
lc.cancel()
}
di.rwMutex.RUnlock()
di.rwMutex.RUnlock(lc.ctx)
}
// localLockInstance - frontend/top-level interface for namespace locks.
@@ -270,9 +270,9 @@ func (li *localLockInstance) GetLock(ctx context.Context, timeout *dynamicTimeou
}
// Unlock - block until write lock is released.
func (li *localLockInstance) Unlock(cancel context.CancelFunc) {
if cancel != nil {
cancel()
func (li *localLockInstance) Unlock(lc LockContext) {
if lc.cancel != nil {
lc.cancel()
}
const readLock = false
for _, path := range li.paths {
@@ -307,9 +307,9 @@ func (li *localLockInstance) GetRLock(ctx context.Context, timeout *dynamicTimeo
}
// RUnlock - block until read lock is released.
func (li *localLockInstance) RUnlock(cancel context.CancelFunc) {
if cancel != nil {
cancel()
func (li *localLockInstance) RUnlock(lc LockContext) {
if lc.cancel != nil {
lc.cancel()
}
const readLock = true
for _, path := range li.paths {
+1 -1
View File
@@ -84,7 +84,7 @@ func testDeleteObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
}
for i, testCase := range testCases {
err := obj.MakeBucketWithLocation(context.Background(), testCase.bucketName, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), testCase.bucketName, MakeBucketOptions{})
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
+5 -4
View File
@@ -424,14 +424,15 @@ func (e BucketRemoteTargetNotFound) Error() string {
// RemoteTargetConnectionErr remote target connection failure.
type RemoteTargetConnectionErr struct {
Err error
Bucket string
Endpoint string
Err error
Bucket string
Endpoint string
AccessKey string
}
func (e RemoteTargetConnectionErr) Error() string {
if e.Bucket != "" {
return fmt.Sprintf("Remote service endpoint offline or target bucket/remote service credentials invalid: %s \n\t%s", e.Bucket, e.Err.Error())
return fmt.Sprintf("Remote service endpoint offline, target bucket: %s or remote service credentials: %s invalid \n\t%s", e.Bucket, e.AccessKey, e.Err.Error())
}
return fmt.Sprintf("Remote service endpoint %s not available\n\t%s", e.Endpoint, e.Err.Error())
}
+1 -1
View File
@@ -31,7 +31,7 @@ func TestGetObjectInfo(t *testing.T) {
// Testing GetObjectInfo().
func testGetObjectInfo(obj ObjectLayer, instanceType string, t TestErrHandler) {
// This bucket is used for testing getObjectInfo operations.
err := obj.MakeBucketWithLocation(context.Background(), "test-getobjectinfo", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "test-getobjectinfo", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
+6 -3
View File
@@ -96,6 +96,8 @@ type ObjectOptions struct {
// IndexCB will return any index created but the compression.
// Object must have been read at this point.
IndexCB func() []byte
InclFreeVersions bool
}
// ExpirationOptions represents object options for object expiration at objectLayer.
@@ -115,17 +117,18 @@ type TransitionOptions struct {
// MakeBucketOptions represents bucket options for ObjectLayer bucket operations
type MakeBucketOptions struct {
Location string
LockEnabled bool
VersioningEnabled bool
ForceCreate bool // Create buckets even if they are already created.
CreatedAt time.Time // only for site replication
NoLock bool // does not lock the make bucket call if set to 'true'
}
// DeleteBucketOptions provides options for DeleteBucket calls.
type DeleteBucketOptions struct {
NoLock bool // does not lock the delete bucket call if set to 'true'
NoRecreate bool // do not recreate bucket on delete failures
Force bool // Force deletion
NoRecreate bool // Do not recreate on delete failures
SRDeleteOp SRBucketDeleteOp // only when site replication is enabled
}
@@ -198,7 +201,7 @@ type ObjectLayer interface {
LocalStorageInfo(ctx context.Context) StorageInfo
// Bucket operations.
MakeBucketWithLocation(ctx context.Context, bucket string, opts MakeBucketOptions) error
MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error
GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bucketInfo BucketInfo, err error)
ListBuckets(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error)
DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error
+6 -6
View File
@@ -41,7 +41,7 @@ func testListObjectsVersionedFolders(obj ObjectLayer, instanceType string, t1 Te
"test-bucket-files",
}
for _, bucket := range testBuckets {
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{
VersioningEnabled: true,
})
if err != nil {
@@ -331,7 +331,7 @@ func _testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler, v
6: "test-bucket-custom-delimiter",
}
for _, bucket := range testBuckets {
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{
VersioningEnabled: versioned,
})
if err != nil {
@@ -1036,7 +1036,7 @@ func testDeleteObjectVersion(obj ObjectLayer, instanceType string, t1 TestErrHan
"bucket-suspended-version-id",
}
for _, bucket := range testBuckets {
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{
VersioningEnabled: true,
})
if err != nil {
@@ -1123,7 +1123,7 @@ func testListObjectVersions(obj ObjectLayer, instanceType string, t1 TestErrHand
"test-bucket-max-keys-prefixes",
}
for _, bucket := range testBuckets {
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{VersioningEnabled: true})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{VersioningEnabled: true})
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
@@ -1753,7 +1753,7 @@ func testListObjectsContinuation(obj ObjectLayer, instanceType string, t1 TestEr
"test-bucket-list-object-continuation-2",
}
for _, bucket := range testBuckets {
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
t.Fatalf("%s : %s", instanceType, err.Error())
}
@@ -1908,7 +1908,7 @@ func BenchmarkListObjects(b *testing.B) {
bucket := "ls-benchmark-bucket"
// Create a bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
b.Fatal(err)
}
+11 -11
View File
@@ -56,7 +56,7 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestEr
}
// Create bucket before intiating NewMultipartUpload.
err = obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -89,7 +89,7 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test
object := "minio-object"
opts := ObjectOptions{}
// Create bucket before intiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -136,7 +136,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestE
object := "minio-object"
// Create bucket before intiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -168,7 +168,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
object := "minio-object"
opts := ObjectOptions{}
// Create bucket before intiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -181,7 +181,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
}
uploadID := res.UploadID
// Creating a dummy bucket for tests.
err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "unused-bucket", MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -316,7 +316,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
// objectNames[0].
// uploadIds [0].
// Create bucket before initiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -334,7 +334,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
// objectNames[0].
// uploadIds [1-3].
// Bucket to test for mutiple upload Id's for a given object.
err = obj.MakeBucketWithLocation(context.Background(), bucketNames[1], MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucketNames[1], MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -355,7 +355,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
// bucketnames[2].
// objectNames[0-2].
// uploadIds [4-9].
err = obj.MakeBucketWithLocation(context.Background(), bucketNames[2], MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucketNames[2], MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -1197,7 +1197,7 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks
// objectNames[0].
// uploadIds [0].
// Create bucket before intiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -1441,7 +1441,7 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler)
// objectNames[0].
// uploadIds [0].
// Create bucket before intiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucketNames[0], MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -1682,7 +1682,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T
// objectNames[0].
// uploadIds [0].
// Create bucket before intiating NewMultipartUpload.
err = obj.MakeBucketWithLocation(context.Background(), bucketNames[0], MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err)
+1 -3
View File
@@ -96,9 +96,7 @@ func getOpts(ctx context.Context, r *http.Request, bucket, object string) (Objec
vid := strings.TrimSpace(r.Form.Get(xhttp.VersionID))
if vid != "" && vid != nullVersionID {
_, err := uuid.Parse(vid)
if err != nil {
logger.LogIf(ctx, err)
if _, err := uuid.Parse(vid); err != nil {
return opts, InvalidVersionID{
Bucket: bucket,
Object: object,
+6 -6
View File
@@ -47,14 +47,14 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl
object := "minio-object"
// Create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
}
// Creating a dummy bucket for tests.
err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "unused-bucket", MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -223,14 +223,14 @@ func testObjectAPIPutObjectDiskNotFound(obj ObjectLayer, instanceType string, di
object := "minio-object"
// Create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
}
// Creating a dummy bucket for tests.
err = obj.MakeBucketWithLocation(context.Background(), "unused-bucket", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "unused-bucket", MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -335,7 +335,7 @@ func testObjectAPIPutObjectStaleFiles(obj ObjectLayer, instanceType string, disk
object := "minio-object"
// Create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -380,7 +380,7 @@ func testObjectAPIMultipartPutObjectStaleFiles(obj ObjectLayer, instanceType str
object := "minio-object"
// Create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucket, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
+7
View File
@@ -170,6 +170,13 @@ func IsValidObjectPrefix(object string) bool {
if strings.Contains(object, `//`) {
return false
}
// This is valid for AWS S3 but it will never
// work with file systems, we will reject here
// to return object name invalid rather than
// a cryptic error from the file system.
if strings.ContainsRune(object, 0) {
return false
}
return true
}
+7 -7
View File
@@ -194,7 +194,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
return
}
ctx = lkctx.Context()
defer lock.RUnlock(lkctx.Cancel)
defer lock.RUnlock(lkctx)
getObjectNInfo := objectAPI.GetObjectNInfo
if api.CacheAPI() != nil {
@@ -397,7 +397,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
// Handle only errInvalidRange. Ignore other
// parse error and treat it as regular Get
// request like Amazon S3.
if rangeErr == errInvalidRange {
if errors.Is(rangeErr, errInvalidRange) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidRange), r.URL)
return
}
@@ -433,7 +433,7 @@ func (api objectAPIHandlers) getObjectHandler(ctx context.Context, objectAPI Obj
proxyGetErr := ErrorRespToObjectError(perr, bucket, object)
if !isErrObjectNotFound(proxyGetErr) && !isErrVersionNotFound(proxyGetErr) &&
!isErrPreconditionFailed(proxyGetErr) && !isErrInvalidRange(proxyGetErr) {
logger.LogIf(ctx, fmt.Errorf("Replication proxy failed for %s/%s(%s) - %w", bucket, object, opts.VersionID, perr))
logger.LogIf(ctx, fmt.Errorf("Proxying request (replication) failed for %s/%s(%s) - %w", bucket, object, opts.VersionID, perr))
}
}
if reader != nil && proxy.Proxy && perr == nil {
@@ -803,12 +803,12 @@ func (api objectAPIHandlers) headObjectHandler(ctx context.Context, objectAPI Ob
// Handle only errInvalidRange. Ignore other
// parse error and treat it as regular Get
// request like Amazon S3.
if err == errInvalidRange {
if errors.Is(err, errInvalidRange) {
writeErrorResponseHeadersOnly(w, errorCodes.ToAPIErr(ErrInvalidRange))
return
}
logger.LogIf(ctx, err)
logger.LogIf(ctx, err, logger.Application)
}
}
@@ -3155,7 +3155,7 @@ func (api objectAPIHandlers) PostRestoreObjectHandler(w http.ResponseWriter, r *
w.WriteHeader(statusCode)
// Notify object restore started via a POST request.
sendEvent(eventArgs{
EventName: event.ObjectRestorePostInitiated,
EventName: event.ObjectRestorePost,
BucketName: bucket,
Object: objInfo,
ReqParams: extractReqParams(r),
@@ -3224,7 +3224,7 @@ func (api objectAPIHandlers) PostRestoreObjectHandler(w http.ResponseWriter, r *
// Notify object restore completed via a POST request.
sendEvent(eventArgs{
EventName: event.ObjectRestorePostCompleted,
EventName: event.ObjectRestoreCompleted,
BucketName: bucket,
Object: objInfo,
ReqParams: extractReqParams(r),
+18 -18
View File
@@ -77,7 +77,7 @@ func TestMakeBucket(t *testing.T) {
// Tests validate bucket creation.
func testMakeBucket(obj ObjectLayer, instanceType string, t TestErrHandler) {
err := obj.MakeBucketWithLocation(context.Background(), "bucket-unknown", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket-unknown", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -91,7 +91,7 @@ func TestMultipartObjectCreation(t *testing.T) {
// Tests validate creation of part files during Multipart operation.
func testMultipartObjectCreation(obj ObjectLayer, instanceType string, t TestErrHandler) {
var opts ObjectOptions
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -137,7 +137,7 @@ func TestMultipartObjectAbort(t *testing.T) {
// Tests validate abortion of Multipart operation.
func testMultipartObjectAbort(obj ObjectLayer, instanceType string, t TestErrHandler) {
var opts ObjectOptions
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -184,7 +184,7 @@ func TestMultipleObjectCreation(t *testing.T) {
func testMultipleObjectCreation(obj ObjectLayer, instanceType string, t TestErrHandler) {
objects := make(map[string][]byte)
var opts ObjectOptions
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -239,7 +239,7 @@ func TestPaging(t *testing.T) {
// Tests validate creation of objects and the order of listing using various filters for ListObjects operation.
func testPaging(obj ObjectLayer, instanceType string, t TestErrHandler) {
obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
result, err := obj.ListObjects(context.Background(), "bucket", "", "", "", 0)
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
@@ -443,7 +443,7 @@ func TestObjectOverwriteWorks(t *testing.T) {
// Tests validate overwriting of an existing object.
func testObjectOverwriteWorks(obj ObjectLayer, instanceType string, t TestErrHandler) {
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -497,11 +497,11 @@ func TestBucketRecreateFails(t *testing.T) {
// Tests validate that recreation of the bucket fails.
func testBucketRecreateFails(obj ObjectLayer, instanceType string, t TestErrHandler) {
err := obj.MakeBucketWithLocation(context.Background(), "string", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "string", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
err = obj.MakeBucketWithLocation(context.Background(), "string", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "string", MakeBucketOptions{})
if err == nil {
t.Fatalf("%s: Expected error but found nil.", instanceType)
}
@@ -602,7 +602,7 @@ func testPutObject(obj ObjectLayer, instanceType string, t TestErrHandler) {
length := int64(len(content))
readerEOF := newTestReaderEOF(content)
readerNoEOF := newTestReaderNoEOF(content)
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -642,7 +642,7 @@ func TestPutObjectInSubdir(t *testing.T) {
// Tests validate PutObject with subdirectory prefix.
func testPutObjectInSubdir(obj ObjectLayer, instanceType string, t TestErrHandler) {
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -684,7 +684,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, t TestErrHandler) {
}
// add one and test exists.
err = obj.MakeBucketWithLocation(context.Background(), "bucket1", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "bucket1", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -698,7 +698,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, t TestErrHandler) {
}
// add two and test exists.
err = obj.MakeBucketWithLocation(context.Background(), "bucket2", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "bucket2", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -712,7 +712,7 @@ func testListBuckets(obj ObjectLayer, instanceType string, t TestErrHandler) {
}
// add three and test exists + prefix.
err = obj.MakeBucketWithLocation(context.Background(), "bucket22", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "bucket22", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -736,11 +736,11 @@ func testListBucketsOrder(obj ObjectLayer, instanceType string, t TestErrHandler
// if implementation contains a map, order of map keys will vary.
// this ensures they return in the same order each time.
// add one and test exists.
err := obj.MakeBucketWithLocation(context.Background(), "bucket1", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket1", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
err = obj.MakeBucketWithLocation(context.Background(), "bucket2", MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), "bucket2", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -789,7 +789,7 @@ func TestNonExistantObjectInBucket(t *testing.T) {
// Tests validate that GetObject fails on a non-existent bucket as expected.
func testNonExistantObjectInBucket(obj ObjectLayer, instanceType string, t TestErrHandler) {
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -817,7 +817,7 @@ func TestGetDirectoryReturnsObjectNotFound(t *testing.T) {
// Tests validate that GetObject on an existing directory fails as expected.
func testGetDirectoryReturnsObjectNotFound(obj ObjectLayer, instanceType string, t TestErrHandler) {
bucketName := "bucket"
err := obj.MakeBucketWithLocation(context.Background(), bucketName, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucketName, MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
@@ -859,7 +859,7 @@ func TestContentType(t *testing.T) {
// Test content-type.
func testContentType(obj ObjectLayer, instanceType string, t TestErrHandler) {
err := obj.MakeBucketWithLocation(context.Background(), "bucket", MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), "bucket", MakeBucketOptions{})
if err != nil {
t.Fatalf("%s: <ERROR> %s", instanceType, err)
}
+346
View File
@@ -0,0 +1,346 @@
// Copyright (c) 2015-2022 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"
"encoding/gob"
"errors"
"fmt"
"io"
"net/url"
"strconv"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest"
"github.com/minio/minio/internal/sync/errgroup"
xnet "github.com/minio/pkg/net"
)
var errPeerOffline = errors.New("peer is offline")
// client to talk to peer Nodes.
type peerS3Client struct {
host *xnet.Host
restClient *rest.Client
}
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *peerS3Client) call(method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
return client.callWithContext(GlobalContext, method, values, body, length)
}
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
// after verifying format.json
func (client *peerS3Client) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
if values == nil {
values = make(url.Values)
}
respBody, err = client.restClient.Call(ctx, method, values, body, length)
if err == nil {
return respBody, nil
}
err = toStorageErr(err)
return nil, err
}
// S3PeerSys - S3 peer call system.
type S3PeerSys struct {
peerClients []*peerS3Client // Excludes self
allPeerClients []*peerS3Client // Includes nil client for self
}
// NewS3PeerSys - creates new S3 peer calls.
func NewS3PeerSys(endpoints EndpointServerPools) *S3PeerSys {
remote, all := newPeerS3Clients(endpoints)
return &S3PeerSys{
peerClients: remote,
allPeerClients: all,
}
}
// ListBuckets lists buckets across all servers and returns a possible consistent view
func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) (result []BucketInfo, err error) {
g := errgroup.WithNErrs(len(sys.peerClients))
localBuckets, err := listBucketsLocal(ctx, opts)
if err != nil {
return nil, err
}
nodeBuckets := make([][]BucketInfo, len(sys.peerClients)+1)
errs := []error{nil}
nodeBuckets[0] = localBuckets
for idx, client := range sys.peerClients {
idx := idx
client := client
g.Go(func() error {
if client == nil {
return errPeerOffline
}
localBuckets, err := client.ListBuckets(ctx, opts)
if err != nil {
return err
}
nodeBuckets[idx+1] = localBuckets
return nil
}, idx)
}
errs = append(errs, g.Wait()...)
quorum := (len(sys.allPeerClients) / 2)
if err = reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, quorum); err != nil {
return nil, err
}
bucketsMap := make(map[string]struct{})
for idx, buckets := range nodeBuckets {
if errs[idx] != nil {
continue
}
for _, bi := range buckets {
_, ok := bucketsMap[bi.Name]
if !ok {
bucketsMap[bi.Name] = struct{}{}
result = append(result, bi)
}
}
}
return result, nil
}
// GetBucketInfo returns bucket stat info about bucket on disk across all peers
func (sys *S3PeerSys) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (binfo BucketInfo, err error) {
g := errgroup.WithNErrs(len(sys.peerClients))
bucketInfos := make([]BucketInfo, len(sys.peerClients)+1)
bucketInfo, err := getBucketInfoLocal(ctx, bucket, opts)
if err != nil {
return BucketInfo{}, err
}
errs := []error{nil}
bucketInfos[0] = bucketInfo
for idx, client := range sys.peerClients {
idx := idx
client := client
g.Go(func() error {
if client == nil {
return errPeerOffline
}
bucketInfo, err := client.GetBucketInfo(ctx, bucket, opts)
if err != nil {
return err
}
bucketInfos[idx] = bucketInfo
return nil
}, idx)
}
errs = append(errs, g.Wait()...)
quorum := (len(sys.allPeerClients) / 2)
if err = reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, quorum); err != nil {
return BucketInfo{}, err
}
for i, err := range errs {
if err == nil {
bucketInfo = bucketInfos[i]
break
}
}
return bucketInfo, nil
}
func (client *peerS3Client) ListBuckets(ctx context.Context, opts BucketOptions) ([]BucketInfo, error) {
v := url.Values{}
v.Set(peerS3BucketDeleted, strconv.FormatBool(opts.Deleted))
respBody, err := client.call(peerS3MethodListBuckets, v, nil, -1)
if err != nil {
return nil, err
}
defer xhttp.DrainBody(respBody)
var buckets []BucketInfo
err = gob.NewDecoder(respBody).Decode(&buckets)
return buckets, err
}
// GetBucketInfo returns bucket stat info from a peer
func (client *peerS3Client) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
v := url.Values{}
v.Set(peerS3Bucket, bucket)
v.Set(peerS3BucketDeleted, strconv.FormatBool(opts.Deleted))
respBody, err := client.call(peerS3MethodGetBucketInfo, v, nil, -1)
if err != nil {
return BucketInfo{}, err
}
defer xhttp.DrainBody(respBody)
var bucketInfo BucketInfo
err = gob.NewDecoder(respBody).Decode(&bucketInfo)
return bucketInfo, err
}
// MakeBucket creates bucket across all peers
func (sys *S3PeerSys) MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error {
g := errgroup.WithNErrs(len(sys.peerClients))
for idx, client := range sys.peerClients {
client := client
g.Go(func() error {
if client == nil {
return errPeerOffline
}
return client.MakeBucket(ctx, bucket, opts)
}, idx)
}
errs := g.Wait()
errs = append(errs, makeBucketLocal(ctx, bucket, opts))
quorum := (len(sys.allPeerClients) / 2) + 1
err := reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, quorum)
return toObjectErr(err, bucket)
}
// MakeBucket creates a bucket on a peer
func (client *peerS3Client) MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error {
v := url.Values{}
v.Set(peerS3Bucket, bucket)
v.Set(peerS3BucketForceCreate, strconv.FormatBool(opts.ForceCreate))
respBody, err := client.call(peerS3MethodMakeBucket, v, nil, -1)
if err != nil {
return err
}
defer xhttp.DrainBody(respBody)
return nil
}
// DeleteBucket deletes bucket across all peers
func (sys *S3PeerSys) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
g := errgroup.WithNErrs(len(sys.peerClients))
for idx, client := range sys.peerClients {
client := client
g.Go(func() error {
if client == nil {
return errPeerOffline
}
return client.DeleteBucket(ctx, bucket, opts)
}, idx)
}
errs := g.Wait()
errs = append(errs, deleteBucketLocal(ctx, bucket, opts))
for _, err := range errs {
if err != nil {
return err
}
}
return nil
}
// DeleteBucket deletes bucket on a peer
func (client *peerS3Client) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
v := url.Values{}
v.Set(peerS3Bucket, bucket)
v.Set(peerS3BucketForceDelete, strconv.FormatBool(opts.Force))
respBody, err := client.call(peerS3MethodDeleteBucket, v, nil, -1)
if err != nil {
return err
}
defer xhttp.DrainBody(respBody)
return nil
}
// newPeerS3Clients creates new peer clients.
// The two slices will point to the same clients,
// but 'all' will contain nil entry for local client.
// The 'all' slice will be in the same order across the cluster.
func newPeerS3Clients(endpoints EndpointServerPools) (remote, all []*peerS3Client) {
if !globalIsDistErasure {
// Only useful in distributed setups
return nil, nil
}
hosts := endpoints.hostsSorted()
remote = make([]*peerS3Client, 0, len(hosts))
all = make([]*peerS3Client, len(hosts))
for i, host := range hosts {
if host == nil {
continue
}
all[i] = newPeerS3Client(host)
remote = append(remote, all[i])
}
if len(all) != len(remote)+1 {
logger.LogIf(context.Background(), fmt.Errorf("WARNING: Expected number of all hosts (%v) to be remote +1 (%v)", len(all), len(remote)))
}
return remote, all
}
// Returns a peer S3 client.
func newPeerS3Client(peer *xnet.Host) *peerS3Client {
scheme := "http"
if globalIsTLS {
scheme = "https"
}
serverURL := &url.URL{
Scheme: scheme,
Host: peer.String(),
Path: peerS3Path,
}
restClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
// Use a separate client to avoid recursive calls.
healthClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
healthClient.ExpectTimeouts = true
healthClient.NoMetrics = true
// Construct a new health function.
restClient.HealthCheckFn = func() bool {
ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout)
defer cancel()
respBody, err := healthClient.Call(ctx, peerS3MethodHealth, nil, nil, -1)
xhttp.DrainBody(respBody)
return !isNetworkError(err)
}
return &peerS3Client{host: peer, restClient: restClient}
}
+325
View File
@@ -0,0 +1,325 @@
// Copyright (c) 2015-2022 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"
"encoding/gob"
"errors"
"net/http"
"sort"
"github.com/gorilla/mux"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/sync/errgroup"
)
const (
peerS3Version = "v1" // First implementation
peerS3VersionPrefix = SlashSeparator + peerS3Version
peerS3Prefix = minioReservedBucketPath + "/peer"
peerS3Path = peerS3Prefix + peerS3VersionPrefix
)
const (
peerS3MethodHealth = "/health"
peerS3MethodMakeBucket = "/make-bucket"
peerS3MethodGetBucketInfo = "/get-bucket-info"
peerS3MethodDeleteBucket = "/delete-bucket"
peerS3MethodListBuckets = "/list-buckets"
)
const (
peerS3Bucket = "bucket"
peerS3BucketDeleted = "bucket-deleted"
peerS3BucketForceCreate = "force-create"
peerS3BucketForceDelete = "force-delete"
)
type peerS3Server struct{}
func (s *peerS3Server) writeErrorResponse(w http.ResponseWriter, err error) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(err.Error()))
}
// IsValid - To authenticate and verify the time difference.
func (s *peerS3Server) IsValid(w http.ResponseWriter, r *http.Request) bool {
objAPI := newObjectLayerFn()
if objAPI == nil {
s.writeErrorResponse(w, errServerNotInitialized)
return false
}
if err := storageServerRequestValidate(r); err != nil {
s.writeErrorResponse(w, err)
return false
}
return true
}
// HealthHandler - returns true of health
func (s *peerS3Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
s.IsValid(w, r)
}
func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
quorum := (len(globalLocalDrives) / 2)
buckets = make([]BucketInfo, 0, 32)
healBuckets := map[string]VolInfo{}
// lists all unique buckets across drives.
if err := listAllBuckets(ctx, globalLocalDrives, healBuckets, quorum); err != nil {
return nil, err
}
// include deleted buckets in listBuckets output
deletedBuckets := map[string]VolInfo{}
if opts.Deleted {
// lists all deleted buckets across drives.
if err := listDeletedBuckets(ctx, globalLocalDrives, deletedBuckets, quorum); err != nil {
return nil, err
}
}
for _, v := range healBuckets {
bi := BucketInfo{
Name: v.Name,
Created: v.Created,
}
if vi, ok := deletedBuckets[v.Name]; ok {
bi.Deleted = vi.Created
}
buckets = append(buckets, bi)
}
for _, v := range deletedBuckets {
if _, ok := healBuckets[v.Name]; !ok {
buckets = append(buckets, BucketInfo{
Name: v.Name,
Deleted: v.Created,
})
}
}
sort.Slice(buckets, func(i, j int) bool {
return buckets[i].Name < buckets[j].Name
})
return buckets, nil
}
func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
bucketsInfo := make([]BucketInfo, len(globalLocalDrives))
// Make a volume entry on all underlying storage disks.
for index := range globalLocalDrives {
index := index
g.Go(func() error {
if globalLocalDrives[index] == nil {
return errDiskNotFound
}
volInfo, err := globalLocalDrives[index].StatVol(ctx, bucket)
if err != nil {
if opts.Deleted {
dvi, derr := globalLocalDrives[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket))
if derr != nil {
return err
}
bucketsInfo[index] = BucketInfo{Name: bucket, Deleted: dvi.Created}
return nil
}
return err
}
bucketsInfo[index] = BucketInfo{Name: bucket, Created: volInfo.Created}
return nil
}, index)
}
errs := g.Wait()
if err := reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives) / 2)); err != nil {
return BucketInfo{}, err
}
var bucketInfo BucketInfo
for i, err := range errs {
if err == nil {
bucketInfo = bucketsInfo[i]
break
}
}
return bucketInfo, nil
}
func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
// Make a volume entry on all underlying storage disks.
for index := range globalLocalDrives {
index := index
g.Go(func() error {
if globalLocalDrives[index] == nil {
return errDiskNotFound
}
return globalLocalDrives[index].DeleteVol(ctx, bucket, opts.Force)
}, index)
}
var recreate bool
errs := g.Wait()
for index, err := range errs {
if errors.Is(err, errVolumeNotEmpty) {
recreate = true
}
if err == nil && recreate {
// ignore any errors
globalLocalDrives[index].MakeVol(ctx, bucket)
}
}
for _, err := range errs {
if err != nil {
return err
}
}
return nil
}
func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions) error {
g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
// Make a volume entry on all underlying storage disks.
for index := range globalLocalDrives {
index := index
g.Go(func() error {
if globalLocalDrives[index] == nil {
return errDiskNotFound
}
err := globalLocalDrives[index].MakeVol(ctx, bucket)
if opts.ForceCreate && errors.Is(err, errVolumeExists) {
// No need to return error when force create was
// requested.
return nil
}
if err != nil && !errors.Is(err, errVolumeExists) {
logger.LogIf(ctx, err)
}
return err
}, index)
}
errs := g.Wait()
return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives)/2)+1)
}
func (s *peerS3Server) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
return
}
bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
buckets, err := listBucketsLocal(r.Context(), BucketOptions{
Deleted: bucketDeleted,
})
if err != nil {
s.writeErrorResponse(w, err)
return
}
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(buckets))
}
// GetBucketInfoHandler implements peer BuckeInfo call, returns bucket create date.
func (s *peerS3Server) GetBucketInfoHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
return
}
bucket := r.Form.Get(peerS3Bucket)
bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
bucketInfo, err := getBucketInfoLocal(r.Context(), bucket, BucketOptions{
Deleted: bucketDeleted,
})
if err != nil {
s.writeErrorResponse(w, err)
return
}
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(bucketInfo))
}
// DeleteBucketHandler implements peer delete bucket call.
func (s *peerS3Server) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
return
}
bucket := r.Form.Get(peerS3Bucket)
if isMinioMetaBucket(bucket) {
s.writeErrorResponse(w, errInvalidArgument)
return
}
forceDelete := r.Form.Get(peerS3BucketForceDelete) == "true"
err := deleteBucketLocal(r.Context(), bucket, DeleteBucketOptions{
Force: forceDelete,
})
if err != nil {
s.writeErrorResponse(w, err)
return
}
}
// MakeBucketHandler implements peer create bucket call.
func (s *peerS3Server) MakeBucketHandler(w http.ResponseWriter, r *http.Request) {
if !s.IsValid(w, r) {
return
}
bucket := r.Form.Get(peerS3Bucket)
forceCreate := r.Form.Get(peerS3BucketForceCreate) == "true"
err := makeBucketLocal(r.Context(), bucket, MakeBucketOptions{
ForceCreate: forceCreate,
})
if err != nil {
s.writeErrorResponse(w, err)
return
}
}
// registerPeerS3Handlers - register peer s3 router.
func registerPeerS3Handlers(router *mux.Router) {
server := &peerS3Server{}
subrouter := router.PathPrefix(peerS3Prefix).Subrouter()
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodHealth).HandlerFunc(httpTraceHdrs(server.HealthHandler))
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodMakeBucket).HandlerFunc(httpTraceHdrs(server.MakeBucketHandler))
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodDeleteBucket).HandlerFunc(httpTraceHdrs(server.DeleteBucketHandler))
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodGetBucketInfo).HandlerFunc(httpTraceHdrs(server.GetBucketInfoHandler))
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodListBuckets).HandlerFunc(httpTraceHdrs(server.ListBucketsHandler))
}
+2 -2
View File
@@ -141,7 +141,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
// objectNames[0].
// uploadIds [0].
// Create bucket before initiating NewMultipartUpload.
err := obj.MakeBucketWithLocation(context.Background(), bucketName, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucketName, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
@@ -460,7 +460,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
curTime := UTCNow()
curTimePlus5Min := curTime.Add(time.Minute * 5)
err = obj.MakeBucketWithLocation(context.Background(), bucketName, MakeBucketOptions{})
err = obj.MakeBucket(context.Background(), bucketName, MakeBucketOptions{})
if err != nil {
// Failed to create newbucket, abort.
t.Fatalf("%s : %s", instanceType, err.Error())
+3
View File
@@ -31,6 +31,9 @@ func registerDistErasureRouters(router *mux.Router, endpointServerPools Endpoint
// Register peer REST router only if its a distributed setup.
registerPeerRESTHandlers(router)
// Register peer S3 router only if its a distributed setup.
registerPeerS3Handlers(router)
// Register bootstrap REST router for distributed setups.
registerBootstrapRESTHandlers(router)
+65 -40
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc.
// Copyright (c) 2015-2022 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
@@ -19,7 +19,6 @@ package cmd
import (
"context"
"crypto/tls"
"errors"
"fmt"
"io"
@@ -35,6 +34,7 @@ import (
"time"
"github.com/minio/cli"
"github.com/minio/madmin-go/v2"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/set"
@@ -42,10 +42,8 @@ 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/fips"
xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest"
"github.com/minio/pkg/certs"
"github.com/minio/pkg/env"
)
@@ -236,19 +234,9 @@ func serverHandleCmdArgs(ctx *cli.Context) {
}
// allow transport to be HTTP/1.1 for proxying.
globalProxyTransport = newCustomHTTPProxyTransport(&tls.Config{
RootCAs: globalRootCAs,
CipherSuites: fips.TLSCiphers(),
CurvePreferences: fips.TLSCurveIDs(),
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
}, rest.DefaultTimeout)()
globalProxyTransport = NewCustomHTTPProxyTransport()()
globalProxyEndpoints = GetProxyEndpoints(globalEndpoints)
globalInternodeTransport = newInternodeHTTPTransport(&tls.Config{
RootCAs: globalRootCAs,
CipherSuites: fips.TLSCiphers(),
CurvePreferences: fips.TLSCurveIDs(),
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
}, rest.DefaultTimeout)()
globalInternodeTransport = NewInternodeHTTPTransport()()
globalRemoteTargetTransport = NewRemoteTargetHTTPTransport()()
// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back
@@ -358,6 +346,20 @@ func configRetriableErrors(err error) bool {
errors.Is(err, os.ErrDeadlineExceeded)
}
func bootstrapTrace(msg string) {
if globalTrace.NumSubscribers(madmin.TraceBootstrap) == 0 {
return
}
globalTrace.Publish(madmin.TraceInfo{
TraceType: madmin.TraceBootstrap,
Time: time.Now().UTC(),
NodeName: globalLocalNodeName,
FuncName: "BOOTSTRAP",
Message: fmt.Sprintf("%s %s", getSource(2), msg),
})
}
func initServer(ctx context.Context, newObject ObjectLayer) error {
t1 := time.Now()
@@ -370,7 +372,10 @@ func initServer(ctx context.Context, newObject ObjectLayer) error {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
lockTimeout := newDynamicTimeout(5*time.Second, 3*time.Second)
lockTimeout := newDynamicTimeoutWithOpts(dynamicTimeoutOpts{
timeout: 5 * time.Second,
minimum: 3 * time.Second,
})
for {
select {
@@ -380,6 +385,8 @@ func initServer(ctx context.Context, newObject ObjectLayer) error {
default:
}
bootstrapTrace("trying to acquire transaction.lock")
// Make sure to hold lock for entire migration to avoid
// such that only one server should migrate the entire config
// at a given time, this big transaction lock ensures this
@@ -392,8 +399,15 @@ func initServer(ctx context.Context, newObject ObjectLayer) error {
lkctx, err := txnLk.GetLock(ctx, lockTimeout)
if err != nil {
logger.Info("Waiting for all MinIO sub-systems to be initialized.. trying to acquire lock")
waitDuration := time.Duration(r.Float64() * 5 * float64(time.Second))
bootstrapTrace(fmt.Sprintf("lock not available. error: %v. sleeping for %v before retry", err, waitDuration))
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
// Sleep 0 -> 5 seconds, provider a higher range such that sleeps()
// and retries for lock are more spread out, needed orchestrated
// systems take 30s minimum to respond to DNS resolvers.
//
// Do not change this value.
time.Sleep(waitDuration)
continue
}
@@ -409,7 +423,7 @@ func initServer(ctx context.Context, newObject ObjectLayer) error {
// Upon success migrating the config, initialize all sub-systems
// if all sub-systems initialized successfully return right away
if err = initConfigSubsystem(lkctx.Context(), newObject); err == nil {
txnLk.Unlock(lkctx.Cancel)
txnLk.Unlock(lkctx)
// All successful return.
if globalIsDistErasure {
// These messages only meant primarily for distributed setup, so only log during distributed setup.
@@ -420,7 +434,7 @@ func initServer(ctx context.Context, newObject ObjectLayer) error {
}
// Unlock the transaction lock and allow other nodes to acquire the lock if possible.
txnLk.Unlock(lkctx.Cancel)
txnLk.Unlock(lkctx)
if configRetriableErrors(err) {
logger.Info("Waiting for all MinIO sub-systems to be initialized.. possible cause (%v)", err)
@@ -466,8 +480,23 @@ func getServerListenAddrs() []string {
addrs.Add(net.JoinHostPort(ip.String(), globalMinioPort))
}
}
// Add the interface specified by the user
addrs.Add(globalMinioAddr)
host, _ := mustSplitHostPort(globalMinioAddr)
if host != "" {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
haddrs, err := globalDNSCache.LookupHost(ctx, host)
if err == nil {
for _, addr := range haddrs {
addrs.Add(net.JoinHostPort(addr, globalMinioPort))
}
} else {
// Unable to lookup host in 2-secs, let it fail later anyways.
addrs.Add(globalMinioAddr)
}
} else {
addrs.Add(globalMinioAddr)
}
return addrs.ToSlice()
}
@@ -524,10 +553,6 @@ func serverMain(ctx *cli.Context) {
}
}()
if !globalActiveCred.IsValid() && globalIsDistErasure {
globalActiveCred = auth.DefaultCredentials
}
// Set system resources to maximum.
setMaxResources()
@@ -539,7 +564,7 @@ func serverMain(ctx *cli.Context) {
maxProcs := runtime.GOMAXPROCS(0)
cpuProcs := runtime.NumCPU()
if maxProcs < cpuProcs {
logger.Info(color.RedBold("WARNING: Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance", maxProcs, cpuProcs))
logger.Info(color.RedBoldf("WARNING: Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance", maxProcs, cpuProcs))
}
// Configure server.
@@ -569,6 +594,7 @@ func serverMain(ctx *cli.Context) {
setHTTPServer(httpServer)
if globalIsDistErasure {
bootstrapTrace("verifying system configuration")
// Additionally in distributed setup, validate the setup and configuration.
if err := verifyServerSystemConfig(GlobalContext, globalEndpoints); err != nil {
logger.Fatal(err, "Unable to start the server")
@@ -594,6 +620,13 @@ func serverMain(ctx *cli.Context) {
logger.Info(color.RedBold("WARNING: Strict AWS S3 compatible incoming PUT, POST content payload validation is turned off, caution is advised do not use in production"))
}
if globalActiveCred.Equal(auth.DefaultCredentials) {
msg := fmt.Sprintf("WARNING: Detected default credentials '%s', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables",
globalActiveCred)
logger.Info(color.RedBold(msg))
}
bootstrapTrace("initializing the server")
if err = initServer(GlobalContext, newObject); err != nil {
var cerr config.Err
// For any config error, we don't need to drop into safe-mode
@@ -610,19 +643,6 @@ func serverMain(ctx *cli.Context) {
logger.LogIf(GlobalContext, err)
}
if globalActiveCred.Equal(auth.DefaultCredentials) {
msg := fmt.Sprintf("WARNING: Detected default credentials '%s', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables",
globalActiveCred)
logger.Info(color.RedBold(msg))
}
savedCreds, _ := config.LookupCreds(globalServerConfig[config.CredentialsSubSys][config.Default])
if globalActiveCred.Equal(auth.DefaultCredentials) && !globalActiveCred.Equal(savedCreds) {
msg := fmt.Sprintf("WARNING: Detected credentials changed to '%s', please set them back to previously set values",
globalActiveCred)
logger.Info(color.RedBold(msg))
}
// Initialize users credentials and policies in background right after config has initialized.
go func() {
globalIAMSys.Init(GlobalContext, newObject, globalEtcdClient, globalRefreshIAMInterval)
@@ -702,6 +722,11 @@ func serverMain(ctx *cli.Context) {
// 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
+3 -1
View File
@@ -59,12 +59,14 @@ type check struct {
// Assert - checks if gotValue is same as expectedValue, if not fails the test.
func (c *check) Assert(gotValue interface{}, expectedValue interface{}) {
c.Helper()
if !reflect.DeepEqual(gotValue, expectedValue) {
c.Fatalf("Test %s:%s expected %v, got %v", getSource(2), c.testType, expectedValue, gotValue)
c.Fatalf("Test %s expected %v, got %v", c.testType, expectedValue, gotValue)
}
}
func verifyError(c *check, response *http.Response, code, description string, statusCode int) {
c.Helper()
data, err := io.ReadAll(response.Body)
c.Assert(err, nil)
errorResponse := APIErrorResponse{}
-2
View File
@@ -79,11 +79,9 @@ func handleSignals() {
logger.LogIf(context.Background(), err)
exit(stopProcess())
case osSignal := <-globalOSSignalCh:
globalReplicationPool.SaveState(context.Background())
logger.Info("Exiting on signal: %s", strings.ToUpper(osSignal.String()))
exit(stopProcess())
case signal := <-globalServiceSignalCh:
globalReplicationPool.SaveState(context.Background())
switch signal {
case serviceRestart:
logger.Info("Restarting on service signal")
+7 -7
View File
@@ -141,15 +141,15 @@ func isValidRegion(reqRegion string, confRegion string) bool {
// check if the access key is valid and recognized, additionally
// also returns if the access key is owner/admin.
func checkKeyValid(r *http.Request, accessKey string) (auth.Credentials, bool, APIErrorCode) {
if !globalIAMSys.Initialized() {
// Check if server has initialized, then only proceed
// to check for IAM users otherwise its okay for clients
// to retry with 503 errors when server is coming up.
return auth.Credentials{}, false, ErrServerNotInitialized
}
cred := globalActiveCred
if cred.AccessKey != accessKey {
if !globalIAMSys.Initialized() {
// Check if server has initialized, then only proceed
// to check for IAM users otherwise its okay for clients
// to retry with 503 errors when server is coming up.
return auth.Credentials{}, false, ErrServerNotInitialized
}
// Check if the access key is part of users credentials.
u, ok := globalIAMSys.GetUser(r.Context(), accessKey)
if !ok {
+56 -32
View File
@@ -72,6 +72,10 @@ var (
Cause: errors.New("peer not found"),
Code: ErrSiteReplicationInvalidRequest,
}
errSRRequestorNotFound = SRError{
Cause: errors.New("requesting site not found in site replication config"),
Code: ErrSiteReplicationInvalidRequest,
}
errSRNotEnabled = SRError{
Cause: errors.New("site replication is not enabled"),
Code: ErrSiteReplicationInvalidRequest,
@@ -678,9 +682,6 @@ func (c *SiteReplicationSys) MakeBucketHook(ctx context.Context, bucket string,
}
optsMap := make(map[string]string)
if opts.Location != "" {
optsMap["location"] = opts.Location
}
if opts.LockEnabled {
optsMap["lockEnabled"] = "true"
optsMap["versioningEnabled"] = "true"
@@ -776,7 +777,7 @@ func (c *SiteReplicationSys) PeerBucketMakeWithVersioningHandler(ctx context.Con
return errServerNotInitialized
}
err := objAPI.MakeBucketWithLocation(ctx, bucket, opts)
err := objAPI.MakeBucket(ctx, bucket, opts)
if err != nil {
// Check if this is a bucket exists error.
_, ok1 := err.(BucketExists)
@@ -852,21 +853,24 @@ func (c *SiteReplicationSys) PeerBucketConfigureReplHandler(ctx context.Context,
Region: "",
ReplicationSync: false,
}
bucketTarget.Arn = globalBucketTargetSys.getRemoteARN(bucket, &bucketTarget)
err := globalBucketTargetSys.SetTarget(ctx, bucket, &bucketTarget, false)
if err != nil {
return c.annotatePeerErr(peer.Name, "Bucket target creation error", err)
}
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
if err != nil {
return err
}
tgtBytes, err := json.Marshal(&targets)
if err != nil {
return err
}
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
return err
var exists bool // true if ARN already exists
bucketTarget.Arn, exists = globalBucketTargetSys.getRemoteARN(bucket, &bucketTarget)
if !exists { // persist newly generated ARN to targets and metadata on disk
err := globalBucketTargetSys.SetTarget(ctx, bucket, &bucketTarget, false)
if err != nil {
return c.annotatePeerErr(peer.Name, "Bucket target creation error", err)
}
targets, err := globalBucketTargetSys.ListBucketTargets(ctx, bucket)
if err != nil {
return err
}
tgtBytes, err := json.Marshal(&targets)
if err != nil {
return err
}
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketTargetsFile, tgtBytes); err != nil {
return err
}
}
targetARN = bucketTarget.Arn
}
@@ -1181,6 +1185,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 {
@@ -1205,6 +1210,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,
}
@@ -1866,6 +1872,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,
@@ -2057,12 +2064,12 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
}
}
for _, s := range siteNames {
info, ok := peerMap[s]
pinfo, ok := peerMap[s]
if !ok {
return st, errSRConfigMissingError(errMissingSRConfig)
}
rmvEndpoints = append(rmvEndpoints, info.Endpoint)
delete(updatedPeers, info.DeploymentID)
rmvEndpoints = append(rmvEndpoints, pinfo.Endpoint)
delete(updatedPeers, pinfo.DeploymentID)
}
var wg sync.WaitGroup
errs := make(map[string]error, len(c.state.Peers))
@@ -2084,6 +2091,8 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
errs[pi.DeploymentID] = errSRPeerResp(fmt.Errorf("unable to create admin client for %s: %w", pi.Name, err))
return
}
// set the requesting site's deploymentID for verification of peer request
rreq.RequestingDepID = globalDeploymentID
if _, err = admClient.SRPeerRemove(ctx, rreq); err != nil {
if errors.Is(err, errMissingSRConfig) {
// ignore if peer is already removed.
@@ -2097,9 +2106,11 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
wg.Wait()
errdID := ""
selfTgtsDeleted := errs[globalDeploymentID] == nil // true if all remote targets and replication config cleared successfully on local cluster
for dID, err := range errs {
if err != nil {
if !rreq.RemoveAll {
if !rreq.RemoveAll && !selfTgtsDeleted {
return madmin.ReplicateRemoveStatus{
ErrDetail: err.Error(),
Status: madmin.ReplicateRemoveStatusPartial,
@@ -2143,12 +2154,17 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
return madmin.ReplicateRemoveStatus{
Status: madmin.ReplicateRemoveStatusPartial,
ErrDetail: fmt.Sprintf("unable to save cluster-replication state on local: %v", err),
}, nil
}, err
}
return madmin.ReplicateRemoveStatus{
st = madmin.ReplicateRemoveStatus{
Status: madmin.ReplicateRemoveStatusSuccess,
}, nil
}
if errs[errdID] != nil {
st.Status = madmin.ReplicateRemoveStatusPartial
st.ErrDetail = errs[errdID].Error()
}
return st, nil
}
// InternalRemoveReq - sends an unlink request to peer cluster to remove one or more sites
@@ -2157,6 +2173,19 @@ func (c *SiteReplicationSys) InternalRemoveReq(ctx context.Context, objectAPI Ob
if !c.isEnabled() {
return errSRNotEnabled
}
if rreq.RequestingDepID != "" {
// validate if requesting site is still part of site replication
var foundRequestor bool
for _, p := range c.state.Peers {
if p.DeploymentID == rreq.RequestingDepID {
foundRequestor = true
break
}
}
if !foundRequestor {
return errSRRequestorNotFound
}
}
ourName := ""
peerMap := make(map[string]madmin.PeerInfo)
@@ -3180,7 +3209,6 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
Bucket: bucket,
CreatedAt: bucketInfo.Created.UTC(),
DeletedAt: bucketInfo.Deleted.UTC(),
Location: globalSite.Region,
}
if !bucketExists {
info.Buckets[bucket] = bms
@@ -4251,11 +4279,6 @@ func (c *SiteReplicationSys) healBucket(ctx context.Context, objAPI ObjectLayer,
if isMakeBucket {
var opts MakeBucketOptions
optsMap := make(map[string]string)
if bStatus.Location != "" {
optsMap["location"] = bStatus.Location
opts.Location = bStatus.Location
}
optsMap["versioningEnabled"] = "true"
opts.VersioningEnabled = true
opts.CreatedAt = bStatus.CreatedAt
@@ -4686,6 +4709,7 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
Claims: claims,
SessionPolicy: json.RawMessage(policyJSON),
Status: creds.Status,
Comment: creds.Comment,
},
},
UpdatedAt: lastUpdate,
-3
View File
@@ -69,9 +69,6 @@ var errTooManyOpenFiles = StorageErr("too many open files, please increase 'ulim
// errFileNameTooLong - given file name is too long than supported length.
var errFileNameTooLong = StorageErr("file name too long")
// errFileNeedsHealing - given file name needs to heal.
var errFileNeedsHealing = StorageErr("file name needs healing")
// errVolumeExists - cannot create same volume again.
var errVolumeExists = StorageErr("volume already exists")
+2 -3
View File
@@ -66,7 +66,6 @@ import (
"github.com/minio/minio/internal/crypto"
"github.com/minio/minio/internal/hash"
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/rest"
"github.com/minio/pkg/bucket/policy"
)
@@ -112,7 +111,7 @@ func TestMain(m *testing.M) {
// Initialize globalConsoleSys system
globalConsoleSys = NewConsoleLogger(context.Background())
globalInternodeTransport = newInternodeHTTPTransport(nil, rest.DefaultTimeout)()
globalInternodeTransport = NewInternodeHTTPTransport()()
initHelp()
@@ -1548,7 +1547,7 @@ func initAPIHandlerTest(ctx context.Context, obj ObjectLayer, endpoints []string
bucketName := getRandomBucketName()
// Create bucket.
err := obj.MakeBucketWithLocation(context.Background(), bucketName, MakeBucketOptions{})
err := obj.MakeBucket(context.Background(), bucketName, MakeBucketOptions{})
if err != nil {
// failed to create newbucket, return err.
return "", nil, err
+1 -1
View File
@@ -181,7 +181,7 @@ func getHelmVersion(helmInfoFilePath string) string {
}
return ""
}
defer helmInfoFile.Close()
scanner := bufio.NewScanner(helmInfoFile)
for scanner.Scan() {
if strings.Contains(scanner.Text(), "chart=") {
+54 -153
View File
@@ -38,7 +38,6 @@ import (
"runtime/trace"
"strings"
"sync"
"syscall"
"time"
"github.com/coreos/go-oidc"
@@ -60,6 +59,7 @@ import (
"github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/logger/message/audit"
"github.com/minio/minio/internal/mcontext"
"github.com/minio/minio/internal/rest"
"github.com/minio/pkg/certs"
"github.com/minio/pkg/env"
xnet "github.com/minio/pkg/net"
@@ -568,163 +568,82 @@ func ToS3ETag(etag string) string {
return etag
}
func newInternodeHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() http.RoundTripper {
// For more details about various values used here refer
// https://golang.org/pkg/net/http/#Transport documentation
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
MaxIdleConnsPerHost: 1024,
WriteBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
ReadBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
IdleConnTimeout: 15 * time.Second,
ResponseHeaderTimeout: 15 * time.Minute, // Set conservative timeouts for MinIO internode.
TLSHandshakeTimeout: 15 * time.Second,
ExpectContinueTimeout: 15 * time.Second,
TLSClientConfig: tlsConfig,
// Go net/http automatically unzip if content-type is
// gzip disable this feature, as we are always interested
// in raw stream.
DisableCompression: true,
}
// https://github.com/golang/go/issues/23559
// https://github.com/golang/go/issues/42534
// https://github.com/golang/go/issues/43989
// https://github.com/golang/go/issues/33425
// https://github.com/golang/go/issues/29246
// if tlsConfig != nil {
// trhttp2, _ := http2.ConfigureTransports(tr)
// if trhttp2 != nil {
// // ReadIdleTimeout is the timeout after which a health check using ping
// // frame will be carried out if no frame is received on the
// // connection. 5 minutes is sufficient time for any idle connection.
// trhttp2.ReadIdleTimeout = 5 * time.Minute
// // PingTimeout is the timeout after which the connection will be closed
// // if a response to Ping is not received.
// trhttp2.PingTimeout = dialTimeout
// // DisableCompression, if true, prevents the Transport from
// // requesting compression with an "Accept-Encoding: gzip"
// trhttp2.DisableCompression = true
// }
// }
return func() http.RoundTripper {
return tr
// GetDefaultConnSettings returns default HTTP connection settings.
func GetDefaultConnSettings() xhttp.ConnSettings {
return xhttp.ConnSettings{
DNSCache: globalDNSCache,
DialTimeout: rest.DefaultTimeout,
RootCAs: globalRootCAs,
}
}
// Used by only proxied requests, specifically only supports HTTP/1.1
func newCustomHTTPProxyTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
// For more details about various values used here refer
// https://golang.org/pkg/net/http/#Transport documentation
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
MaxIdleConnsPerHost: 1024,
WriteBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
ReadBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
IdleConnTimeout: 15 * time.Second,
ResponseHeaderTimeout: 30 * time.Minute, // Set larger timeouts for proxied requests.
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
// Go net/http automatically unzip if content-type is
// gzip disable this feature, as we are always interested
// in raw stream.
DisableCompression: true,
}
return func() *http.Transport {
return tr
}
// NewInternodeHTTPTransport returns a transport for internode MinIO
// connections.
func NewInternodeHTTPTransport() func() http.RoundTripper {
return xhttp.ConnSettings{
DNSCache: globalDNSCache,
DialTimeout: rest.DefaultTimeout,
RootCAs: globalRootCAs,
CipherSuites: fips.TLSCiphers(),
CurvePreferences: fips.TLSCurveIDs(),
EnableHTTP2: false,
}.NewInternodeHTTPTransport()
}
func newCustomHTTPTransport(tlsConfig *tls.Config, dialTimeout time.Duration) func() *http.Transport {
// For more details about various values used here refer
// https://golang.org/pkg/net/http/#Transport documentation
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
MaxIdleConnsPerHost: 1024,
WriteBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
ReadBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
IdleConnTimeout: 15 * time.Second,
ResponseHeaderTimeout: 3 * time.Minute,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 10 * time.Second,
TLSClientConfig: tlsConfig,
// Go net/http automatically unzip if content-type is
// gzip disable this feature, as we are always interested
// in raw stream.
DisableCompression: true,
}
// https://github.com/golang/go/issues/23559
// https://github.com/golang/go/issues/42534
// https://github.com/golang/go/issues/43989
// https://github.com/golang/go/issues/33425
// https://github.com/golang/go/issues/29246
// if tlsConfig != nil {
// trhttp2, _ := http2.ConfigureTransports(tr)
// if trhttp2 != nil {
// // ReadIdleTimeout is the timeout after which a health check using ping
// // frame will be carried out if no frame is received on the
// // connection. 5 minutes is sufficient time for any idle connection.
// trhttp2.ReadIdleTimeout = 5 * time.Minute
// // PingTimeout is the timeout after which the connection will be closed
// // if a response to Ping is not received.
// trhttp2.PingTimeout = dialTimeout
// // DisableCompression, if true, prevents the Transport from
// // requesting compression with an "Accept-Encoding: gzip"
// trhttp2.DisableCompression = true
// }
// }
return func() *http.Transport {
return tr
}
// NewCustomHTTPProxyTransport is used only for proxied requests, specifically
// only supports HTTP/1.1
func NewCustomHTTPProxyTransport() func() *http.Transport {
return xhttp.ConnSettings{
DNSCache: globalDNSCache,
DialTimeout: rest.DefaultTimeout,
RootCAs: globalRootCAs,
CipherSuites: fips.TLSCiphers(),
CurvePreferences: fips.TLSCurveIDs(),
EnableHTTP2: false,
}.NewCustomHTTPProxyTransport()
}
// NewHTTPTransportWithClientCerts returns a new http configuration
// used while communicating with the cloud backends.
func NewHTTPTransportWithClientCerts(clientCert, clientKey string) *http.Transport {
transport := newHTTPTransport(1 * time.Minute)
s := xhttp.ConnSettings{
DNSCache: globalDNSCache,
DialTimeout: 1 * time.Minute,
RootCAs: globalRootCAs,
EnableHTTP2: false,
}
if clientCert != "" && clientKey != "" {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
c, err := certs.NewManager(ctx, clientCert, clientKey, tls.LoadX509KeyPair)
transport, err := s.NewHTTPTransportWithClientCerts(ctx, clientCert, clientKey)
if err != nil {
logger.LogIf(ctx, fmt.Errorf("failed to load client key and cert, please check your endpoint configuration: %s",
err.Error()))
}
if c != nil {
c.UpdateReloadDuration(10 * time.Second)
c.ReloadOnSignal(syscall.SIGHUP) // allow reloads upon SIGHUP
transport.TLSClientConfig.GetClientCertificate = c.GetClientCertificate
}
return transport
}
return transport
return s.NewHTTPTransportWithTimeout(1 * time.Minute)
}
// NewHTTPTransport returns a new http configuration
// used while communicating with the cloud backends.
func NewHTTPTransport() *http.Transport {
return newHTTPTransport(1 * time.Minute)
return NewHTTPTransportWithTimeout(1 * time.Minute)
}
// Default values for dial timeout
const defaultDialTimeout = 5 * time.Second
func newHTTPTransport(timeout time.Duration) *http.Transport {
tr := newCustomHTTPTransport(&tls.Config{
RootCAs: globalRootCAs,
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
}, defaultDialTimeout)()
// Customize response header timeout
tr.ResponseHeaderTimeout = timeout
return tr
// NewHTTPTransportWithTimeout allows setting a timeout.
func NewHTTPTransportWithTimeout(timeout time.Duration) *http.Transport {
return xhttp.ConnSettings{
DNSCache: globalDNSCache,
DialTimeout: defaultDialTimeout,
RootCAs: globalRootCAs,
EnableHTTP2: false,
}.NewHTTPTransportWithTimeout(timeout)
}
type dialContext func(ctx context.Context, network, addr string) (net.Conn, error)
@@ -753,29 +672,11 @@ func newCustomDialContext() dialContext {
// NewRemoteTargetHTTPTransport returns a new http configuration
// used while communicating with the remote replication targets.
func NewRemoteTargetHTTPTransport() func() *http.Transport {
// For more details about various values used here refer
// https://golang.org/pkg/net/http/#Transport documentation
tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: newCustomDialContext(),
MaxIdleConnsPerHost: 1024,
WriteBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
ReadBufferSize: 32 << 10, // 32KiB moving up from 4KiB default
IdleConnTimeout: 15 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ExpectContinueTimeout: 5 * time.Second,
TLSClientConfig: &tls.Config{
RootCAs: globalRootCAs,
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
},
// Go net/http automatically unzip if content-type is
// gzip disable this feature, as we are always interested
// in raw stream.
DisableCompression: true,
}
return func() *http.Transport {
return tr
}
return xhttp.ConnSettings{
DialContext: newCustomDialContext(),
RootCAs: globalRootCAs,
EnableHTTP2: false,
}.NewCustomHTTPProxyTransport()
}
// Load the json (typically from disk file).
+1 -1
View File
@@ -42,7 +42,7 @@ func newWarmBackendMinIO(conf madmin.TierMinIO) (*warmBackendMinIO, error) {
creds := credentials.NewStaticV4(conf.AccessKey, conf.SecretKey, "")
getRemoteTierTargetInstanceTransportOnce.Do(func() {
getRemoteTierTargetInstanceTransport = newHTTPTransport(10 * time.Minute)
getRemoteTierTargetInstanceTransport = NewHTTPTransportWithTimeout(10 * time.Minute)
})
opts := &minio.Options{
Creds: creds,
+1 -1
View File
@@ -117,7 +117,7 @@ func newWarmBackendS3(conf madmin.TierS3) (*warmBackendS3, error) {
creds = credentials.NewStaticV4(conf.AccessKey, conf.SecretKey, "")
}
getRemoteTierTargetInstanceTransportOnce.Do(func() {
getRemoteTierTargetInstanceTransport = newHTTPTransport(10 * time.Minute)
getRemoteTierTargetInstanceTransport = NewHTTPTransportWithTimeout(10 * time.Minute)
})
opts := &minio.Options{
Creds: creds,
+3 -2
View File
@@ -25,6 +25,7 @@ import (
"io"
"github.com/minio/madmin-go/v2"
xhttp "github.com/minio/minio/internal/http"
)
// WarmBackendGetOpts is used to express byte ranges within an object. The zero
@@ -60,7 +61,8 @@ func checkWarmBackend(ctx context.Context, w WarmBackend) error {
}
}
_, err = w.Get(ctx, probeObject, rv, WarmBackendGetOpts{})
r, err := w.Get(ctx, probeObject, rv, WarmBackendGetOpts{})
xhttp.DrainBody(r)
if err != nil {
switch err.(type) {
case BackendDown:
@@ -78,7 +80,6 @@ func checkWarmBackend(ctx context.Context, w WarmBackend) error {
}
}
}
if err = w.Remove(ctx, probeObject, rv); err != nil {
switch err.(type) {
case BackendDown:
+1 -1
View File
@@ -122,7 +122,7 @@ func getFileInfo(xlMetaBuf []byte, volume, path, versionID string, data bool) (F
}, nil
}
inData = xlMeta.data
fi, err = xlMeta.ToFileInfo(volume, path, versionID)
fi, err = xlMeta.ToFileInfo(volume, path, versionID, false)
}
if !data || err != nil {
return fi, err
+23 -1
View File
@@ -1700,7 +1700,7 @@ func (x *xlMetaV2) AddLegacy(m *xlMetaV1Object) error {
// ToFileInfo converts xlMetaV2 into a common FileInfo datastructure
// for consumption across callers.
func (x xlMetaV2) ToFileInfo(volume, path, versionID string) (fi FileInfo, err error) {
func (x xlMetaV2) ToFileInfo(volume, path, versionID string, inclFreeVers bool) (fi FileInfo, err error) {
var uv uuid.UUID
if versionID != "" && versionID != nullVersionID {
uv, err = uuid.Parse(versionID)
@@ -1712,12 +1712,29 @@ func (x xlMetaV2) ToFileInfo(volume, path, versionID string) (fi FileInfo, err e
var succModTime int64
isLatest := true
nonFreeVersions := len(x.versions)
var (
freeFi FileInfo
freeFound bool
)
found := false
for _, ver := range x.versions {
header := &ver.header
// skip listing free-version unless explicitly requested via versionID
if header.FreeVersion() {
nonFreeVersions--
// remember the latest free version; will return this FileInfo if no non-free version remain
var freeVersion xlMetaV2Version
if inclFreeVers && !freeFound {
// ignore unmarshalling errors, will return errFileNotFound in that case
if _, err := freeVersion.unmarshalV(x.metaV, ver.meta); err == nil {
if freeFi, err = freeVersion.ToFileInfo(volume, path); err == nil {
freeFi.IsLatest = true // when this is returned, it would be the latest free version remaining.
freeFound = true
}
}
}
if header.VersionID != uv {
continue
}
@@ -1749,6 +1766,11 @@ func (x xlMetaV2) ToFileInfo(volume, path, versionID string) (fi FileInfo, err e
}
if !found {
if versionID == "" {
if inclFreeVers && nonFreeVersions == 0 {
if freeFound {
return freeFi, nil
}
}
return FileInfo{}, errFileNotFound
}
+1 -1
View File
@@ -485,7 +485,7 @@ func BenchmarkXlMetaV2Shallow(b *testing.B) {
b.Fatal(err)
}
// List...
_, err = xl.ToFileInfo("volume", "path", ids[rng.Intn(size)])
_, err = xl.ToFileInfo("volume", "path", ids[rng.Intn(size)], false)
if err != nil {
b.Fatal(err)
}
+57
View File
@@ -18,6 +18,7 @@
package cmd
import (
"errors"
"testing"
"time"
@@ -128,6 +129,11 @@ func TestFreeVersion(t *testing.T) {
report()
fatalErr(err)
// At this point the version stack must look as below,
// v3 --> free version 00000000-0000-0000-0000-0000000000f2 (from removal of null version)
// v2 --> free version 00000000-0000-0000-0000-0000000000f1 (from overwriting of null version )
// v1 --> non-free version 00000000-0000-0000-0000-000000000001
// Check number of free-versions
freeVersions, err := xl.listFreeVersions(newtierfi.Volume, newtierfi.Name)
if err != nil {
@@ -137,6 +143,57 @@ func TestFreeVersion(t *testing.T) {
t.Fatalf("Expected two free versions but got %d", len(freeVersions))
}
freeVersionsTests := []struct {
vol string
name string
inclFreeVers bool
afterFn func(fi FileInfo) (string, error)
expectedFree bool
expectedErr error
}{
// ToFileInfo with 'inclFreeVers = true' should return the latest
// non-free version if one is present
{
vol: newtierfi.Volume,
name: newtierfi.Name,
inclFreeVers: true,
afterFn: xl.DeleteVersion,
expectedFree: false,
},
// ToFileInfo with 'inclFreeVers = true' must return the latest free
// version when no non-free versions are present.
{
vol: newtierfi.Volume,
name: newtierfi.Name,
inclFreeVers: true,
expectedFree: true,
},
// ToFileInfo with 'inclFreeVers = false' must return errFileNotFound
// when no non-free version exist.
{
vol: newtierfi.Volume,
name: newtierfi.Name,
inclFreeVers: false,
expectedErr: errFileNotFound,
},
}
for _, ft := range freeVersionsTests {
fi, err := xl.ToFileInfo(ft.vol, ft.name, "", ft.inclFreeVers)
if err != nil && !errors.Is(err, ft.expectedErr) {
t.Fatalf("ToFileInfo failed due to %v", err)
}
if got := fi.TierFreeVersion(); got != ft.expectedFree {
t.Fatalf("Expected free-version=%v but got free-version=%v", ft.expectedFree, got)
}
if ft.afterFn != nil {
_, err = ft.afterFn(fi)
if err != nil {
t.Fatalf("ft.afterFn failed with err %v", err)
}
}
}
// Simulate scanner removing free-version
freefi := newtierfi
for _, fvID := range fvIDs {
+4 -10
View File
@@ -1796,15 +1796,9 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
}
or := &xioutil.ODirectReader{
File: file,
SmallFile: false,
}
if length <= smallFileThreshold {
or = &xioutil.ODirectReader{
File: file,
SmallFile: true,
}
File: file,
// Select bigger blocks when reading at least 50% of a big block.
SmallFile: length <= xioutil.BlockSizeLarge/2,
}
r := struct {
@@ -2335,7 +2329,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
}
// Replace the data of null version or any other existing version-id
ofi, err := xlMeta.ToFileInfo(dstVolume, dstPath, reqVID)
ofi, err := xlMeta.ToFileInfo(dstVolume, dstPath, reqVID, false)
if err == nil && !ofi.Deleted {
if xlMeta.SharedDataDirCountStr(reqVID, ofi.DataDir) == 0 {
// Purge the destination path as we are not preserving anything
+19 -20
View File
@@ -18,14 +18,7 @@ To replicate objects in a bucket to a destination bucket on a target site either
Ensure that versioning is enabled on the source and target buckets with `mc version` command. If object locking is required, the buckets should have been created with `mc mb --with-lock`
Create a replication target on the source cluster as shown below:
```
mc admin bucket remote add myminio/srcbucket https://accessKey:secretKey@replica-endpoint:9000/destbucket --service replication --region us-east-1
Remote ARN = 'arn:minio:replication:us-east-1:c5be6b16-769d-432a-9ef1-4567081f3566:destbucket'
```
> The user running the above command needs *s3:GetReplicationConfiguration* and *s3:GetBucketVersioning* permission on the source cluster. We do not recommend running root credentials/super admin with replication, instead create a dedicated user. The access credentials used at the destination requires *s3:ReplicateObject* permission.
The user setting up replication needs *s3:GetReplicationConfiguration* and *s3:GetBucketVersioning* permission on the source cluster. We do not recommend running root credentials/super admin with replication, instead create a dedicated user. The access credentials used at the destination requires *s3:ReplicateObject* permission.
The following minimal permission policy is needed by admin user setting up replication on the `source`:
@@ -103,14 +96,24 @@ The access key provided for the replication *target* cluster should have these m
Please note that the permissions required by the admin user on the target cluster can be more fine grained to exclude permissions like "s3:ReplicateDelete", "s3:GetBucketObjectLockConfiguration" etc depending on whether delete replication rules are set up or if object locking is disabled on `destbucket`. The above policies assume that replication of objects, tags and delete marker replication are all enabled on object lock enabled buckets. A sample script to setup replication is provided [here](https://github.com/minio/minio/blob/master/docs/bucket/replication/setup_replication.sh)
Once successfully created and authorized, the `mc admin bucket remote add` command generates a replication target ARN. This command lists all the currently authorized replication targets:
To set up replication from a source bucket `srcbucket` on myminio cluster to a bucket `destbucket` on the target minio cluster with endpoint https://replica-endpoint:9000, use:
```
mc replicate add myminio/srcbucket --priority 1 --remote-bucket https://accessKey:secretKey@replica-endpoint:9000/destbucket
Replication configuration applied successfully to myminio/srcbucket.
```
Internally, this creates an ARN for the remote target associating the remote bucket as a replication target to the srcbucket on myminio.By default, if --replicate flag is not specified, replication of delete marker, permanent deletes, existing object replication and replica modification sync are all enabled. If you are using older mc versions, the ARN needs to be generated as a separate step before adding a replication rule.
> NOTE: If you are using a mc version below `RELEASE.2022-12-24T15-21-38Z`, the --remote-bucket flag needs an ARN generated by `mc admin bucket remote add` command. For mc versions RELEASE.2021-09-02T09-21-27Z and older, the remote target ARN needs to be passed in the --arn flag and actual remote bucket name in --remote-bucket flag of `mc replicate add`. For example, in older releases of mc replication configuration used to be added with:
```
mc admin bucket remote ls myminio/srcbucket --service "replication"
mc admin bucket remote add myminio/srcbucket https://accessKey:secretKey@replica-endpoint:9000/destbucket --service replication --region us-east-1
Remote ARN = 'arn:minio:replication:us-east-1:c5be6b16-769d-432a-9ef1-4567081f3566:destbucket'
mc replicate add myminio/srcbucket/Tax --priority 1 --remote-bucket destbucket --remote-bucket "arn:minio:replication:us-east-1:c5be6b16-769d-432a-9ef1-4567081f3566:destbucket" --tags "Year=2019&Company=AcmeCorp" --storage-class "STANDARD" --replicate "delete,delete-marker"
Replication configuration applied successfully to myminio/srcbucket.
```
The replication configuration can now be added to the source bucket by applying the json file with replication configuration. The Remote ARN above is passed in as a json element in the configuration.
The replication configuration generated has the following format and can be exported with `mc replicate export` command:
```json
{
@@ -197,18 +200,13 @@ To add a replication rule allowing both delete marker replication, versioned del
Additional permission of "s3:ReplicateDelete" action would need to be specified on the access key configured for the target cluster if Delete Marker replication or versioned delete replication is enabled.
```
mc replicate add myminio/srcbucket/Tax --priority 1 --remote-bucket "arn:minio:replication:us-east-1:c5be6b16-769d-432a-9ef1-4567081f3566:destbucket" --tags "Year=2019&Company=AcmeCorp" --storage-class "STANDARD" --replicate "delete,delete-marker"
mc replicate add myminio/srcbucket/Tax --priority 1 --remote-bucket `remote-target` --tags "Year=2019&Company=AcmeCorp" --storage-class "STANDARD" --replicate "delete,delete-marker"
Replication configuration applied successfully to myminio/srcbucket.
```
> NOTE: In mc versions RELEASE.2021-09-02T09-21-27Z and older, the remote target ARN needs to be passed in the --arn flag and actual remote bucket name in --remote-bucket flag of `mc replicate add`. For example, with the ARN above the replication configuration used to be added with
> NOTE: In mc versions `RELEASE.2022-12-24T15-21-38Z` and above `remote-target` should be of the format `https://accessKey:secretKey@replica-endpoint:9000/destbucket` which earlier used to be set during `mc admin bucket remote add`. For older releases, use the arn generated with `mc admin bucket remote add` command - e.g."arn:minio:replication:us-east-1:c5be6b16-769d-432a-9ef1-4567081f3566:destbucket" as the `remote-target`.
```
mc replicate add myminio/srcbucket/Tax --priority 1 --remote-bucket destbucket --arn "arn:minio:replication:us-east-1:c5be6b16-769d-432a-9ef1-4567081f3566:destbucket" --tags "Year=2019&Company=AcmeCorp" --storage-class "STANDARD" --replicate "delete,delete-marker"
Replication configuration applied successfully to myminio/srcbucket.
```
Also note that for `mc` version `RELEASE.2021-09-02T09-21-27Z` or older supports only a single remote target per bucket. To take advantage of multiple destination replication, use the latest version of `mc`
Also note that `mc` version `RELEASE.2021-09-02T09-21-27Z` or older supports only a single remote target per bucket. To take advantage of multiple destination replication, use the latest version of `mc`
Status of delete marker replication can be viewed by doing a GET/HEAD on the object version - it will return a `X-Minio-Replication-DeleteMarker-Status` header and http response code of `405`. In the case of permanent deletes, if the delete replication is pending or failed to propagate to the target cluster, GET/HEAD will return additional `X-Minio-Replication-Delete-Status` header and a http response code of `405`.
@@ -221,7 +219,8 @@ On the target bucket, `s3:PutObject` event shows `X-Amz-Replication-Status` stat
### Sync/Async Replication
By default, replication is completed asynchronously. If synchronous replication is desired, set the --sync flag while adding a
remote replication target using the `mc admin bucket remote add` command
remote replication target using the `mc admin bucket remote add` command. For mc releases on or after `RELEASE.2022-12-24T15-21-38Z`, the
--sync, --health-check and --bandwidth flags can be specified in `mc replicate add|update` command
```
mc admin bucket remote add myminio/srcbucket https://accessKey:secretKey@replica-endpoint:9000/destbucket --service replication --region us-east-1 --sync --healthcheck-seconds 100
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env bash
if [ -n "$TEST_DEBUG" ]; then
set -x
fi
trap 'catch $LINENO' ERR
# shellcheck disable=SC2120
catch() {
if [ $# -ne 0 ]; then
echo "error on line $1"
echo "dc1 server logs ========="
cat /tmp/dc1.log
echo "dc2 server logs ========="
cat /tmp/dc2.log
fi
echo "Cleaning up instances of MinIO"
set +e
pkill minio
pkill mc
rm -rf /tmp/xl/
}
catch
set -e
export MINIO_CI_CD=1
export MINIO_BROWSER=off
export MINIO_ROOT_USER="minio"
export MINIO_ROOT_PASSWORD="minio123"
export MINIO_KMS_AUTO_ENCRYPTION=off
export MINIO_PROMETHEUS_AUTH_TYPE=public
export MINIO_KMS_SECRET_KEY=my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw=
unset MINIO_KMS_KES_CERT_FILE
unset MINIO_KMS_KES_KEY_FILE
unset MINIO_KMS_KES_ENDPOINT
unset MINIO_KMS_KES_KEY_NAME
if [ ! -f ./mc ]; then
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc && \
chmod +x mc
fi
mkdir -p /tmp/xl/1/ /tmp/xl/2/
export MINIO_KMS_SECRET_KEY="my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
export MINIO_ROOT_USER="minioadmin"
export MINIO_ROOT_PASSWORD="minioadmin"
./minio server --address ":9001" /tmp/xl/1/{1...4}/ 2>&1 > /tmp/dc1.log &
./minio server --address ":9002" /tmp/xl/2/{1...4}/ 2>&1 > /tmp/dc2.log &
sleep 3
export MC_HOST_myminio1=http://minioadmin:minioadmin@localhost:9001
export MC_HOST_myminio2=http://minioadmin:minioadmin@localhost:9002
./mc mb myminio1/testbucket/
./mc version enable myminio1/testbucket/
./mc mb myminio2/testbucket/
./mc version enable myminio2/testbucket/
./mc replicate add myminio1/testbucket --remote-bucket http://minioadmin:minioadmin@localhost:9002/testbucket/ --priority 1
./mc cp README.md myminio1/testbucket/dir/file
./mc cp README.md myminio1/testbucket/dir/file
sleep 1s
echo "=== myminio1"
./mc ls --versions myminio1/testbucket/dir/file
echo "=== myminio2"
./mc ls --versions myminio2/testbucket/dir/file
versionId="$(mc ls --json --versions myminio1/testbucket/dir/ | tail -n1 | jq -r .versionId)"
aws s3api --endpoint-url http://localhost:9001 --profile minio delete-object --bucket testbucket --key dir/file --version-id "$versionId"
./mc ls -r --versions myminio1/testbucket > /tmp/myminio1.txt
./mc ls -r --versions myminio2/testbucket > /tmp/myminio2.txt
out=$(diff -qpruN /tmp/myminio1.txt /tmp/myminio2.txt)
ret=$?
if [ $ret -ne 0 ]; then
echo "BUG: expected no missing entries after replication: $out"
exit 1
fi
./mc rm myminio1/testbucket/dir/file
sleep 1s
./mc ls -r --versions myminio1/testbucket > /tmp/myminio1.txt
./mc ls -r --versions myminio2/testbucket > /tmp/myminio2.txt
out=$(diff -qpruN /tmp/myminio1.txt /tmp/myminio2.txt)
ret=$?
if [ $ret -ne 0 ]; then
echo "BUG: expected no missing entries after replication: $out"
exit 1
fi
echo "Success"
catch
@@ -41,7 +41,7 @@ unset MINIO_KMS_KES_KEY_NAME
if [ ! -f ./mc ]; then
wget --quiet -O mc https://dl.minio.io/client/mc/release/linux-amd64/mc && \
chmod +x mc
chmod +x mc
fi
minio server --address 127.0.0.1:9001 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
@@ -76,14 +76,11 @@ done
./mc mb siteb/bucket/
./mc version enable siteb/bucket/
echo "adding replication config for site a -> site b"
remote_arn=$(./mc admin bucket remote add sitea/bucket/ \
http://minio:minio123@127.0.0.1:9004/bucket \
--service "replication" --json | jq -r ".RemoteARN")
echo "adding replication rule for a -> b : ${remote_arn}"
echo "adding replication rule for site a -> site b"
./mc replicate add sitea/bucket/ \
--remote-bucket "${remote_arn}"
--remote-bucket http://minio:minio123@127.0.0.1:9004/bucket
remote_arn=$(./mc replicate ls sitea/bucket --json | jq -r .rule.Destination.Bucket)
sleep 1
./mc replicate resync start sitea/bucket/ --remote-bucket "${remote_arn}"
@@ -80,127 +80,76 @@ export MC_HOST_sitec=http://minio:minio123@127.0.0.1:9006
./mc version enable sitec/bucket/
./mc mb -l sitec/olockbucket
echo "adding replication config for site a -> site b"
remote_arn=$(./mc admin bucket remote add sitea/bucket/ \
http://minio:minio123@127.0.0.1:9004/bucket \
--service "replication" --json | jq -r ".RemoteARN")
echo "adding replication rule for a -> b : ${remote_arn}"
sleep 1
./mc replicate add sitea/bucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9004/bucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync"
sleep 1
echo "adding replication config for site b -> site a"
remote_arn=$(./mc admin bucket remote add siteb/bucket/ \
http://minio:minio123@127.0.0.1:9001/bucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for b -> a : ${remote_arn}"
./mc replicate add siteb/bucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9001/bucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync"
sleep 1
echo "adding replication config for site a -> site c"
remote_arn=$(./mc admin bucket remote add sitea/bucket/ \
http://minio:minio123@127.0.0.1:9006/bucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for a -> c : ${remote_arn}"
./mc replicate add sitea/bucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9006/bucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 2
sleep 1
echo "adding replication config for site c -> site a"
remote_arn=$(./mc admin bucket remote add sitec/bucket/ \
http://minio:minio123@127.0.0.1:9001/bucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for c -> a : ${remote_arn}"
./mc replicate add sitec/bucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9001/bucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 2
sleep 1
echo "adding replication config for site b -> site c"
remote_arn=$(./mc admin bucket remote add siteb/bucket/ \
http://minio:minio123@127.0.0.1:9006/bucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for b -> c : ${remote_arn}"
./mc replicate add siteb/bucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9006/bucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 3
sleep 1
echo "adding replication config for site c -> site b"
remote_arn=$(././mc admin bucket remote add sitec/bucket \
http://minio:minio123@127.0.0.1:9004/bucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for c -> b : ${remote_arn}"
./mc replicate add sitec/bucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9004/bucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 3
sleep 1
echo "adding replication config for olockbucket site a -> site b"
remote_arn=$(././mc admin bucket remote add sitea/olockbucket/ \
http://minio:minio123@127.0.0.1:9004/olockbucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for olockbucket a -> b : ${remote_arn}"
./mc replicate add sitea/olockbucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9004/olockbucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync"
sleep 1
echo "adding replication config for site b -> site a"
remote_arn=$(././mc admin bucket remote add siteb/olockbucket/ \
http://minio:minio123@127.0.0.1:9001/olockbucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for olockbucket b -> a : ${remote_arn}"
./mc replicate add siteb/olockbucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9001/olockbucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync"
sleep 1
echo "adding replication config for olockbucket site a -> site c"
remote_arn=$(././mc admin bucket remote add sitea/olockbucket/ \
http://minio:minio123@127.0.0.1:9006/olockbucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for olockbucket a -> c : ${remote_arn}"
./mc replicate add sitea/olockbucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9006/olockbucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 2
sleep 1
echo "adding replication config for site c -> site a"
remote_arn=$(././mc admin bucket remote add sitec/olockbucket/ \
http://minio:minio123@127.0.0.1:9001/olockbucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for olockbucket c -> a : ${remote_arn}"
./mc replicate add sitec/olockbucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9001/olockbucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 2
sleep 1
echo "adding replication config for site b -> site c"
remote_arn=$(././mc admin bucket remote add siteb/olockbucket/ \
http://minio:minio123@127.0.0.1:9006/olockbucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for olockbucket b -> c : ${remote_arn}"
./mc replicate add siteb/olockbucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9006/olockbucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 3
sleep 1
echo "adding replication config for site c -> site b"
remote_arn=$(././mc admin bucket remote add sitec/olockbucket \
http://minio:minio123@127.0.0.1:9004/olockbucket \
--service "replication" --json | jq -r ".RemoteARN")
sleep 1
echo "adding replication rule for olockbucket c -> b : ${remote_arn}"
./mc replicate add sitec/olockbucket/ \
--remote-bucket "${remote_arn}" \
--remote-bucket http://minio:minio123@127.0.0.1:9004/olockbucket \
--replicate "existing-objects,delete,delete-marker,replica-metadata-sync" --priority 3
sleep 1
+2 -6
View File
@@ -96,10 +96,6 @@ cat ./replpolicy.json
# assign this replication policy to repluser
mc admin policy set dest replpolicy user=repluser
# define remote target for replication from source/bucket -> dest/bucket
remote_arn=$(mc admin bucket remote add repladminAlias/bucket http://repluser:repluser123@localhost:9000/bucket --service replication --json | jq -r ".RemoteARN")
echo "Now, use this ARN to add replication rules using 'mc replicate add' command"
# use arn returned by above command to create a replication policy on the source/bucket with `mc replicate add`
mc replicate add source/bucket --priority 1 --remote-bucket "${remote_arn}" \
# configure replication config to remote bucket at http://localhost:9000
mc replicate add source/bucket --priority 1 --remote-bucket http://repluser:repluser123@localhost:9000/bucket \
--replicate existing-objects,delete,delete-marker,replica-metadata-sync

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