mirror of
https://github.com/pgsty/minio.git
synced 2026-11-07 08:12:03 +03:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb577835d9 | |||
| 7f35f74f14 | |||
| 3d6194e93c | |||
| 72c7845f7e | |||
| 1c99597a06 | |||
| feb9d8480b | |||
| 4e670458b8 | |||
| 48deccdc40 | |||
| 2eee744e34 | |||
| 3f72439b8a | |||
| 468a9fae83 | |||
| d87f91720b | |||
| aa0eec16ab | |||
| d63e603040 | |||
| 8222a640ac | |||
| 7e45d84ace | |||
| 139a606f0a | |||
| 289223b6de | |||
| c61dd16a1e | |||
| 3e38fa54a5 | |||
| 4a02189ba0 | |||
| 3d4fc28ec9 | |||
| ec3a3bb10d | |||
| 364d3a0ac9 | |||
| cb536a73eb | |||
| 428155add9 | |||
| 8bce123bba | |||
| 0a56dbde2f | |||
| 7ff4164d65 | |||
| 53a14c7301 | |||
| dc45a5010d | |||
| 4b9192034c | |||
| deeadd1a37 | |||
| 1fc4203c19 | |||
| 15b930be1f | |||
| 7fd76dbbb7 | |||
| da81c6cc27 | |||
| a03dac41eb | |||
| b657ffa496 | |||
| 55778ae278 | |||
| d990661d1f | |||
| 280526caf7 | |||
| 1173b26fc8 | |||
| 383489d5d9 | |||
| 9370b11684 | |||
| 999bbd3a14 | |||
| 235edd88aa | |||
| b5e074e54c | |||
| 4d7068931a | |||
| d7fb6fddf6 | |||
| 7213bd7131 | |||
| d4aac7cd72 | |||
| 741de4cf94 | |||
| f168ef9989 | |||
| a0de56abb6 | |||
| c201d8bda9 | |||
| d2373d5d6c |
@@ -9,6 +9,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v1
|
||||
uses: actions/dependency-review-action@v4
|
||||
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
go-version: [1.21.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
go-version: [1.21.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
go-version: [1.21.x]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -24,8 +24,8 @@ jobs:
|
||||
go-version: [1.21.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
|
||||
@@ -76,8 +76,8 @@ jobs:
|
||||
openid: "http://127.0.0.1:5556/dex"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -25,10 +25,10 @@ jobs:
|
||||
sudo -S rm -rf ${GITHUB_WORKSPACE}
|
||||
mkdir ${GITHUB_WORKSPACE}
|
||||
- name: checkout-step
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: setup-go-step
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.x
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ jobs:
|
||||
go-version: [1.21.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -25,8 +25,8 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
name: runner / shfmt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: luizm/action-sh-checker@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -25,8 +25,8 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
|
||||
@@ -17,9 +17,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.8
|
||||
check-latest: true
|
||||
|
||||
@@ -21,6 +21,11 @@ RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go
|
||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||
chmod +x /go/bin/mc
|
||||
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||
chmod +x /go/bin/curl; \
|
||||
fi
|
||||
|
||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||
@@ -49,6 +54,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||
COPY --from=build /go/bin/cur* /usr/bin/
|
||||
|
||||
COPY CREDITS /licenses/CREDITS
|
||||
COPY LICENSE /licenses/LICENSE
|
||||
|
||||
@@ -21,6 +21,11 @@ RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go
|
||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||
chmod +x /go/bin/mc
|
||||
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||
chmod +x /go/bin/curl; \
|
||||
fi
|
||||
|
||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||
@@ -49,6 +54,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||
COPY --from=build /go/bin/cur* /usr/bin/
|
||||
|
||||
COPY CREDITS /licenses/CREDITS
|
||||
COPY LICENSE /licenses/LICENSE
|
||||
|
||||
@@ -16,6 +16,11 @@ RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archiv
|
||||
curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips.minisig -o /go/bin/minio.minisig && \
|
||||
chmod +x /go/bin/minio
|
||||
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||
chmod +x /go/bin/curl; \
|
||||
fi
|
||||
|
||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||
|
||||
@@ -41,6 +46,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||
COPY --from=build /go/bin/cur* /usr/bin/
|
||||
|
||||
COPY CREDITS /licenses/CREDITS
|
||||
COPY LICENSE /licenses/LICENSE
|
||||
|
||||
@@ -21,6 +21,11 @@ RUN curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc -o /go
|
||||
curl -s -q https://dl.min.io/client/mc/release/linux-${TARGETARCH}/mc.minisig -o /go/bin/mc.minisig && \
|
||||
chmod +x /go/bin/mc
|
||||
|
||||
RUN if [ "$TARGETARCH" = "amd64" ]; then \
|
||||
curl -L -s -q https://github.com/moparisthebest/static-curl/releases/latest/download/curl-${TARGETARCH} -o /go/bin/curl; \
|
||||
chmod +x /go/bin/curl; \
|
||||
fi
|
||||
|
||||
# Verify binary signature using public key "RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGavRUN"
|
||||
RUN minisign -Vqm /go/bin/minio -x /go/bin/minio.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav && \
|
||||
minisign -Vqm /go/bin/mc -x /go/bin/mc.minisig -P RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav
|
||||
@@ -49,6 +54,7 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
COPY --from=build /go/bin/minio /usr/bin/minio
|
||||
COPY --from=build /go/bin/mc /usr/bin/mc
|
||||
COPY --from=build /go/bin/cur* /usr/bin/
|
||||
|
||||
COPY CREDITS /licenses/CREDITS
|
||||
COPY LICENSE /licenses/LICENSE
|
||||
|
||||
@@ -24,7 +24,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 $(GOLANGCI_DIR)
|
||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@6ac204f0b4d48d17ab4fa442134c7fba13127a4e
|
||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.10-0.20240227114326-6d6f813fff1b
|
||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||
|
||||
crosscompile: ## cross compile minio
|
||||
@@ -107,6 +107,12 @@ test-site-replication-oidc: install-race ## verify automatic site replication
|
||||
test-site-replication-minio: install-race ## verify automatic site replication
|
||||
@echo "Running tests for automatic site replication of IAM (with MinIO IDP)"
|
||||
@(env bash $(PWD)/docs/site-replication/run-multi-site-minio-idp.sh)
|
||||
@echo "Running tests for automatic site replication of SSE-C objects"
|
||||
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication.sh)
|
||||
@echo "Running tests for automatic site replication of SSE-C objects with SSE-KMS enabled for bucket"
|
||||
@(env bash $(PWD)/docs/site-replication/run-sse-kms-object-replication.sh)
|
||||
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
|
||||
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
|
||||
|
||||
verify: ## verify minio various setups
|
||||
@echo "Verifying build with race"
|
||||
@@ -147,9 +153,9 @@ hotfix-vars:
|
||||
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
|
||||
|
||||
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
|
||||
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.1/pkger_2.2.1_linux_amd64.deb
|
||||
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.9/pkger_2.2.9_linux_amd64.deb
|
||||
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
|
||||
@sudo apt install ./pkger_2.2.1_linux_amd64.deb --yes
|
||||
@sudo apt install ./pkger_2.2.9_linux_amd64.deb --yes
|
||||
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
|
||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
|
||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
|
||||
|
||||
@@ -193,6 +193,7 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
||||
// fail if ldap is not enabled
|
||||
if !globalIAMSys.LDAPConfig.Enabled() {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errors.New("LDAP not enabled")), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the user for the request sender (as it may be sent via a service
|
||||
@@ -232,12 +233,12 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
|
||||
targetGroups = requestorGroups
|
||||
|
||||
// Deny if the target user is not LDAP
|
||||
isLDAP, err := globalIAMSys.LDAPConfig.DoesUsernameExist(targetUser)
|
||||
foundLDAPDN, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(targetUser)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if isLDAP == "" {
|
||||
if foundLDAPDN == "" {
|
||||
err := errors.New("Specified user does not exist on LDAP server")
|
||||
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
||||
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
||||
@@ -373,7 +374,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
}
|
||||
|
||||
targetAccount, err := globalIAMSys.LDAPConfig.DoesUsernameExist(userDN)
|
||||
targetAccount, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
+26
-26
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
"github.com/minio/pkg/v2/policy"
|
||||
)
|
||||
|
||||
@@ -106,21 +108,12 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
||||
poolIndices = append(poolIndices, idx)
|
||||
}
|
||||
|
||||
if len(poolIndices) > 0 && !globalEndpoints[poolIndices[0]].Endpoints[0].IsLocal {
|
||||
ep := globalEndpoints[poolIndices[0]].Endpoints[0]
|
||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||
if proxyEp.Endpoint.Host == ep.Host {
|
||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(poolIndices) == 0 || !proxyDecommissionRequest(ctx, globalEndpoints[poolIndices[0]].Endpoints[0], w, r) {
|
||||
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := z.Decommission(r.Context(), poolIndices...); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -162,20 +155,12 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
if ep := globalEndpoints[idx].Endpoints[0]; !ep.IsLocal {
|
||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||
if proxyEp.Endpoint.Host == ep.Host {
|
||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !proxyDecommissionRequest(ctx, globalEndpoints[idx].Endpoints[0], w, r) {
|
||||
if err := pools.DecommissionCancel(ctx, idx); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := pools.DecommissionCancel(ctx, idx); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -391,3 +376,18 @@ func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request)
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
logger.LogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
|
||||
}
|
||||
|
||||
func proxyDecommissionRequest(ctx context.Context, defaultEndPoint Endpoint, w http.ResponseWriter, r *http.Request) (proxy bool) {
|
||||
host := env.Get("_MINIO_DECOM_ENDPOINT_HOST", defaultEndPoint.Host)
|
||||
if host == "" {
|
||||
return
|
||||
}
|
||||
for nodeIdx, proxyEp := range globalProxyEndpoints {
|
||||
if proxyEp.Endpoint.Host == host && !proxyEp.IsLocal {
|
||||
if proxyRequestByNodeIndex(ctx, w, r, nodeIdx) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/v2/policy"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
// RemoveUser - DELETE /minio/admin/v3/remove-user?accessKey=<access_key>
|
||||
@@ -1936,13 +1937,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
case userPolicyMappingsFile:
|
||||
userPolicyMap := make(map[string]MappedPolicy)
|
||||
userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
userPolData, err := json.Marshal(userPolicyMap)
|
||||
userPolData, err := json.Marshal(mappedPoliciesToMap(userPolicyMap))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
@@ -1953,13 +1954,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
case groupPolicyMappingsFile:
|
||||
groupPolicyMap := make(map[string]MappedPolicy)
|
||||
groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
err := globalIAMSys.store.loadMappedPolicies(ctx, regUser, true, groupPolicyMap)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
grpPolData, err := json.Marshal(groupPolicyMap)
|
||||
grpPolData, err := json.Marshal(mappedPoliciesToMap(groupPolicyMap))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
@@ -1970,13 +1971,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
case stsUserPolicyMappingsFile:
|
||||
userPolicyMap := make(map[string]MappedPolicy)
|
||||
userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, userPolicyMap)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
userPolData, err := json.Marshal(userPolicyMap)
|
||||
userPolData, err := json.Marshal(mappedPoliciesToMap(userPolicyMap))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
@@ -1986,13 +1987,13 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
case stsGroupPolicyMappingsFile:
|
||||
groupPolicyMap := make(map[string]MappedPolicy)
|
||||
groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
err := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
}
|
||||
grpPolData, err := json.Marshal(groupPolicyMap)
|
||||
grpPolData, err := json.Marshal(mappedPoliciesToMap(groupPolicyMap))
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, exportError(ctx, err, iamFile, ""), r.URL)
|
||||
return
|
||||
|
||||
+3
-2
@@ -427,8 +427,9 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
|
||||
default:
|
||||
}
|
||||
stopFn := globalBatchJobsMetrics.trace(batchJobMetricExpire, ri.JobID, attempts)
|
||||
_, err := api.DeleteObject(ctx, exp.Bucket, exp.Name, ObjectOptions{
|
||||
DeletePrefix: true,
|
||||
_, err := api.DeleteObject(ctx, exp.Bucket, encodeDirObject(exp.Name), ObjectOptions{
|
||||
DeletePrefix: true,
|
||||
DeletePrefixObject: true, // use prefix delete on exact object (this is an optimization to avoid fan-out calls)
|
||||
})
|
||||
if err != nil {
|
||||
stopFn(exp, err)
|
||||
|
||||
+21
-31
@@ -277,7 +277,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
}
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
isTags := len(r.Flags.Filter.Tags) != 0
|
||||
hasTags := len(r.Flags.Filter.Tags) != 0
|
||||
isMetadata := len(r.Flags.Filter.Metadata) != 0
|
||||
isStorageClassOnly := len(r.Flags.Filter.Metadata) == 1 && strings.EqualFold(r.Flags.Filter.Metadata[0].Key, xhttp.AmzStorageClass)
|
||||
|
||||
@@ -302,7 +302,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
return true
|
||||
}
|
||||
|
||||
if isTags {
|
||||
if hasTags {
|
||||
// Only parse object tags if tags filter is specified.
|
||||
tagMap := map[string]string{}
|
||||
tagStr := oi.UserTags
|
||||
@@ -407,7 +407,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
|
||||
continue
|
||||
}
|
||||
}
|
||||
if isTags {
|
||||
if hasTags {
|
||||
tags, err := c.GetObjectTagging(ctx, r.Source.Bucket, obj.Key, minio.GetObjectTaggingOptions{})
|
||||
if err == nil {
|
||||
oi.UserTags = tags.String()
|
||||
@@ -1064,8 +1064,6 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
|
||||
if !*r.Source.Snowball.Disable && r.Source.Type.isMinio() && r.Target.Type.isMinio() {
|
||||
go func() {
|
||||
defer xioutil.SafeClose(slowCh)
|
||||
|
||||
// Snowball currently needs the high level minio-go Client, not the Core one
|
||||
cl, err := miniogo.New(u.Host, &miniogo.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
@@ -1081,31 +1079,8 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
// Already validated before arriving here
|
||||
smallerThan, _ := humanize.ParseBytes(*r.Source.Snowball.SmallerThan)
|
||||
|
||||
var (
|
||||
obj = ObjectInfo{}
|
||||
batch = make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||
valid = true
|
||||
)
|
||||
|
||||
for valid {
|
||||
obj, valid = <-walkCh
|
||||
|
||||
if !valid {
|
||||
goto write
|
||||
}
|
||||
|
||||
if obj.DeleteMarker || !obj.VersionPurgeStatus.Empty() || obj.Size >= int64(smallerThan) {
|
||||
slowCh <- obj
|
||||
continue
|
||||
}
|
||||
|
||||
batch = append(batch, obj)
|
||||
|
||||
if len(batch) < *r.Source.Snowball.Batch {
|
||||
continue
|
||||
}
|
||||
|
||||
write:
|
||||
batch := make([]ObjectInfo, 0, *r.Source.Snowball.Batch)
|
||||
writeFn := func(batch []ObjectInfo) {
|
||||
if len(batch) > 0 {
|
||||
if err := r.writeAsArchive(ctx, api, cl, batch); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
@@ -1118,9 +1093,24 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
// persist in-memory state to disk after every 10secs.
|
||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||
}
|
||||
batch = batch[:0]
|
||||
}
|
||||
}
|
||||
for obj := range walkCh {
|
||||
if obj.DeleteMarker || !obj.VersionPurgeStatus.Empty() || obj.Size >= int64(smallerThan) {
|
||||
slowCh <- obj
|
||||
continue
|
||||
}
|
||||
|
||||
batch = append(batch, obj)
|
||||
|
||||
if len(batch) < *r.Source.Snowball.Batch {
|
||||
continue
|
||||
}
|
||||
writeFn(batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
writeFn(batch)
|
||||
xioutil.SafeClose(slowCh)
|
||||
}()
|
||||
} else {
|
||||
slowCh = walkCh
|
||||
|
||||
+2
-2
@@ -274,7 +274,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
|
||||
rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
skip := func(info FileInfo) (ok bool) {
|
||||
selectObj := func(info FileInfo) (ok bool) {
|
||||
if r.Flags.Filter.OlderThan > 0 && time.Since(info.ModTime) < r.Flags.Filter.OlderThan {
|
||||
// skip all objects that are newer than specified older duration
|
||||
return false
|
||||
@@ -360,7 +360,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
results := make(chan ObjectInfo, 100)
|
||||
if err := api.Walk(ctx, r.Bucket, r.Prefix, results, WalkOptions{
|
||||
Marker: lastObject,
|
||||
Filter: skip,
|
||||
Filter: selectObj,
|
||||
}); err != nil {
|
||||
cancel()
|
||||
// Do not need to retry if we can't list objects on source.
|
||||
|
||||
@@ -1218,11 +1218,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
reader io.Reader
|
||||
keyID string
|
||||
|
||||
@@ -233,6 +233,10 @@ func (es *expiryState) enqueueByDays(oi ObjectInfo, event lifecycle.Event, src l
|
||||
// enqueueByNewerNoncurrent enqueues object versions expired by
|
||||
// NewerNoncurrentVersions limit for expiry.
|
||||
func (es *expiryState) enqueueByNewerNoncurrent(bucket string, versions []ObjectToDelete, lcEvent lifecycle.Event) {
|
||||
if len(versions) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
task := newerNoncurrentTask{bucket: bucket, versions: versions, event: lcEvent}
|
||||
wrkr := es.getWorkerCh(task.OpHash())
|
||||
if wrkr == nil {
|
||||
|
||||
@@ -584,6 +584,7 @@ func (z *InQueueStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z InQueueStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 0
|
||||
_ = z
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -595,6 +596,7 @@ func (z InQueueStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z InQueueStats) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 0
|
||||
_ = z
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -749,6 +749,7 @@ func (z *ReplicateDecision) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z ReplicateDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 0
|
||||
_ = z
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -760,6 +761,7 @@ func (z ReplicateDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z ReplicateDecision) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 0
|
||||
_ = z
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
@@ -1388,6 +1390,7 @@ func (z *ResyncDecision) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z ResyncDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 0
|
||||
_ = z
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -1399,6 +1402,7 @@ func (z ResyncDecision) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z ResyncDecision) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 0
|
||||
_ = z
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
|
||||
+53
-22
@@ -51,6 +51,8 @@ import (
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
"github.com/zeebo/xxh3"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -76,11 +78,6 @@ const (
|
||||
ReplicationWorkerMultiplier = 1.5
|
||||
)
|
||||
|
||||
func isReplicationEnabled(ctx context.Context, bucketName string) bool {
|
||||
rc, _ := getReplicationConfig(ctx, bucketName)
|
||||
return rc != nil
|
||||
}
|
||||
|
||||
// gets replication config associated to a given bucket name.
|
||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||
rCfg, _, err := globalBucketMetadataSys.GetReplicationConfig(ctx, bucketName)
|
||||
@@ -764,13 +761,20 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
|
||||
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) {
|
||||
meta := make(map[string]string)
|
||||
for k, v := range objInfo.UserDefined {
|
||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||
continue
|
||||
// In case of SSE-C objects copy the allowed internal headers as well
|
||||
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
|
||||
continue
|
||||
}
|
||||
if isStandardHeader(k) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if isStandardHeader(k) {
|
||||
continue
|
||||
if slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
|
||||
meta[validSSEReplicationHeaders[k]] = v
|
||||
} else {
|
||||
meta[k] = v
|
||||
}
|
||||
meta[k] = v
|
||||
}
|
||||
|
||||
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
|
||||
@@ -1166,9 +1170,10 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||
|
||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
||||
VersionID: ri.VersionID,
|
||||
Versioned: versioned,
|
||||
VersionSuspended: versionSuspended,
|
||||
VersionID: ri.VersionID,
|
||||
Versioned: versioned,
|
||||
VersionSuspended: versionSuspended,
|
||||
ReplicationRequest: true,
|
||||
})
|
||||
if err != nil {
|
||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||
@@ -1322,11 +1327,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
versioned := globalBucketVersioningSys.PrefixEnabled(bucket, object)
|
||||
versionSuspended := globalBucketVersioningSys.PrefixSuspended(bucket, object)
|
||||
|
||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{}, ObjectOptions{
|
||||
VersionID: ri.VersionID,
|
||||
Versioned: versioned,
|
||||
VersionSuspended: versionSuspended,
|
||||
})
|
||||
gr, err := objectAPI.GetObjectNInfo(ctx, bucket, object, nil, http.Header{},
|
||||
ObjectOptions{
|
||||
VersionID: ri.VersionID,
|
||||
Versioned: versioned,
|
||||
VersionSuspended: versionSuspended,
|
||||
ReplicationRequest: true,
|
||||
})
|
||||
if err != nil {
|
||||
if !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||
objInfo := ri.ToObjectInfo()
|
||||
@@ -1344,6 +1351,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
defer gr.Close()
|
||||
|
||||
objInfo := gr.ObjInfo
|
||||
|
||||
// make sure we have the latest metadata for metrics calculation
|
||||
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
|
||||
|
||||
@@ -1367,6 +1375,11 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
return
|
||||
}
|
||||
|
||||
// Set the encrypted size for SSE-C objects
|
||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||
size = objInfo.Size
|
||||
}
|
||||
|
||||
if tgt.Bucket == "" {
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s) to %s, target bucket is missing", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
||||
sendEvent(eventArgs{
|
||||
@@ -1563,6 +1576,9 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
|
||||
return err
|
||||
}
|
||||
attempts++
|
||||
time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
|
||||
}
|
||||
@@ -1596,21 +1612,35 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||
pInfo minio.ObjectPart
|
||||
)
|
||||
|
||||
var objectSize int64
|
||||
for _, partInfo := range objInfo.Parts {
|
||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize)
|
||||
} else {
|
||||
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cHeader := http.Header{}
|
||||
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
|
||||
popts := minio.PutObjectPartOptions{
|
||||
SSE: opts.ServerSideEncryption,
|
||||
SSE: opts.ServerSideEncryption,
|
||||
CustomHeader: cHeader,
|
||||
}
|
||||
|
||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.ActualSize, popts)
|
||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
|
||||
objectSize += partInfo.Size
|
||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts)
|
||||
} else {
|
||||
objectSize += partInfo.ActualSize
|
||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.ActualSize, popts)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pInfo.Size != partInfo.ActualSize {
|
||||
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) && pInfo.Size != partInfo.ActualSize {
|
||||
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
|
||||
}
|
||||
uploadedParts = append(uploadedParts, minio.CompletePart{
|
||||
@@ -1621,6 +1651,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
|
||||
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
|
||||
defer ccancel()
|
||||
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
|
||||
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]},
|
||||
Internal: minio.AdvancedPutOptions{
|
||||
SourceMTime: objInfo.ModTime,
|
||||
// always set this to distinguish between `mc mirror` replication and serverside
|
||||
|
||||
+19
-10
@@ -66,9 +66,11 @@ import (
|
||||
)
|
||||
|
||||
// serverDebugLog will enable debug printing
|
||||
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||
|
||||
var currentReleaseTime time.Time
|
||||
var (
|
||||
serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||
currentReleaseTime time.Time
|
||||
orchestrated = IsKubernetes() || IsDocker()
|
||||
)
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -361,6 +363,14 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
||||
ctxt.ConsoleAddr = ctx.String("console-address")
|
||||
}
|
||||
|
||||
if cxml := ctx.String("crossdomain-xml"); cxml != "" {
|
||||
buf, err := os.ReadFile(cxml)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctxt.CrossDomainXML = string(buf)
|
||||
}
|
||||
|
||||
// Check "no-compat" flag from command line argument.
|
||||
ctxt.StrictS3Compat = !(ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat"))
|
||||
|
||||
@@ -486,7 +496,11 @@ func runDNSCache(ctx *cli.Context) {
|
||||
dnsTTL := ctx.Duration("dns-cache-ttl")
|
||||
// Check if we have configured a custom DNS cache TTL.
|
||||
if dnsTTL <= 0 {
|
||||
dnsTTL = 10 * time.Minute
|
||||
if orchestrated {
|
||||
dnsTTL = 30 * time.Second
|
||||
} else {
|
||||
dnsTTL = 10 * time.Minute
|
||||
}
|
||||
}
|
||||
|
||||
// Call to refresh will refresh names in cache.
|
||||
@@ -749,12 +763,7 @@ func serverHandleEnvVars() {
|
||||
for _, endpoint := range minioEndpoints {
|
||||
if net.ParseIP(endpoint) == nil {
|
||||
// Checking if the IP is a DNS entry.
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = net.DefaultResolver.LookupHost
|
||||
}
|
||||
|
||||
addrs, err := lookupHost(GlobalContext, endpoint)
|
||||
addrs, err := globalDNSCache.LookupHost(GlobalContext, endpoint)
|
||||
if err != nil {
|
||||
logger.FatalIf(err, "Unable to initialize MinIO server with [%s] invalid entry found in MINIO_PUBLIC_IPS", endpoint)
|
||||
}
|
||||
|
||||
@@ -620,13 +620,13 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
userAgent := getUserAgent(getMinioMode())
|
||||
for n, l := range loggerCfg.HTTP {
|
||||
if l.Enabled {
|
||||
l.LogOnce = logger.LogOnceConsoleIf
|
||||
l.LogOnceIf = logger.LogOnceConsoleIf
|
||||
l.UserAgent = userAgent
|
||||
l.Transport = NewHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
||||
}
|
||||
loggerCfg.HTTP[n] = l
|
||||
}
|
||||
if errs := logger.UpdateSystemTargets(ctx, loggerCfg); len(errs) > 0 {
|
||||
if errs := logger.UpdateHTTPWebhooks(ctx, loggerCfg.HTTP); len(errs) > 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to update logger webhook config: %v", errs))
|
||||
}
|
||||
case config.AuditWebhookSubSys:
|
||||
@@ -637,14 +637,14 @@ func applyDynamicConfigForSubSys(ctx context.Context, objAPI ObjectLayer, s conf
|
||||
userAgent := getUserAgent(getMinioMode())
|
||||
for n, l := range loggerCfg.AuditWebhook {
|
||||
if l.Enabled {
|
||||
l.LogOnce = logger.LogOnceConsoleIf
|
||||
l.LogOnceIf = logger.LogOnceConsoleIf
|
||||
l.UserAgent = userAgent
|
||||
l.Transport = NewHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
||||
}
|
||||
loggerCfg.AuditWebhook[n] = l
|
||||
}
|
||||
|
||||
if errs := logger.UpdateAuditWebhookTargets(ctx, loggerCfg); len(errs) > 0 {
|
||||
if errs := logger.UpdateAuditWebhooks(ctx, loggerCfg.AuditWebhook); len(errs) > 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to update audit webhook targets: %v", errs))
|
||||
}
|
||||
case config.AuditKafkaSubSys:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -32,10 +32,14 @@ const crossDomainXMLEntity = "/crossdomain.xml"
|
||||
// policy file that grants access to the source domain, allowing the client to continue the transaction.
|
||||
func setCrossDomainPolicyMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
cxml := crossDomainXML
|
||||
if globalServerCtxt.CrossDomainXML != "" {
|
||||
cxml = globalServerCtxt.CrossDomainXML
|
||||
}
|
||||
// Look for 'crossdomain.xml' in the incoming request.
|
||||
if r.URL.Path == crossDomainXMLEntity {
|
||||
// Write the standard cross domain policy xml.
|
||||
w.Write([]byte(crossDomainXML))
|
||||
w.Write([]byte(cxml))
|
||||
// Request completed, no need to serve to other handlers.
|
||||
return
|
||||
}
|
||||
|
||||
+9
-3
@@ -1051,7 +1051,9 @@ func (i *scannerItem) applyNewerNoncurrentVersionLimit(ctx context.Context, _ Ob
|
||||
})
|
||||
}
|
||||
|
||||
expState.enqueueByNewerNoncurrent(i.bucket, toDel, event)
|
||||
if len(toDel) > 0 {
|
||||
expState.enqueueByNewerNoncurrent(i.bucket, toDel, event)
|
||||
}
|
||||
return objectInfos, nil
|
||||
}
|
||||
|
||||
@@ -1219,6 +1221,8 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
|
||||
if lcEvent.Action.DeleteAll() {
|
||||
opts.DeletePrefix = true
|
||||
// use prefix delete on exact object (this is an optimization to avoid fan-out calls)
|
||||
opts.DeletePrefixObject = true
|
||||
}
|
||||
var (
|
||||
dobj ObjectInfo
|
||||
@@ -1238,7 +1242,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
}
|
||||
}()
|
||||
|
||||
dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, obj.Name, opts)
|
||||
dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, encodeDirObject(obj.Name), opts)
|
||||
if err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
return false
|
||||
@@ -1259,7 +1263,9 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
|
||||
if obj.DeleteMarker {
|
||||
eventName = event.ObjectRemovedDeleteMarkerCreated
|
||||
}
|
||||
|
||||
if lcEvent.Action.DeleteAll() {
|
||||
eventName = event.ObjectRemovedDeleteAllVersions
|
||||
}
|
||||
// Notify object deleted event.
|
||||
sendEvent(eventArgs{
|
||||
EventName: eventName,
|
||||
|
||||
+27
-10
@@ -191,6 +191,22 @@ type replicationAllStatsV1 struct {
|
||||
ReplicaCount uint64 `msg:"ReplicaCount,omitempty"`
|
||||
}
|
||||
|
||||
// empty returns true if the replicationAllStats is empty (contains no entries).
|
||||
func (r *replicationAllStats) empty() bool {
|
||||
if r == nil {
|
||||
return true
|
||||
}
|
||||
if r.ReplicaSize != 0 || r.ReplicaCount != 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range r.Targets {
|
||||
if !v.Empty() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// clone creates a deep-copy clone.
|
||||
func (r *replicationAllStats) clone() *replicationAllStats {
|
||||
if r == nil {
|
||||
@@ -523,20 +539,18 @@ func (d *dataUsageCache) searchParent(h dataUsageHash) *dataUsageHash {
|
||||
want := h.Key()
|
||||
if idx := strings.LastIndexByte(want, '/'); idx >= 0 {
|
||||
if v := d.find(want[:idx]); v != nil {
|
||||
for child := range v.Children {
|
||||
if child == want {
|
||||
found := hashPath(want[:idx])
|
||||
return &found
|
||||
}
|
||||
_, ok := v.Children[want]
|
||||
if ok {
|
||||
found := hashPath(want[:idx])
|
||||
return &found
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range d.Cache {
|
||||
for child := range v.Children {
|
||||
if child == want {
|
||||
found := dataUsageHash(k)
|
||||
return &found
|
||||
}
|
||||
_, ok := v.Children[want]
|
||||
if ok {
|
||||
found := dataUsageHash(k)
|
||||
return &found
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -901,6 +915,9 @@ func (d *dataUsageCache) sizeRecursive(path string) *dataUsageEntry {
|
||||
return root
|
||||
}
|
||||
flat := d.flatten(*root)
|
||||
if flat.ReplicationStats.empty() {
|
||||
flat.ReplicationStats = nil
|
||||
}
|
||||
return &flat
|
||||
}
|
||||
|
||||
|
||||
+18
-14
@@ -1762,7 +1762,7 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(10)
|
||||
var zb0001Mask uint16 /* 10 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -1866,7 +1866,7 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x80) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
||||
// write "rs"
|
||||
err = en.Append(0xa2, 0x72, 0x73)
|
||||
if err != nil {
|
||||
@@ -1885,7 +1885,7 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x100) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x100) == 0 { // if not omitted
|
||||
// write "ats"
|
||||
err = en.Append(0xa3, 0x61, 0x74, 0x73)
|
||||
if err != nil {
|
||||
@@ -1920,7 +1920,7 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(10)
|
||||
var zb0001Mask uint16 /* 10 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -1968,7 +1968,7 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
for za0002 := range z.ObjVersions {
|
||||
o = msgp.AppendUint64(o, z.ObjVersions[za0002])
|
||||
}
|
||||
if (zb0001Mask & 0x80) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x80) == 0 { // if not omitted
|
||||
// string "rs"
|
||||
o = append(o, 0xa2, 0x72, 0x73)
|
||||
if z.ReplicationStats == nil {
|
||||
@@ -1981,7 +1981,7 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x100) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x100) == 0 { // if not omitted
|
||||
// string "ats"
|
||||
o = append(o, 0xa3, 0x61, 0x74, 0x73)
|
||||
if z.AllTierStats == nil {
|
||||
@@ -3243,7 +3243,7 @@ func (z *replicationAllStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *replicationAllStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(3)
|
||||
var zb0001Mask uint8 /* 3 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -3267,7 +3267,7 @@ func (z *replicationAllStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
if zb0001Len == 0 {
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x1) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x1) == 0 { // if not omitted
|
||||
// write "t"
|
||||
err = en.Append(0xa1, 0x74)
|
||||
if err != nil {
|
||||
@@ -3291,7 +3291,7 @@ func (z *replicationAllStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x2) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||
// write "r"
|
||||
err = en.Append(0xa1, 0x72)
|
||||
if err != nil {
|
||||
@@ -3303,7 +3303,7 @@ func (z *replicationAllStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x4) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x4) == 0 { // if not omitted
|
||||
// write "rc"
|
||||
err = en.Append(0xa2, 0x72, 0x63)
|
||||
if err != nil {
|
||||
@@ -3321,7 +3321,7 @@ func (z *replicationAllStats) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *replicationAllStats) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(3)
|
||||
var zb0001Mask uint8 /* 3 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -3342,7 +3342,7 @@ func (z *replicationAllStats) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
if zb0001Len == 0 {
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x1) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x1) == 0 { // if not omitted
|
||||
// string "t"
|
||||
o = append(o, 0xa1, 0x74)
|
||||
o = msgp.AppendMapHeader(o, uint32(len(z.Targets)))
|
||||
@@ -3355,12 +3355,12 @@ func (z *replicationAllStats) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x2) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||
// string "r"
|
||||
o = append(o, 0xa1, 0x72)
|
||||
o = msgp.AppendUint64(o, z.ReplicaSize)
|
||||
}
|
||||
if (zb0001Mask & 0x4) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x4) == 0 { // if not omitted
|
||||
// string "rc"
|
||||
o = append(o, 0xa2, 0x72, 0x63)
|
||||
o = msgp.AppendUint64(o, z.ReplicaCount)
|
||||
@@ -3478,6 +3478,8 @@ func (z *replicationAllStatsV1) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
delete(z.Targets, key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0002 > 0 {
|
||||
zb0002--
|
||||
var za0001 string
|
||||
@@ -3588,6 +3590,8 @@ func (z *replicationAllStatsV1) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
delete(z.Targets, key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0002 > 0 {
|
||||
var za0001 string
|
||||
var za0002 replicationStats
|
||||
|
||||
@@ -364,6 +364,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
|
||||
}
|
||||
if e == nil {
|
||||
t.Fatal("got nil result")
|
||||
return
|
||||
}
|
||||
if e.Size != int64(w.size) {
|
||||
t.Error("got size", e.Size, "want", w.size)
|
||||
|
||||
@@ -590,8 +590,6 @@ func hostResolveToLocalhost(endpoint Endpoint) bool {
|
||||
|
||||
// UpdateIsLocal - resolves the host and discovers the local host.
|
||||
func (endpoints Endpoints) UpdateIsLocal() error {
|
||||
orchestrated := IsDocker() || IsKubernetes()
|
||||
|
||||
var epsResolved int
|
||||
var foundLocal bool
|
||||
resolvedList := make([]bool, len(endpoints))
|
||||
@@ -775,8 +773,6 @@ type PoolEndpointList []Endpoints
|
||||
|
||||
// UpdateIsLocal - resolves all hosts and discovers which are local
|
||||
func (p PoolEndpointList) UpdateIsLocal() error {
|
||||
orchestrated := IsDocker() || IsKubernetes()
|
||||
|
||||
var epsResolved int
|
||||
var epCount int
|
||||
|
||||
@@ -1034,7 +1030,6 @@ func CreatePoolEndpoints(serverAddr string, poolsLayout ...poolDisksLayout) ([]E
|
||||
}
|
||||
}
|
||||
|
||||
orchestrated := IsKubernetes() || IsDocker()
|
||||
reverseProxy := (env.Get("_MINIO_REVERSE_PROXY", "") != "") && ((env.Get("MINIO_CI_CD", "") != "") || (env.Get("CI", "") != ""))
|
||||
// If not orchestrated
|
||||
// and not setup in reverse proxy
|
||||
|
||||
@@ -210,6 +210,24 @@ func (fi FileInfo) DataMov() bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
func auditHealObject(ctx context.Context, bucket, object, versionID string, err error) {
|
||||
if len(logger.AuditTargets()) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
opts := AuditLogOptions{
|
||||
Event: "HealObject",
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionID: versionID,
|
||||
}
|
||||
if err != nil {
|
||||
opts.Error = err.Error()
|
||||
}
|
||||
|
||||
auditLogInternal(ctx, opts)
|
||||
}
|
||||
|
||||
// Heals an object by re-writing corrupt/missing erasure blocks.
|
||||
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
|
||||
dryRun := opts.DryRun
|
||||
@@ -218,6 +236,8 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
storageDisks := er.getDisks()
|
||||
storageEndpoints := er.getEndpoints()
|
||||
|
||||
defer auditHealObject(ctx, bucket, object, versionID, err)
|
||||
|
||||
if globalTrace.NumSubscribers(madmin.TraceHealing) > 0 {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
|
||||
@@ -634,7 +634,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
defer removeRoots(fsDirs)
|
||||
|
||||
// Everything is fine, should return nil
|
||||
objLayer, disks, err := initObjectLayer(ctx, mustGetPoolEndpoints(0, fsDirs...))
|
||||
objLayer, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(0, fsDirs...))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -650,7 +650,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
t.Fatalf("Failed to make a bucket - %v", err)
|
||||
}
|
||||
|
||||
disks = objLayer.(*erasureServerPools).serverPools[0].erasureDisks[0]
|
||||
disks := objLayer.(*erasureServerPools).serverPools[0].erasureDisks[0]
|
||||
orgDisks := append([]StorageAPI{}, disks...)
|
||||
|
||||
// Enable versioning.
|
||||
|
||||
@@ -381,7 +381,7 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
|
||||
rctx := lkctx.Context()
|
||||
obj, err := er.getObjectInfo(rctx, bucket, object, opts)
|
||||
lk.RUnlock(lkctx)
|
||||
if err != nil && !isErrVersionNotFound(err) {
|
||||
if err != nil && !isErrVersionNotFound(err) && !isErrObjectNotFound(err) {
|
||||
return nil, err
|
||||
}
|
||||
if opts.CheckPrecondFn(obj) {
|
||||
@@ -1255,7 +1255,11 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
}
|
||||
|
||||
// Save the consolidated actual size.
|
||||
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(objectActualSize, 10)
|
||||
if opts.ReplicationRequest {
|
||||
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = opts.UserDefined["X-Minio-Internal-Actual-Object-Size"]
|
||||
} else {
|
||||
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(objectActualSize, 10)
|
||||
}
|
||||
|
||||
if opts.DataMovement {
|
||||
fi.SetDataMov()
|
||||
|
||||
+67
-8
@@ -31,6 +31,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/klauspost/readahead"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
@@ -244,6 +245,11 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
||||
}, toObjectErr(errMethodNotAllowed, bucket, object)
|
||||
}
|
||||
|
||||
// Set NoDecryption for SSE-C objects and if replication request
|
||||
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) && opts.ReplicationRequest {
|
||||
opts.NoDecryption = true
|
||||
}
|
||||
|
||||
if objInfo.IsRemote() {
|
||||
gr, err := getTransitionedObjectReader(ctx, bucket, object, rs, h, objInfo, opts)
|
||||
if err != nil {
|
||||
@@ -1396,20 +1402,25 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
defer er.deleteAll(context.Background(), minioMetaTmpBucket, tempObj)
|
||||
|
||||
shardFileSize := erasure.ShardFileSize(data.Size())
|
||||
inlineBlock := globalStorageClass.InlineBlock()
|
||||
if inlineBlock <= 0 {
|
||||
inlineBlock = 128 * humanize.KiByte
|
||||
}
|
||||
|
||||
writers := make([]io.Writer, len(onlineDisks))
|
||||
var inlineBuffers []*bytes.Buffer
|
||||
if shardFileSize >= 0 {
|
||||
if !opts.Versioned && shardFileSize < smallFileThreshold {
|
||||
if !opts.Versioned && shardFileSize < inlineBlock {
|
||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||
} else if shardFileSize < smallFileThreshold/8 {
|
||||
} else if shardFileSize < inlineBlock/8 {
|
||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||
}
|
||||
} else {
|
||||
// If compressed, use actual size to determine.
|
||||
if sz := erasure.ShardFileSize(data.ActualSize()); sz > 0 {
|
||||
if !opts.Versioned && sz < smallFileThreshold {
|
||||
if !opts.Versioned && sz < inlineBlock {
|
||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||
} else if sz < smallFileThreshold/8 {
|
||||
} else if sz < inlineBlock/8 {
|
||||
inlineBuffers = make([]*bytes.Buffer, len(onlineDisks))
|
||||
}
|
||||
}
|
||||
@@ -1815,10 +1826,6 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
||||
auditObjectErasureSet(ctx, object, &er)
|
||||
}
|
||||
|
||||
if opts.DeletePrefix {
|
||||
return ObjectInfo{}, toObjectErr(er.deletePrefix(ctx, bucket, object), bucket, object)
|
||||
}
|
||||
|
||||
var lc *lifecycle.Lifecycle
|
||||
var rcfg lock.Retention
|
||||
var replcfg *replication.Config
|
||||
@@ -1845,12 +1852,63 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
||||
}
|
||||
}
|
||||
|
||||
if opts.DeletePrefix {
|
||||
if opts.Expiration.Expire {
|
||||
// Expire all versions expiration must still verify the state() on disk
|
||||
// via a getObjectInfo() call as follows, any read quorum issues we
|
||||
// must not proceed further for safety reasons. attempt a MRF heal
|
||||
// while we see such quorum errors.
|
||||
goi, _, gerr := er.getObjectInfoAndQuorum(ctx, bucket, object, opts)
|
||||
if gerr != nil && goi.Name == "" {
|
||||
if _, ok := gerr.(InsufficientReadQuorum); ok {
|
||||
// Add an MRF heal for next time.
|
||||
er.addPartial(bucket, object, opts.VersionID)
|
||||
|
||||
return objInfo, InsufficientWriteQuorum{}
|
||||
}
|
||||
return objInfo, gerr
|
||||
}
|
||||
|
||||
// Add protection and re-verify the ILM rules for qualification
|
||||
// based on the latest objectInfo and see if the object still
|
||||
// qualifies for deletion.
|
||||
if gerr == nil {
|
||||
evt := evalActionFromLifecycle(ctx, *lc, rcfg, replcfg, goi)
|
||||
var isErr bool
|
||||
switch evt.Action {
|
||||
case lifecycle.NoneAction:
|
||||
isErr = true
|
||||
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
|
||||
isErr = true
|
||||
}
|
||||
if isErr {
|
||||
if goi.VersionID != "" {
|
||||
return goi, VersionNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionID: goi.VersionID,
|
||||
}
|
||||
}
|
||||
return goi, ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
}
|
||||
}
|
||||
}
|
||||
} // Delete marker and any latest that qualifies shall be expired permanently.
|
||||
|
||||
return ObjectInfo{}, toObjectErr(er.deletePrefix(ctx, bucket, object), bucket, object)
|
||||
}
|
||||
|
||||
storageDisks := er.getDisks()
|
||||
versionFound := true
|
||||
objInfo = ObjectInfo{VersionID: opts.VersionID} // version id needed in Delete API response.
|
||||
goi, _, gerr := er.getObjectInfoAndQuorum(ctx, bucket, object, opts)
|
||||
if gerr != nil && goi.Name == "" {
|
||||
if _, ok := gerr.(InsufficientReadQuorum); ok {
|
||||
// Add an MRF heal for next time.
|
||||
er.addPartial(bucket, object, opts.VersionID)
|
||||
|
||||
return objInfo, InsufficientWriteQuorum{}
|
||||
}
|
||||
// For delete marker replication, versionID being replicated will not exist on disk
|
||||
@@ -1860,6 +1918,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
||||
return objInfo, gerr
|
||||
}
|
||||
}
|
||||
|
||||
if opts.EvalMetadataFn != nil {
|
||||
dsc, err := opts.EvalMetadataFn(&goi, err)
|
||||
if err != nil {
|
||||
|
||||
@@ -72,9 +72,6 @@ func (pd *PoolDecommissionInfo) Clone() *PoolDecommissionInfo {
|
||||
if pd == nil {
|
||||
return nil
|
||||
}
|
||||
if pd.StartTime.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return &PoolDecommissionInfo{
|
||||
StartTime: pd.StartTime,
|
||||
StartSize: pd.StartSize,
|
||||
@@ -97,7 +94,7 @@ func (pd *PoolDecommissionInfo) Clone() *PoolDecommissionInfo {
|
||||
|
||||
// bucketPop should be called when a bucket is done decommissioning.
|
||||
// Adds the bucket to the list of decommissioned buckets and updates resume numbers.
|
||||
func (pd *PoolDecommissionInfo) bucketPop(bucket string) {
|
||||
func (pd *PoolDecommissionInfo) bucketPop(bucket string) bool {
|
||||
pd.DecommissionedBuckets = append(pd.DecommissionedBuckets, bucket)
|
||||
for i, b := range pd.QueuedBuckets {
|
||||
if b == bucket {
|
||||
@@ -109,9 +106,10 @@ func (pd *PoolDecommissionInfo) bucketPop(bucket string) {
|
||||
pd.Prefix = "" // empty this out for the next bucket
|
||||
pd.Object = "" // empty this out for next object
|
||||
}
|
||||
return
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (pd *PoolDecommissionInfo) isBucketDecommissioned(bucket string) bool {
|
||||
@@ -222,12 +220,12 @@ func (p poolMeta) isBucketDecommissioned(idx int, bucket string) bool {
|
||||
return p.Pools[idx].Decommission.isBucketDecommissioned(bucket)
|
||||
}
|
||||
|
||||
func (p *poolMeta) BucketDone(idx int, bucket decomBucketInfo) {
|
||||
func (p *poolMeta) BucketDone(idx int, bucket decomBucketInfo) bool {
|
||||
if p.Pools[idx].Decommission == nil {
|
||||
// Decommission not in progress.
|
||||
return
|
||||
return false
|
||||
}
|
||||
p.Pools[idx].Decommission.bucketPop(bucket.String())
|
||||
return p.Pools[idx].Decommission.bucketPop(bucket.String())
|
||||
}
|
||||
|
||||
func (p poolMeta) ResumeBucketObject(idx int) (bucket, object string) {
|
||||
@@ -1055,8 +1053,10 @@ func (z *erasureServerPools) decommissionInBackground(ctx context.Context, idx i
|
||||
}
|
||||
|
||||
z.poolMetaMutex.Lock()
|
||||
z.poolMeta.BucketDone(idx, bucket) // remove from pendingBuckets and persist.
|
||||
z.poolMeta.save(ctx, z.serverPools)
|
||||
if z.poolMeta.BucketDone(idx, bucket) {
|
||||
// remove from pendingBuckets and persist.
|
||||
logger.LogIf(ctx, z.poolMeta.save(ctx, z.serverPools))
|
||||
}
|
||||
z.poolMetaMutex.Unlock()
|
||||
continue
|
||||
}
|
||||
@@ -1071,8 +1071,9 @@ func (z *erasureServerPools) decommissionInBackground(ctx context.Context, idx i
|
||||
stopFn(nil)
|
||||
|
||||
z.poolMetaMutex.Lock()
|
||||
z.poolMeta.BucketDone(idx, bucket)
|
||||
z.poolMeta.save(ctx, z.serverPools)
|
||||
if z.poolMeta.BucketDone(idx, bucket) {
|
||||
logger.LogIf(ctx, z.poolMeta.save(ctx, z.serverPools))
|
||||
}
|
||||
z.poolMetaMutex.Unlock()
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -614,6 +614,7 @@ func (z *rebalanceMetrics) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z rebalanceMetrics) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 0
|
||||
_ = z
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -625,6 +626,7 @@ func (z rebalanceMetrics) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z rebalanceMetrics) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 0
|
||||
_ = z
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -688,12 +688,17 @@ func (z *erasureServerPools) NSScanner(ctx context.Context, updates chan<- DataU
|
||||
updates <- DataUsageInfo{} // no buckets found update data usage to reflect latest state
|
||||
return nil
|
||||
}
|
||||
|
||||
totalResults := 0
|
||||
resultIndex := -1
|
||||
for _, z := range z.serverPools {
|
||||
totalResults += len(z.sets)
|
||||
}
|
||||
results = make([]dataUsageCache, totalResults)
|
||||
// Collect for each set in serverPools.
|
||||
for _, z := range z.serverPools {
|
||||
for _, erObj := range z.sets {
|
||||
resultIndex++
|
||||
wg.Add(1)
|
||||
results = append(results, dataUsageCache{})
|
||||
go func(i int, erObj *erasureObjects) {
|
||||
updates := make(chan dataUsageCache, 1)
|
||||
defer xioutil.SafeClose(updates)
|
||||
@@ -719,7 +724,7 @@ func (z *erasureServerPools) NSScanner(ctx context.Context, updates chan<- DataU
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
}(len(results)-1, erObj)
|
||||
}(resultIndex, erObj)
|
||||
}
|
||||
}
|
||||
updateCloser := make(chan chan struct{})
|
||||
@@ -1266,13 +1271,17 @@ func (z *erasureServerPools) CopyObject(ctx context.Context, srcBucket, srcObjec
|
||||
return z.serverPools[poolIdx].PutObject(ctx, dstBucket, dstObject, srcInfo.PutObjReader, putOpts)
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||
return z.listObjectsGeneric(ctx, bucket, prefix, marker, delimiter, maxKeys, true)
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) ListObjectsV2(ctx context.Context, bucket, prefix, continuationToken, delimiter string, maxKeys int, fetchOwner bool, startAfter string) (ListObjectsV2Info, error) {
|
||||
marker := continuationToken
|
||||
if marker == "" {
|
||||
marker = startAfter
|
||||
}
|
||||
|
||||
loi, err := z.ListObjects(ctx, bucket, prefix, marker, delimiter, maxKeys)
|
||||
loi, err := z.listObjectsGeneric(ctx, bucket, prefix, marker, delimiter, maxKeys, false)
|
||||
if err != nil {
|
||||
return ListObjectsV2Info{}, err
|
||||
}
|
||||
@@ -1381,9 +1390,10 @@ func maxKeysPlusOne(maxKeys int, addOne bool) int {
|
||||
return maxKeys
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int) (ListObjectsInfo, error) {
|
||||
func (z *erasureServerPools) listObjectsGeneric(ctx context.Context, bucket, prefix, marker, delimiter string, maxKeys int, v1 bool) (ListObjectsInfo, error) {
|
||||
var loi ListObjectsInfo
|
||||
opts := listPathOptions{
|
||||
V1: v1,
|
||||
Bucket: bucket,
|
||||
Prefix: prefix,
|
||||
Separator: delimiter,
|
||||
@@ -1554,8 +1564,23 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
||||
}
|
||||
if loi.IsTruncated {
|
||||
last := objects[len(objects)-1]
|
||||
loi.NextMarker = opts.encodeMarker(last.Name)
|
||||
loi.NextMarker = last.Name
|
||||
}
|
||||
|
||||
if merged.lastSkippedEntry != "" {
|
||||
if merged.lastSkippedEntry > loi.NextMarker {
|
||||
// An object hidden by ILM was found during listing. Since the number of entries
|
||||
// fetched from drives is limited, set IsTruncated to true to ask the s3 client
|
||||
// to continue listing if it wishes in order to find if there is more objects.
|
||||
loi.IsTruncated = true
|
||||
loi.NextMarker = merged.lastSkippedEntry
|
||||
}
|
||||
}
|
||||
|
||||
if loi.NextMarker != "" {
|
||||
loi.NextMarker = opts.encodeMarker(loi.NextMarker)
|
||||
}
|
||||
|
||||
return loi, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -543,9 +543,13 @@ func (er erasureObjects) nsScanner(ctx context.Context, buckets []BucketInfo, wa
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
// Flatten for upstream, but save full state.
|
||||
var root dataUsageEntry
|
||||
if r := cache.root(); r != nil {
|
||||
root = cache.flatten(*r)
|
||||
if root.ReplicationStats.empty() {
|
||||
root.ReplicationStats = nil
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -33,6 +33,8 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
xnet "github.com/minio/pkg/v2/net"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/minio/minio/internal/amztime"
|
||||
"github.com/minio/minio/internal/config/dns"
|
||||
@@ -73,6 +75,9 @@ const (
|
||||
// and must not set by clients
|
||||
func containsReservedMetadata(header http.Header) bool {
|
||||
for key := range header {
|
||||
if slices.Contains(maps.Keys(validSSEReplicationHeaders), key) {
|
||||
return false
|
||||
}
|
||||
if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -108,15 +108,15 @@ var containsReservedMetadataTests = []struct {
|
||||
},
|
||||
{
|
||||
header: http.Header{crypto.MetaIV: []string{"iv"}},
|
||||
shouldFail: true,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
header: http.Header{crypto.MetaAlgorithm: []string{crypto.InsecureSealAlgorithm}},
|
||||
shouldFail: true,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
header: http.Header{crypto.MetaSealedKeySSEC: []string{"mac"}},
|
||||
shouldFail: true,
|
||||
shouldFail: false,
|
||||
},
|
||||
{
|
||||
header: http.Header{ReservedMetadataPrefix + "Key": []string{"value"}},
|
||||
|
||||
@@ -171,6 +171,7 @@ type serverCtxt struct {
|
||||
ReadHeaderTimeout time.Duration
|
||||
MaxIdleConnsPerHost int
|
||||
|
||||
CrossDomainXML string
|
||||
// The layout of disks as interpreted
|
||||
Layout disksLayout
|
||||
}
|
||||
|
||||
+1
-5
@@ -35,13 +35,9 @@ var globalGrid atomic.Pointer[grid.Manager]
|
||||
var globalGridStart = make(chan struct{})
|
||||
|
||||
func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
hosts, local := eps.GridHosts()
|
||||
g, err := grid.NewManager(ctx, grid.ManagerOptions{
|
||||
Dialer: grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions))),
|
||||
Dialer: grid.ContextDialer(xhttp.DialContextWithLookupHost(globalDNSCache.LookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions))),
|
||||
Local: local,
|
||||
Hosts: hosts,
|
||||
AddAuth: newCachedAuthToken(),
|
||||
|
||||
+10
-5
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
@@ -55,7 +56,7 @@ type apiConfig struct {
|
||||
gzipObjects bool
|
||||
rootAccess bool
|
||||
syncEvents bool
|
||||
objectMaxVersions int
|
||||
objectMaxVersions int64
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -291,7 +292,11 @@ func (t *apiConfig) getRequestsPool() (chan struct{}, time.Duration) {
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
if t.requestsPool == nil {
|
||||
return nil, time.Duration(0)
|
||||
return nil, 10 * time.Second
|
||||
}
|
||||
|
||||
if t.requestsDeadline <= 0 {
|
||||
return t.requestsPool, 10 * time.Second
|
||||
}
|
||||
|
||||
return t.requestsPool, t.requestsDeadline
|
||||
@@ -389,13 +394,13 @@ func (t *apiConfig) isSyncEventsEnabled() bool {
|
||||
return t.syncEvents
|
||||
}
|
||||
|
||||
func (t *apiConfig) getObjectMaxVersions() int {
|
||||
func (t *apiConfig) getObjectMaxVersions() int64 {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
if t.objectMaxVersions <= 0 {
|
||||
// defaults to 'maxObjectVersions' when unset.
|
||||
return maxObjectVersions
|
||||
// defaults to 'IntMax' when unset.
|
||||
return math.MaxInt64
|
||||
}
|
||||
|
||||
return t.objectMaxVersions
|
||||
|
||||
+32
-1
@@ -33,6 +33,8 @@ import (
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/mcontext"
|
||||
xnet "github.com/minio/pkg/v2/net"
|
||||
"golang.org/x/exp/maps"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -82,6 +84,31 @@ var supportedHeaders = []string{
|
||||
xhttp.AmzObjectTagging,
|
||||
"expires",
|
||||
xhttp.AmzBucketReplicationStatus,
|
||||
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Iv",
|
||||
"X-Minio-Replication-Encrypted-Multipart",
|
||||
"X-Minio-Replication-Actual-Object-Size",
|
||||
// Add more supported headers here.
|
||||
}
|
||||
|
||||
// mapping of internal headers to allowed replication headers
|
||||
var validSSEReplicationHeaders = map[string]string{
|
||||
"X-Minio-Internal-Server-Side-Encryption-Sealed-Key": "X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||
"X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm": "X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||
"X-Minio-Internal-Server-Side-Encryption-Iv": "X-Minio-Replication-Server-Side-Encryption-Iv",
|
||||
"X-Minio-Internal-Encrypted-Multipart": "X-Minio-Replication-Encrypted-Multipart",
|
||||
"X-Minio-Internal-Actual-Object-Size": "X-Minio-Replication-Actual-Object-Size",
|
||||
// Add more supported headers here.
|
||||
}
|
||||
|
||||
// mapping of replication headers to internal headers
|
||||
var replicationToInternalHeaders = map[string]string{
|
||||
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key": "X-Minio-Internal-Server-Side-Encryption-Sealed-Key",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm": "X-Minio-Internal-Server-Side-Encryption-Seal-Algorithm",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Iv": "X-Minio-Internal-Server-Side-Encryption-Iv",
|
||||
"X-Minio-Replication-Encrypted-Multipart": "X-Minio-Internal-Encrypted-Multipart",
|
||||
"X-Minio-Replication-Actual-Object-Size": "X-Minio-Internal-Actual-Object-Size",
|
||||
// Add more supported headers here.
|
||||
}
|
||||
|
||||
@@ -178,7 +205,11 @@ func extractMetadataFromMime(ctx context.Context, v textproto.MIMEHeader, m map[
|
||||
for _, supportedHeader := range supportedHeaders {
|
||||
value, ok := nv[http.CanonicalHeaderKey(supportedHeader)]
|
||||
if ok {
|
||||
m[supportedHeader] = strings.Join(value, ",")
|
||||
if slices.Contains(maps.Keys(replicationToInternalHeaders), supportedHeader) {
|
||||
m[replicationToInternalHeaders[supportedHeader]] = strings.Join(value, ",")
|
||||
} else {
|
||||
m[supportedHeader] = strings.Join(value, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
"go.etcd.io/etcd/api/v3/mvccpb"
|
||||
etcd "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
@@ -325,11 +326,11 @@ func (ies *IAMEtcdStore) loadGroups(ctx context.Context, m map[string]GroupInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, _ int) error {
|
||||
func (ies *IAMEtcdStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error {
|
||||
return ies.loadMappedPolicy(ctx, name, userType, isGroup, m)
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
|
||||
func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
|
||||
var p MappedPolicy
|
||||
err := ies.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
|
||||
if err != nil {
|
||||
@@ -338,11 +339,11 @@ func (ies *IAMEtcdStore) loadMappedPolicy(ctx context.Context, name string, user
|
||||
}
|
||||
return err
|
||||
}
|
||||
m[name] = p
|
||||
m.Store(name, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMappedPolicy(ctx context.Context, kv *mvccpb.KeyValue, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, basePrefix string) error {
|
||||
func getMappedPolicy(kv *mvccpb.KeyValue, m *xsync.MapOf[string, MappedPolicy], basePrefix string) error {
|
||||
var p MappedPolicy
|
||||
err := getIAMConfig(&p, kv.Value, string(kv.Key))
|
||||
if err != nil {
|
||||
@@ -352,11 +353,11 @@ func getMappedPolicy(ctx context.Context, kv *mvccpb.KeyValue, userType IAMUserT
|
||||
return err
|
||||
}
|
||||
name := extractPathPrefixAndSuffix(string(kv.Key), basePrefix, ".json")
|
||||
m[name] = p
|
||||
m.Store(name, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
|
||||
func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
|
||||
cctx, cancel := context.WithTimeout(ctx, defaultContextTimeout)
|
||||
defer cancel()
|
||||
var basePrefix string
|
||||
@@ -381,7 +382,7 @@ func (ies *IAMEtcdStore) loadMappedPolicies(ctx context.Context, userType IAMUse
|
||||
|
||||
// Parse all policies mapping to create the proper data model
|
||||
for _, kv := range r.Kvs {
|
||||
if err = getMappedPolicy(ctx, kv, userType, isGroup, m, basePrefix); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
if err = getMappedPolicy(kv, m, basePrefix); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
+77
-63
@@ -25,16 +25,17 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
// IAMObjectStore implements IAMStorageAPI
|
||||
@@ -44,6 +45,8 @@ type IAMObjectStore struct {
|
||||
|
||||
*iamCache
|
||||
|
||||
cachedIAMListing atomic.Value
|
||||
|
||||
usersSysType UsersSysType
|
||||
|
||||
objAPI ObjectLayer
|
||||
@@ -325,9 +328,7 @@ func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool,
|
||||
m map[string]MappedPolicy, retries int,
|
||||
) error {
|
||||
func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error {
|
||||
for {
|
||||
retry:
|
||||
var p MappedPolicy
|
||||
@@ -344,14 +345,12 @@ func (iamOS *IAMObjectStore) loadMappedPolicyWithRetry(ctx context.Context, name
|
||||
goto retry
|
||||
}
|
||||
|
||||
m[name] = p
|
||||
m.Store(name, p)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool,
|
||||
m map[string]MappedPolicy,
|
||||
) error {
|
||||
func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
|
||||
var p MappedPolicy
|
||||
err := iamOS.loadIAMConfig(ctx, &p, getMappedPolicyPath(name, userType, isGroup))
|
||||
if err != nil {
|
||||
@@ -361,11 +360,11 @@ func (iamOS *IAMObjectStore) loadMappedPolicy(ctx context.Context, name string,
|
||||
return err
|
||||
}
|
||||
|
||||
m[name] = p
|
||||
m.Store(name, p)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error {
|
||||
func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error {
|
||||
var basePath string
|
||||
if isGroup {
|
||||
basePath = iamConfigPolicyDBGroupsPrefix
|
||||
@@ -396,53 +395,45 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IA
|
||||
}
|
||||
|
||||
var (
|
||||
usersListKey = "users/"
|
||||
svcAccListKey = "service-accounts/"
|
||||
groupsListKey = "groups/"
|
||||
policiesListKey = "policies/"
|
||||
stsListKey = "sts/"
|
||||
policyDBUsersListKey = "policydb/users/"
|
||||
policyDBSTSUsersListKey = "policydb/sts-users/"
|
||||
policyDBServiceAccountsListKey = "policydb/service-accounts/"
|
||||
policyDBGroupsListKey = "policydb/groups/"
|
||||
|
||||
// List of directories from which to read iam data into memory.
|
||||
allListKeys = []string{
|
||||
usersListKey,
|
||||
svcAccListKey,
|
||||
groupsListKey,
|
||||
policiesListKey,
|
||||
stsListKey,
|
||||
policyDBUsersListKey,
|
||||
policyDBSTSUsersListKey,
|
||||
policyDBServiceAccountsListKey,
|
||||
policyDBGroupsListKey,
|
||||
}
|
||||
|
||||
// List of directories to skip: we do not read STS directories for better
|
||||
// performance. STS credentials would be stored in memory when they are
|
||||
// first used.
|
||||
iamLoadSkipListKeySet = set.CreateStringSet(
|
||||
stsListKey,
|
||||
policyDBSTSUsersListKey,
|
||||
)
|
||||
usersListKey = "users/"
|
||||
svcAccListKey = "service-accounts/"
|
||||
groupsListKey = "groups/"
|
||||
policiesListKey = "policies/"
|
||||
stsListKey = "sts/"
|
||||
policyDBUsersListKey = "policydb/users/"
|
||||
policyDBSTSUsersListKey = "policydb/sts-users/"
|
||||
policyDBGroupsListKey = "policydb/groups/"
|
||||
)
|
||||
|
||||
// splitPath splits a path into a top-level directory and a child item. The
|
||||
// parent directory retains the trailing slash.
|
||||
func splitPath(s string) (string, string) {
|
||||
i := strings.Index(s, "/")
|
||||
if i == -1 {
|
||||
return s, ""
|
||||
}
|
||||
// Include the trailing slash in the parent directory.
|
||||
return s[:i+1], s[i+1:]
|
||||
}
|
||||
|
||||
func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) {
|
||||
res := make(map[string][]string)
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
for _, listKey := range allListKeys {
|
||||
if iamLoadSkipListKeySet.Contains(listKey) {
|
||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator) {
|
||||
if item.Err != nil {
|
||||
return nil, item.Err
|
||||
}
|
||||
|
||||
listKey, trimmedItem := splitPath(item.Item)
|
||||
if listKey == iamFormatFile {
|
||||
continue
|
||||
}
|
||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+listKey) {
|
||||
if item.Err != nil {
|
||||
return nil, item.Err
|
||||
}
|
||||
res[listKey] = append(res[listKey], item.Item)
|
||||
}
|
||||
|
||||
res[listKey] = append(res[listKey], trimmedItem)
|
||||
}
|
||||
// Store the listing for later re-use.
|
||||
iamOS.cachedIAMListing.Store(res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@@ -453,22 +444,47 @@ func (iamOS *IAMObjectStore) PurgeExpiredSTS(ctx context.Context) error {
|
||||
}
|
||||
|
||||
bootstrapTraceMsg("purging expired STS credentials")
|
||||
|
||||
iamListing, ok := iamOS.cachedIAMListing.Load().(map[string][]string)
|
||||
if !ok {
|
||||
// There has been no store yet. This should never happen!
|
||||
logger.LogIf(GlobalContext, errors.New("WARNING: no cached IAM listing found"))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Scan STS users on disk and purge expired ones. We do not need to hold a
|
||||
// lock with store.lock() here.
|
||||
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator+stsListKey) {
|
||||
if item.Err != nil {
|
||||
return item.Err
|
||||
}
|
||||
userName := path.Dir(item.Item)
|
||||
// loadUser() will delete expired user during the load - we do not need
|
||||
// to keep the loaded user around in memory, so we reinitialize the map
|
||||
// each time.
|
||||
m := map[string]UserIdentity{}
|
||||
if err := iamOS.loadUser(ctx, userName, stsUser, m); err != nil && err != errNoSuchUser {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("unable to load user during STS purge: %w (%s)", err, item.Item))
|
||||
stsAccountsFromStore := map[string]UserIdentity{}
|
||||
stsAccPoliciesFromStore := xsync.NewMapOf[string, MappedPolicy]()
|
||||
for _, item := range iamListing[stsListKey] {
|
||||
userName := path.Dir(item)
|
||||
// loadUser() will delete expired user during the load.
|
||||
err := iamOS.loadUser(ctx, userName, stsUser, stsAccountsFromStore)
|
||||
if err != nil && !errors.Is(err, errNoSuchUser) {
|
||||
logger.LogIf(GlobalContext,
|
||||
fmt.Errorf("unable to load user during STS purge: %w (%s)", err, item))
|
||||
}
|
||||
|
||||
}
|
||||
// Loading the STS policy mappings from disk ensures that stale entries
|
||||
// (removed during loadUser() in the loop above) are removed from memory.
|
||||
for _, item := range iamListing[policyDBSTSUsersListKey] {
|
||||
stsName := strings.TrimSuffix(item, ".json")
|
||||
err := iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, stsAccPoliciesFromStore)
|
||||
if err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
logger.LogIf(GlobalContext,
|
||||
fmt.Errorf("unable to load policies during STS purge: %w (%s)", err, item))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Store the newly populated map in the iam cache. This takes care of
|
||||
// removing stale entries from the existing map.
|
||||
iamOS.Lock()
|
||||
defer iamOS.Unlock()
|
||||
iamOS.iamCache.iamSTSAccountsMap = stsAccountsFromStore
|
||||
iamOS.iamCache.iamSTSPolicyMap = stsAccPoliciesFromStore
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -629,10 +645,8 @@ type itemOrErr struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
// Lists files or dirs in the minioMetaBucket at the given path
|
||||
// prefix. If dirs is true, only directories are listed, otherwise
|
||||
// only objects are listed. All returned items have the pathPrefix
|
||||
// removed from their names.
|
||||
// Lists objects in the minioMetaBucket at the given path prefix. All returned
|
||||
// items have the pathPrefix removed from their names.
|
||||
func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix string) <-chan itemOrErr {
|
||||
ch := make(chan itemOrErr)
|
||||
|
||||
|
||||
+179
-109
@@ -35,6 +35,7 @@ import (
|
||||
"github.com/minio/minio/internal/jwt"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/policy"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -179,6 +180,16 @@ type MappedPolicy struct {
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
// mappedPoliciesToMap copies the map of mapped policies to a regular map.
|
||||
func mappedPoliciesToMap(m *xsync.MapOf[string, MappedPolicy]) map[string]MappedPolicy {
|
||||
policies := make(map[string]MappedPolicy, m.Size())
|
||||
m.Range(func(k string, v MappedPolicy) bool {
|
||||
policies[k] = v
|
||||
return true
|
||||
})
|
||||
return policies
|
||||
}
|
||||
|
||||
// converts a mapped policy into a slice of distinct policies
|
||||
func (mp MappedPolicy) toSlice() []string {
|
||||
var policies []string
|
||||
@@ -277,32 +288,32 @@ type iamCache struct {
|
||||
// map of regular username to credentials
|
||||
iamUsersMap map[string]UserIdentity
|
||||
// map of regular username to policy names
|
||||
iamUserPolicyMap map[string]MappedPolicy
|
||||
iamUserPolicyMap *xsync.MapOf[string, MappedPolicy]
|
||||
|
||||
// STS accounts are loaded on demand and not via the periodic IAM reload.
|
||||
// map of STS access key to credentials
|
||||
iamSTSAccountsMap map[string]UserIdentity
|
||||
// map of STS access key to policy names
|
||||
iamSTSPolicyMap map[string]MappedPolicy
|
||||
iamSTSPolicyMap *xsync.MapOf[string, MappedPolicy]
|
||||
|
||||
// map of group names to group info
|
||||
iamGroupsMap map[string]GroupInfo
|
||||
// map of user names to groups they are a member of
|
||||
iamUserGroupMemberships map[string]set.StringSet
|
||||
// map of group names to policy names
|
||||
iamGroupPolicyMap map[string]MappedPolicy
|
||||
iamGroupPolicyMap *xsync.MapOf[string, MappedPolicy]
|
||||
}
|
||||
|
||||
func newIamCache() *iamCache {
|
||||
return &iamCache{
|
||||
iamPolicyDocsMap: map[string]PolicyDoc{},
|
||||
iamUsersMap: map[string]UserIdentity{},
|
||||
iamUserPolicyMap: map[string]MappedPolicy{},
|
||||
iamUserPolicyMap: xsync.NewMapOf[string, MappedPolicy](),
|
||||
iamSTSAccountsMap: map[string]UserIdentity{},
|
||||
iamSTSPolicyMap: map[string]MappedPolicy{},
|
||||
iamSTSPolicyMap: xsync.NewMapOf[string, MappedPolicy](),
|
||||
iamGroupsMap: map[string]GroupInfo{},
|
||||
iamUserGroupMemberships: map[string]set.StringSet{},
|
||||
iamGroupPolicyMap: map[string]MappedPolicy{},
|
||||
iamGroupPolicyMap: xsync.NewMapOf[string, MappedPolicy](),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +370,13 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
g, ok := c.iamGroupsMap[name]
|
||||
if !ok {
|
||||
return nil, time.Time{}, errNoSuchGroup
|
||||
if err := store.loadGroup(context.Background(), name, c.iamGroupsMap); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
g, ok = c.iamGroupsMap[name]
|
||||
if !ok {
|
||||
return nil, time.Time{}, errNoSuchGroup
|
||||
}
|
||||
}
|
||||
|
||||
// Group is disabled, so we return no policy - this
|
||||
@@ -369,7 +386,15 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
|
||||
}
|
||||
}
|
||||
|
||||
return c.iamGroupPolicyMap[name].toSlice(), c.iamGroupPolicyMap[name].UpdatedAt, nil
|
||||
policy, ok := c.iamGroupPolicyMap.Load(name)
|
||||
if ok {
|
||||
return policy.toSlice(), policy.UpdatedAt, nil
|
||||
}
|
||||
if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
policy, _ = c.iamGroupPolicyMap.Load(name)
|
||||
return policy.toSlice(), policy.UpdatedAt, nil
|
||||
}
|
||||
|
||||
// When looking for a user's policies, we also check if the user
|
||||
@@ -384,15 +409,24 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
|
||||
// For internal IDP regular/service account user accounts, the policy
|
||||
// mapping is iamUserPolicyMap. For STS accounts, the parent user would be
|
||||
// passed here and we lookup the mapping in iamSTSPolicyMap.
|
||||
mp, ok := c.iamUserPolicyMap[name]
|
||||
mp, ok := c.iamUserPolicyMap.Load(name)
|
||||
if !ok {
|
||||
// Since user "name" could be a parent user of an STS account, we lookup
|
||||
// mappings for those too.
|
||||
mp, ok = c.iamSTSPolicyMap[name]
|
||||
if err := store.loadMappedPolicyWithRetry(context.TODO(), name, regUser, false, c.iamUserPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
|
||||
mp, ok = c.iamUserPolicyMap.Load(name)
|
||||
if !ok {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap)
|
||||
mp = c.iamSTSPolicyMap[name]
|
||||
// Since user "name" could be a parent user of an STS account, we look up
|
||||
// mappings for those too.
|
||||
mp, ok = c.iamSTSPolicyMap.Load(name)
|
||||
if !ok {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
if err := store.loadMappedPolicyWithRetry(context.TODO(), name, stsUser, false, c.iamSTSPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
mp, _ = c.iamSTSPolicyMap.Load(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,13 +434,34 @@ func (c *iamCache) policyDBGet(store *IAMStoreSys, name string, isGroup bool) ([
|
||||
policies := mp.toSlice()
|
||||
|
||||
for _, group := range c.iamUserGroupMemberships[name].ToSlice() {
|
||||
// Skip missing or disabled groups
|
||||
gi, ok := c.iamGroupsMap[group]
|
||||
if !ok || gi.Status == statusDisabled {
|
||||
continue
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
g, ok := c.iamGroupsMap[group]
|
||||
if !ok {
|
||||
if err := store.loadGroup(context.Background(), group, c.iamGroupsMap); err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
g, ok = c.iamGroupsMap[group]
|
||||
if !ok {
|
||||
return nil, time.Time{}, errNoSuchGroup
|
||||
}
|
||||
}
|
||||
|
||||
// Group is disabled, so we return no policy - this
|
||||
// ensures the request is denied.
|
||||
if g.Status == statusDisabled {
|
||||
return nil, time.Time{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
policies = append(policies, c.iamGroupPolicyMap[group].toSlice()...)
|
||||
policy, ok := c.iamGroupPolicyMap.Load(group)
|
||||
if !ok {
|
||||
if err := store.loadMappedPolicyWithRetry(context.TODO(), group, regUser, true, c.iamGroupPolicyMap, 3); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
policy, _ = c.iamGroupPolicyMap.Load(group)
|
||||
}
|
||||
|
||||
policies = append(policies, policy.toSlice()...)
|
||||
}
|
||||
|
||||
return policies, mp.UpdatedAt, nil
|
||||
@@ -447,9 +502,9 @@ type IAMStorageAPI interface {
|
||||
loadUsers(ctx context.Context, userType IAMUserType, m map[string]UserIdentity) error
|
||||
loadGroup(ctx context.Context, group string, m map[string]GroupInfo) error
|
||||
loadGroups(ctx context.Context, m map[string]GroupInfo) error
|
||||
loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
|
||||
loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m map[string]MappedPolicy, retries int) error
|
||||
loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m map[string]MappedPolicy) error
|
||||
loadMappedPolicy(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error
|
||||
loadMappedPolicyWithRetry(ctx context.Context, name string, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy], retries int) error
|
||||
loadMappedPolicies(ctx context.Context, userType IAMUserType, isGroup bool, m *xsync.MapOf[string, MappedPolicy]) error
|
||||
saveIAMConfig(ctx context.Context, item interface{}, path string, opts ...options) error
|
||||
loadIAMConfig(ctx context.Context, item interface{}, path string) error
|
||||
deleteIAMConfig(ctx context.Context, path string) error
|
||||
@@ -574,9 +629,10 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) erro
|
||||
// here is to account for STS policy mapping changes that should apply
|
||||
// for service accounts derived from such STS accounts (i.e. LDAP STS
|
||||
// accounts).
|
||||
for k, v := range newCache.iamSTSPolicyMap {
|
||||
cache.iamSTSPolicyMap[k] = v
|
||||
}
|
||||
newCache.iamSTSPolicyMap.Range(func(k string, v MappedPolicy) bool {
|
||||
cache.iamSTSPolicyMap.Store(k, v)
|
||||
return true
|
||||
})
|
||||
|
||||
cache.updatedAt = time.Now()
|
||||
}
|
||||
@@ -615,12 +671,10 @@ func (store *IAMStoreSys) GetMappedPolicy(name string, isGroup bool) (MappedPoli
|
||||
defer store.runlock()
|
||||
|
||||
if isGroup {
|
||||
v, ok := cache.iamGroupPolicyMap[name]
|
||||
v, ok := cache.iamGroupPolicyMap.Load(name)
|
||||
return v, ok
|
||||
}
|
||||
|
||||
v, ok := cache.iamUserPolicyMap[name]
|
||||
return v, ok
|
||||
return cache.iamUserPolicyMap.Load(name)
|
||||
}
|
||||
|
||||
// GroupNotificationHandler - updates in-memory cache on notification of
|
||||
@@ -639,7 +693,7 @@ func (store *IAMStoreSys) GroupNotificationHandler(ctx context.Context, group st
|
||||
// group does not exist - so remove from memory.
|
||||
cache.removeGroupFromMembershipsMap(group)
|
||||
delete(cache.iamGroupsMap, group)
|
||||
delete(cache.iamGroupPolicyMap, group)
|
||||
cache.iamGroupPolicyMap.Delete(group)
|
||||
|
||||
cache.updatedAt = time.Now()
|
||||
return nil
|
||||
@@ -818,7 +872,7 @@ func (store *IAMStoreSys) RemoveUsersFromGroup(ctx context.Context, group string
|
||||
|
||||
// Delete from server memory
|
||||
delete(cache.iamGroupsMap, group)
|
||||
delete(cache.iamGroupPolicyMap, group)
|
||||
cache.iamGroupPolicyMap.Delete(group)
|
||||
cache.updatedAt = time.Now()
|
||||
return cache.updatedAt, nil
|
||||
}
|
||||
@@ -910,16 +964,17 @@ func (store *IAMStoreSys) ListGroups(ctx context.Context) (res []string, err err
|
||||
}
|
||||
|
||||
if store.getUsersSysType() == LDAPUsersSysType {
|
||||
m := map[string]MappedPolicy{}
|
||||
m := xsync.NewMapOf[string, MappedPolicy]()
|
||||
err = store.loadMappedPolicies(ctx, stsUser, true, m)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cache.iamGroupPolicyMap = m
|
||||
cache.updatedAt = time.Now()
|
||||
for k := range cache.iamGroupPolicyMap {
|
||||
cache.iamGroupPolicyMap.Range(func(k string, v MappedPolicy) bool {
|
||||
res = append(res, k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
@@ -937,9 +992,10 @@ func (store *IAMStoreSys) listGroups(ctx context.Context) (res []string, err err
|
||||
}
|
||||
|
||||
if store.getUsersSysType() == LDAPUsersSysType {
|
||||
for k := range cache.iamGroupPolicyMap {
|
||||
cache.iamGroupPolicyMap.Range(func(k string, _ MappedPolicy) bool {
|
||||
res = append(res, k)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -962,14 +1018,14 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
||||
var mp MappedPolicy
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
stsMap := map[string]MappedPolicy{}
|
||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), name, stsUser, false, stsMap)
|
||||
|
||||
mp = stsMap[name]
|
||||
mp, _ = stsMap.Load(name)
|
||||
} else {
|
||||
mp = cache.iamUserPolicyMap[name]
|
||||
mp, _ = cache.iamUserPolicyMap.Load(name)
|
||||
}
|
||||
} else {
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
@@ -984,7 +1040,7 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
||||
return
|
||||
}
|
||||
}
|
||||
mp = cache.iamGroupPolicyMap[name]
|
||||
mp, _ = cache.iamGroupPolicyMap.Load(name)
|
||||
}
|
||||
|
||||
// Compute net policy change effect and updated policy mapping
|
||||
@@ -1027,12 +1083,12 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
delete(cache.iamSTSPolicyMap, name)
|
||||
cache.iamSTSPolicyMap.Delete(name)
|
||||
} else {
|
||||
delete(cache.iamUserPolicyMap, name)
|
||||
cache.iamUserPolicyMap.Delete(name)
|
||||
}
|
||||
} else {
|
||||
delete(cache.iamGroupPolicyMap, name)
|
||||
cache.iamGroupPolicyMap.Delete(name)
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1041,12 +1097,12 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
cache.iamSTSPolicyMap[name] = newPolicyMapping
|
||||
cache.iamSTSPolicyMap.Store(name, newPolicyMapping)
|
||||
} else {
|
||||
cache.iamUserPolicyMap[name] = newPolicyMapping
|
||||
cache.iamUserPolicyMap.Store(name, newPolicyMapping)
|
||||
}
|
||||
} else {
|
||||
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
||||
cache.iamGroupPolicyMap.Store(name, newPolicyMapping)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1081,12 +1137,12 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
delete(cache.iamSTSPolicyMap, name)
|
||||
cache.iamSTSPolicyMap.Delete(name)
|
||||
} else {
|
||||
delete(cache.iamUserPolicyMap, name)
|
||||
cache.iamUserPolicyMap.Delete(name)
|
||||
}
|
||||
} else {
|
||||
delete(cache.iamGroupPolicyMap, name)
|
||||
cache.iamGroupPolicyMap.Delete(name)
|
||||
}
|
||||
cache.updatedAt = time.Now()
|
||||
return cache.updatedAt, nil
|
||||
@@ -1105,12 +1161,12 @@ func (store *IAMStoreSys) PolicyDBSet(ctx context.Context, name, policy string,
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
cache.iamSTSPolicyMap[name] = mp
|
||||
cache.iamSTSPolicyMap.Store(name, mp)
|
||||
} else {
|
||||
cache.iamUserPolicyMap[name] = mp
|
||||
cache.iamUserPolicyMap.Store(name, mp)
|
||||
}
|
||||
} else {
|
||||
cache.iamGroupPolicyMap[name] = mp
|
||||
cache.iamGroupPolicyMap.Store(name, mp)
|
||||
}
|
||||
cache.updatedAt = time.Now()
|
||||
return mp.UpdatedAt, nil
|
||||
@@ -1134,33 +1190,35 @@ func (store *IAMStoreSys) PolicyNotificationHandler(ctx context.Context, policy
|
||||
delete(cache.iamPolicyDocsMap, policy)
|
||||
|
||||
// update user policy map
|
||||
for u, mp := range cache.iamUserPolicyMap {
|
||||
cache.iamUserPolicyMap.Range(func(u string, mp MappedPolicy) bool {
|
||||
pset := mp.policySet()
|
||||
if !pset.Contains(policy) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
_, ok := cache.iamUsersMap[u]
|
||||
if !ok {
|
||||
// happens when account is deleted or
|
||||
// expired.
|
||||
delete(cache.iamUserPolicyMap, u)
|
||||
continue
|
||||
cache.iamUserPolicyMap.Delete(u)
|
||||
return true
|
||||
}
|
||||
}
|
||||
pset.Remove(policy)
|
||||
cache.iamUserPolicyMap[u] = newMappedPolicy(strings.Join(pset.ToSlice(), ","))
|
||||
}
|
||||
cache.iamUserPolicyMap.Store(u, newMappedPolicy(strings.Join(pset.ToSlice(), ",")))
|
||||
return true
|
||||
})
|
||||
|
||||
// update group policy map
|
||||
for g, mp := range cache.iamGroupPolicyMap {
|
||||
cache.iamGroupPolicyMap.Range(func(g string, mp MappedPolicy) bool {
|
||||
pset := mp.policySet()
|
||||
if !pset.Contains(policy) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
pset.Remove(policy)
|
||||
cache.iamGroupPolicyMap[g] = newMappedPolicy(strings.Join(pset.ToSlice(), ","))
|
||||
}
|
||||
cache.iamGroupPolicyMap.Store(g, newMappedPolicy(strings.Join(pset.ToSlice(), ",")))
|
||||
return true
|
||||
})
|
||||
|
||||
cache.updatedAt = time.Now()
|
||||
return nil
|
||||
@@ -1186,26 +1244,28 @@ func (store *IAMStoreSys) DeletePolicy(ctx context.Context, policy string, isFro
|
||||
// we do allow deletion.
|
||||
users := []string{}
|
||||
groups := []string{}
|
||||
for u, mp := range cache.iamUserPolicyMap {
|
||||
cache.iamUserPolicyMap.Range(func(u string, mp MappedPolicy) bool {
|
||||
pset := mp.policySet()
|
||||
if store.getUsersSysType() == MinIOUsersSysType {
|
||||
if _, ok := cache.iamUsersMap[u]; !ok {
|
||||
// This case can happen when a temporary account is
|
||||
// deleted or expired - remove it from userPolicyMap.
|
||||
delete(cache.iamUserPolicyMap, u)
|
||||
continue
|
||||
cache.iamUserPolicyMap.Delete(u)
|
||||
return true
|
||||
}
|
||||
}
|
||||
if pset.Contains(policy) {
|
||||
users = append(users, u)
|
||||
}
|
||||
}
|
||||
for g, mp := range cache.iamGroupPolicyMap {
|
||||
return true
|
||||
})
|
||||
cache.iamGroupPolicyMap.Range(func(g string, mp MappedPolicy) bool {
|
||||
pset := mp.policySet()
|
||||
if pset.Contains(policy) {
|
||||
groups = append(groups, g)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
if len(users) != 0 || len(groups) != 0 {
|
||||
return errPolicyInUse
|
||||
}
|
||||
@@ -1422,11 +1482,11 @@ func (store *IAMStoreSys) GetBucketUsers(bucket string) (map[string]madmin.UserI
|
||||
continue
|
||||
}
|
||||
var policies []string
|
||||
mp, ok := cache.iamUserPolicyMap[k]
|
||||
mp, ok := cache.iamUserPolicyMap.Load(k)
|
||||
if ok {
|
||||
policies = append(policies, mp.Policies)
|
||||
for _, group := range cache.iamUserGroupMemberships[k].ToSlice() {
|
||||
if nmp, ok := cache.iamGroupPolicyMap[group]; ok {
|
||||
if nmp, ok := cache.iamGroupPolicyMap.Load(group); ok {
|
||||
policies = append(policies, nmp.Policies)
|
||||
}
|
||||
}
|
||||
@@ -1461,8 +1521,9 @@ func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo {
|
||||
if v.IsTemp() || v.IsServiceAccount() {
|
||||
continue
|
||||
}
|
||||
pl, _ := cache.iamUserPolicyMap.Load(k)
|
||||
result[k] = madmin.UserInfo{
|
||||
PolicyName: cache.iamUserPolicyMap[k].Policies,
|
||||
PolicyName: pl.Policies,
|
||||
Status: func() madmin.AccountStatus {
|
||||
if v.IsValid() {
|
||||
return madmin.AccountEnabled
|
||||
@@ -1470,7 +1531,7 @@ func (store *IAMStoreSys) GetUsers() map[string]madmin.UserInfo {
|
||||
return madmin.AccountDisabled
|
||||
}(),
|
||||
MemberOf: cache.iamUserGroupMemberships[k].ToSlice(),
|
||||
UpdatedAt: cache.iamUserPolicyMap[k].UpdatedAt,
|
||||
UpdatedAt: pl.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1483,12 +1544,14 @@ func (store *IAMStoreSys) GetUsersWithMappedPolicies() map[string]string {
|
||||
defer store.runlock()
|
||||
|
||||
result := make(map[string]string)
|
||||
for k, v := range cache.iamUserPolicyMap {
|
||||
cache.iamUserPolicyMap.Range(func(k string, v MappedPolicy) bool {
|
||||
result[k] = v.Policies
|
||||
}
|
||||
for k, v := range cache.iamSTSPolicyMap {
|
||||
return true
|
||||
})
|
||||
cache.iamSTSPolicyMap.Range(func(k string, v MappedPolicy) bool {
|
||||
result[k] = v.Policies
|
||||
}
|
||||
return true
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1517,14 +1580,14 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
|
||||
break
|
||||
}
|
||||
}
|
||||
mappedPolicy, ok := cache.iamUserPolicyMap[name]
|
||||
mappedPolicy, ok := cache.iamUserPolicyMap.Load(name)
|
||||
if !ok {
|
||||
mappedPolicy, ok = cache.iamSTSPolicyMap[name]
|
||||
mappedPolicy, ok = cache.iamSTSPolicyMap.Load(name)
|
||||
}
|
||||
if !ok {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), name, stsUser, false, cache.iamSTSPolicyMap)
|
||||
mappedPolicy, ok = cache.iamSTSPolicyMap[name]
|
||||
mappedPolicy, ok = cache.iamSTSPolicyMap.Load(name)
|
||||
if !ok {
|
||||
return u, errNoSuchUser
|
||||
}
|
||||
@@ -1545,9 +1608,9 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
|
||||
if cred.IsTemp() || cred.IsServiceAccount() {
|
||||
return u, errIAMActionNotAllowed
|
||||
}
|
||||
|
||||
pl, _ := cache.iamUserPolicyMap.Load(name)
|
||||
return madmin.UserInfo{
|
||||
PolicyName: cache.iamUserPolicyMap[name].Policies,
|
||||
PolicyName: pl.Policies,
|
||||
Status: func() madmin.AccountStatus {
|
||||
if cred.IsValid() {
|
||||
return madmin.AccountEnabled
|
||||
@@ -1555,7 +1618,7 @@ func (store *IAMStoreSys) GetUserInfo(name string) (u madmin.UserInfo, err error
|
||||
return madmin.AccountDisabled
|
||||
}(),
|
||||
MemberOf: cache.iamUserGroupMemberships[name].ToSlice(),
|
||||
UpdatedAt: cache.iamUserPolicyMap[name].UpdatedAt,
|
||||
UpdatedAt: pl.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1568,7 +1631,7 @@ func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context,
|
||||
cache := store.lock()
|
||||
defer store.unlock()
|
||||
|
||||
var m map[string]MappedPolicy
|
||||
var m *xsync.MapOf[string, MappedPolicy]
|
||||
switch {
|
||||
case isGroup:
|
||||
m = cache.iamGroupPolicyMap
|
||||
@@ -1579,7 +1642,7 @@ func (store *IAMStoreSys) PolicyMappingNotificationHandler(ctx context.Context,
|
||||
if errors.Is(err, errNoSuchPolicy) {
|
||||
// This means that the policy mapping was deleted, so we update
|
||||
// the cache.
|
||||
delete(m, userOrGroup)
|
||||
m.Delete(userOrGroup)
|
||||
cache.updatedAt = time.Now()
|
||||
|
||||
err = nil
|
||||
@@ -1644,7 +1707,7 @@ func (store *IAMStoreSys) UserNotificationHandler(ctx context.Context, accessKey
|
||||
}
|
||||
|
||||
// 3. Delete any mapped policy
|
||||
delete(cache.iamUserPolicyMap, accessKey)
|
||||
cache.iamUserPolicyMap.Delete(accessKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1738,7 +1801,7 @@ func (store *IAMStoreSys) DeleteUser(ctx context.Context, accessKey string, user
|
||||
|
||||
// It is ok to ignore deletion error on the mapped policy
|
||||
store.deleteMappedPolicy(ctx, accessKey, userType, false)
|
||||
delete(cache.iamUserPolicyMap, accessKey)
|
||||
cache.iamUserPolicyMap.Delete(accessKey)
|
||||
|
||||
err := store.deleteUserIdentity(ctx, accessKey, userType)
|
||||
if err == errNoSuchUser {
|
||||
@@ -1778,7 +1841,7 @@ func (store *IAMStoreSys) SetTempUser(ctx context.Context, accessKey string, cre
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
cache.iamSTSPolicyMap[cred.ParentUser] = mp
|
||||
cache.iamSTSPolicyMap.Store(cred.ParentUser, mp)
|
||||
}
|
||||
|
||||
u := newUserIdentity(cred)
|
||||
@@ -1815,7 +1878,7 @@ func (store *IAMStoreSys) DeleteUsers(ctx context.Context, users []string) error
|
||||
if usersToDelete.Contains(user) || usersToDelete.Contains(cred.ParentUser) {
|
||||
// Delete this user account and its policy mapping
|
||||
store.deleteMappedPolicy(ctx, user, userType, false)
|
||||
delete(cache.iamUserPolicyMap, user)
|
||||
cache.iamUserPolicyMap.Delete(user)
|
||||
|
||||
// we are only logging errors, not handling them.
|
||||
err := store.deleteUserIdentity(ctx, user, userType)
|
||||
@@ -1917,13 +1980,13 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
|
||||
) []madmin.UserPolicyEntities {
|
||||
var r []madmin.UserPolicyEntities
|
||||
usersSet := set.CreateStringSet(users...)
|
||||
for user, mappedPolicy := range cache.iamUserPolicyMap {
|
||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
if !usersSet.IsEmpty() && !usersSet.Contains(user) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
@@ -1932,17 +1995,18 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
|
||||
User: user,
|
||||
Policies: ps,
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
stsMap := map[string]MappedPolicy{}
|
||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
for _, user := range users {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||
}
|
||||
|
||||
for user, mappedPolicy := range stsMap {
|
||||
stsMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
@@ -1951,7 +2015,8 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
|
||||
User: user,
|
||||
Policies: ps,
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].User < r[j].User
|
||||
@@ -1966,13 +2031,13 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
|
||||
) []madmin.GroupPolicyEntities {
|
||||
var r []madmin.GroupPolicyEntities
|
||||
groupsSet := set.CreateStringSet(groups...)
|
||||
for group, mappedPolicy := range cache.iamGroupPolicyMap {
|
||||
cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool {
|
||||
if groupPredicate != nil && !groupPredicate(group) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
if !groupsSet.IsEmpty() && !groupsSet.Contains(group) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
@@ -1981,7 +2046,8 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
|
||||
Group: group,
|
||||
Policies: ps,
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].Group < r[j].Group
|
||||
@@ -1997,9 +2063,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
queryPolSet := set.CreateStringSet(policies...)
|
||||
|
||||
policyToUsersMap := make(map[string]set.StringSet)
|
||||
for user, mappedPolicy := range cache.iamUserPolicyMap {
|
||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
commonPolicySet := mappedPolicy.policySet()
|
||||
@@ -2015,7 +2081,8 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
policyToUsersMap[policy] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if iamOS, ok := store.IAMStorageAPI.(*IAMObjectStore); ok {
|
||||
for item := range listIAMConfigItems(context.Background(), iamOS.objAPI, iamConfigPrefix+SlashSeparator+policyDBSTSUsersListKey) {
|
||||
@@ -2044,9 +2111,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
}
|
||||
|
||||
policyToGroupsMap := make(map[string]set.StringSet)
|
||||
for group, mappedPolicy := range cache.iamGroupPolicyMap {
|
||||
cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool {
|
||||
if groupPredicate != nil && !groupPredicate(group) {
|
||||
continue
|
||||
return true
|
||||
}
|
||||
|
||||
commonPolicySet := mappedPolicy.policySet()
|
||||
@@ -2062,7 +2129,8 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
policyToGroupsMap[policy] = s
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
m := make(map[string]madmin.PolicyEntities, len(policyToGroupsMap))
|
||||
for policy, groups := range policyToGroupsMap {
|
||||
@@ -2520,13 +2588,15 @@ func (store *IAMStoreSys) LoadUser(ctx context.Context, accessKey string) {
|
||||
|
||||
// Load any associated policy definitions
|
||||
if !stsAccountFound {
|
||||
for _, policy := range cache.iamUserPolicyMap[accessKey].toSlice() {
|
||||
pols, _ := cache.iamUserPolicyMap.Load(accessKey)
|
||||
for _, policy := range pols.toSlice() {
|
||||
if _, found = cache.iamPolicyDocsMap[policy]; !found {
|
||||
store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, policy := range cache.iamSTSPolicyMap[stsUserCred.Credentials.AccessKey].toSlice() {
|
||||
pols, _ := cache.iamSTSPolicyMap.Load(stsUserCred.Credentials.AccessKey)
|
||||
for _, policy := range pols.toSlice() {
|
||||
if _, found = cache.iamPolicyDocsMap[policy]; !found {
|
||||
store.loadPolicyDocWithRetry(ctx, policy, cache.iamPolicyDocsMap, 3)
|
||||
}
|
||||
|
||||
+39
-24
@@ -362,13 +362,24 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
// Add a random interval of up to 20% of the base interval.
|
||||
randInterval := func() time.Duration {
|
||||
return time.Duration(r.Float64() * float64(baseInterval) * 0.2)
|
||||
|
||||
// Calculate the waitInterval between periodic refreshes so that each server
|
||||
// independently picks a (uniformly distributed) random time in an interval
|
||||
// of size = baseInterval.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// - if baseInterval=10s, then 5s <= waitInterval() < 15s
|
||||
//
|
||||
// - if baseInterval=10m, then 5m <= waitInterval() < 15m
|
||||
waitInterval := func() time.Duration {
|
||||
// Calculate a random value such that 0 <= value < baseInterval
|
||||
randAmt := time.Duration(r.Float64() * float64(baseInterval))
|
||||
return baseInterval/2 + randAmt
|
||||
}
|
||||
|
||||
var maxDurationSecondsForLog float64 = 5
|
||||
timer := time.NewTimer(baseInterval + randInterval())
|
||||
timer := time.NewTimer(waitInterval())
|
||||
defer timer.Stop()
|
||||
|
||||
for {
|
||||
@@ -386,21 +397,21 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
||||
}
|
||||
}
|
||||
|
||||
// Purge expired STS credentials.
|
||||
purgeStart := time.Now()
|
||||
if err := sys.store.PurgeExpiredSTS(ctx); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Failure in periodic STS purge for IAM (took %.2fs): %v", time.Since(purgeStart).Seconds(), err))
|
||||
} else {
|
||||
took := time.Since(purgeStart).Seconds()
|
||||
if took > maxDurationSecondsForLog {
|
||||
// Log if we took a lot of time to load.
|
||||
logger.Info("IAM expired STS purge took %.2fs", took)
|
||||
}
|
||||
}
|
||||
|
||||
// The following actions are performed about once in 4 times that
|
||||
// IAM is refreshed:
|
||||
if r.Intn(4) == 0 {
|
||||
// Purge expired STS credentials.
|
||||
purgeStart := time.Now()
|
||||
if err := sys.store.PurgeExpiredSTS(ctx); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Failure in periodic STS purge for IAM (took %.2fs): %v", time.Since(purgeStart).Seconds(), err))
|
||||
} else {
|
||||
took := time.Since(purgeStart).Seconds()
|
||||
if took > maxDurationSecondsForLog {
|
||||
// Log if we took a lot of time to load.
|
||||
logger.Info("IAM expired STS purge took %.2fs", took)
|
||||
}
|
||||
}
|
||||
|
||||
// Poll and remove accounts for those users who were removed
|
||||
// from LDAP/OpenID.
|
||||
if sys.LDAPConfig.Enabled() {
|
||||
@@ -412,7 +423,7 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
||||
}
|
||||
}
|
||||
|
||||
timer.Reset(baseInterval + randInterval())
|
||||
timer.Reset(waitInterval())
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
@@ -1701,7 +1712,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
||||
var dn string
|
||||
var isGroup bool
|
||||
if r.User != "" {
|
||||
dn, err = sys.LDAPConfig.DoesUsernameExist(r.User)
|
||||
dn, err = sys.LDAPConfig.GetValidatedDNForUsername(r.User)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
@@ -1718,22 +1729,26 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
||||
isGroup = false
|
||||
} else {
|
||||
if isAttach {
|
||||
var exists bool
|
||||
if exists, err = sys.LDAPConfig.DoesGroupDNExist(r.Group); err != nil {
|
||||
var foundGroupDN string
|
||||
if foundGroupDN, err = sys.LDAPConfig.GetValidatedGroupDN(r.Group); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
} else if !exists {
|
||||
} else if foundGroupDN == "" {
|
||||
err = errNoSuchGroup
|
||||
return
|
||||
}
|
||||
// We use the group DN returned by the LDAP server (this may not
|
||||
// equal the input group name, but we assume it is canonical).
|
||||
dn = foundGroupDN
|
||||
} else {
|
||||
dn = r.Group
|
||||
}
|
||||
dn = r.Group
|
||||
isGroup = true
|
||||
}
|
||||
|
||||
userType := stsUser
|
||||
updatedAt, addedOrRemoved, effectivePolicies, err = sys.store.PolicyDBUpdate(ctx, dn, isGroup,
|
||||
userType, r.Policies, isAttach)
|
||||
updatedAt, addedOrRemoved, effectivePolicies, err = sys.store.PolicyDBUpdate(
|
||||
ctx, dn, isGroup, userType, r.Policies, isAttach)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ func (z *localLockMap) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
delete((*z), key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0004 > 0 {
|
||||
zb0004--
|
||||
var zb0001 string
|
||||
@@ -115,6 +117,8 @@ func (z *localLockMap) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
delete((*z), key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0004 > 0 {
|
||||
var zb0001 string
|
||||
var zb0002 []lockRequesterInfo
|
||||
|
||||
@@ -473,6 +473,8 @@ type metaCacheEntriesSorted struct {
|
||||
listID string
|
||||
// Reuse buffers
|
||||
reuse bool
|
||||
// Contain the last skipped object after an ILM expiry evaluation
|
||||
lastSkippedEntry string
|
||||
}
|
||||
|
||||
// shallowClone will create a shallow clone of the array objects,
|
||||
|
||||
@@ -291,14 +291,6 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// Do lifecycle filtering.
|
||||
if o.Lifecycle != nil || o.Replication.Config != nil {
|
||||
filterIn := make(chan metaCacheEntry, 10)
|
||||
go applyBucketActions(ctx, o, filterIn, results)
|
||||
// Replace results.
|
||||
results = filterIn
|
||||
}
|
||||
|
||||
// Gather results to a single channel.
|
||||
// Quorum is one since we are merging across sets.
|
||||
err := mergeEntryChannels(ctx, inputs, results, 1)
|
||||
@@ -339,84 +331,50 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyBucketActions applies lifecycle and replication actions on the listing
|
||||
// It will filter out objects if the most recent version should be deleted by lifecycle.
|
||||
// Entries that failed replication will be queued if no lifecycle rules got applied.
|
||||
// out will be closed when there are no more results.
|
||||
// When 'in' is closed or the context is canceled the
|
||||
// function closes 'out' and exits.
|
||||
func applyBucketActions(ctx context.Context, o listPathOptions, in <-chan metaCacheEntry, out chan<- metaCacheEntry) {
|
||||
defer xioutil.SafeClose(out)
|
||||
// triggerExpiryAndRepl applies lifecycle and replication actions on the listing
|
||||
// It returns true if the listing is non-versioned and the given object is expired.
|
||||
func triggerExpiryAndRepl(ctx context.Context, o listPathOptions, obj metaCacheEntry) (skip bool) {
|
||||
versioned := o.Versioning != nil && o.Versioning.Versioned(obj.name)
|
||||
|
||||
for {
|
||||
var obj metaCacheEntry
|
||||
var ok bool
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case obj, ok = <-in:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var skip bool
|
||||
|
||||
versioned := o.Versioning != nil && o.Versioning.Versioned(obj.name)
|
||||
|
||||
// skip latest object from listing only for regular
|
||||
// listObjects calls, versioned based listing cannot
|
||||
// filter out between versions 'obj' cannot be truncated
|
||||
// in such a manner, so look for skipping an object only
|
||||
// for regular ListObjects() call only.
|
||||
if !o.Versioned {
|
||||
fi, err := obj.fileInfo(o.Bucket)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned)
|
||||
if o.Lifecycle != nil {
|
||||
act := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, o.Replication.Config, objInfo).Action
|
||||
skip = act.Delete()
|
||||
if act.DeleteRestored() {
|
||||
// do not skip DeleteRestored* actions
|
||||
skip = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip entry only if needed via ILM, skipping is never true for versioned listing.
|
||||
if !skip {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case out <- obj:
|
||||
}
|
||||
}
|
||||
|
||||
fiv, err := obj.fileInfoVersions(o.Bucket)
|
||||
// skip latest object from listing only for regular
|
||||
// listObjects calls, versioned based listing cannot
|
||||
// filter out between versions 'obj' cannot be truncated
|
||||
// in such a manner, so look for skipping an object only
|
||||
// for regular ListObjects() call only.
|
||||
if !o.Versioned && !o.V1 {
|
||||
fi, err := obj.fileInfo(o.Bucket)
|
||||
if err != nil {
|
||||
continue
|
||||
return
|
||||
}
|
||||
|
||||
// Expire all versions if needed, if not attempt to queue for replication.
|
||||
for _, version := range fiv.Versions {
|
||||
objInfo := version.ToObjectInfo(o.Bucket, obj.name, versioned)
|
||||
|
||||
if o.Lifecycle != nil {
|
||||
evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, o.Replication.Config, objInfo)
|
||||
if evt.Action.Delete() {
|
||||
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_s3ListObjects)
|
||||
if !evt.Action.DeleteRestored() {
|
||||
continue
|
||||
} // queue version for replication upon expired restored copies if needed.
|
||||
}
|
||||
}
|
||||
|
||||
queueReplicationHeal(ctx, o.Bucket, objInfo, o.Replication, 0)
|
||||
objInfo := fi.ToObjectInfo(o.Bucket, obj.name, versioned)
|
||||
if o.Lifecycle != nil {
|
||||
act := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, o.Replication.Config, objInfo).Action
|
||||
skip = act.Delete() && !act.DeleteRestored()
|
||||
}
|
||||
}
|
||||
|
||||
fiv, err := obj.fileInfoVersions(o.Bucket)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Expire all versions if needed, if not attempt to queue for replication.
|
||||
for _, version := range fiv.Versions {
|
||||
objInfo := version.ToObjectInfo(o.Bucket, obj.name, versioned)
|
||||
|
||||
if o.Lifecycle != nil {
|
||||
evt := evalActionFromLifecycle(ctx, *o.Lifecycle, o.Retention, o.Replication.Config, objInfo)
|
||||
if evt.Action.Delete() {
|
||||
globalExpiryState.enqueueByDays(objInfo, evt, lcEventSrc_s3ListObjects)
|
||||
if !evt.Action.DeleteRestored() {
|
||||
continue
|
||||
} // queue version for replication upon expired restored copies if needed.
|
||||
}
|
||||
}
|
||||
|
||||
queueReplicationHeal(ctx, o.Bucket, objInfo, o.Replication, 0)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) listAndSave(ctx context.Context, o *listPathOptions) (entries metaCacheEntriesSorted, err error) {
|
||||
|
||||
+10
-1
@@ -98,6 +98,8 @@ type listPathOptions struct {
|
||||
|
||||
// Versioned is this a ListObjectVersions call.
|
||||
Versioned bool
|
||||
// V1 listing type
|
||||
V1 bool
|
||||
|
||||
// Versioning config is used for if the path
|
||||
// has versioning enabled.
|
||||
@@ -172,7 +174,8 @@ func (o *listPathOptions) debugln(data ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// gatherResults will collect all results on the input channel and filter results according to the options.
|
||||
// gatherResults will collect all results on the input channel and filter results according
|
||||
// to the options or to the current bucket ILM expiry rules.
|
||||
// Caller should close the channel when done.
|
||||
// The returned function will return the results once there is enough or input is closed,
|
||||
// or the context is canceled.
|
||||
@@ -214,6 +217,12 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
|
||||
if !o.InclDeleted && entry.isObject() && entry.isLatestDeletemarker() && !entry.isObjectDir() {
|
||||
continue
|
||||
}
|
||||
if o.Lifecycle != nil || o.Replication.Config != nil {
|
||||
if skipped := triggerExpiryAndRepl(ctx, *o, entry); skipped == true {
|
||||
results.lastSkippedEntry = entry.name
|
||||
continue
|
||||
}
|
||||
}
|
||||
if o.Limit > 0 && results.len() >= o.Limit {
|
||||
// We have enough and we have more.
|
||||
// Do not return io.EOF
|
||||
|
||||
@@ -114,6 +114,12 @@ func (z *listPathOptions) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Versioned")
|
||||
return
|
||||
}
|
||||
case "V1":
|
||||
z.V1, err = dc.ReadBool()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "V1")
|
||||
return
|
||||
}
|
||||
case "StopDiskAtLimit":
|
||||
z.StopDiskAtLimit, err = dc.ReadBool()
|
||||
if err != nil {
|
||||
@@ -145,9 +151,9 @@ func (z *listPathOptions) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *listPathOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 18
|
||||
// map header, size 19
|
||||
// write "ID"
|
||||
err = en.Append(0xde, 0x0, 0x12, 0xa2, 0x49, 0x44)
|
||||
err = en.Append(0xde, 0x0, 0x13, 0xa2, 0x49, 0x44)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -296,6 +302,16 @@ func (z *listPathOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Versioned")
|
||||
return
|
||||
}
|
||||
// write "V1"
|
||||
err = en.Append(0xa2, 0x56, 0x31)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteBool(z.V1)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "V1")
|
||||
return
|
||||
}
|
||||
// write "StopDiskAtLimit"
|
||||
err = en.Append(0xaf, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x69, 0x73, 0x6b, 0x41, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74)
|
||||
if err != nil {
|
||||
@@ -332,9 +348,9 @@ func (z *listPathOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *listPathOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 18
|
||||
// map header, size 19
|
||||
// string "ID"
|
||||
o = append(o, 0xde, 0x0, 0x12, 0xa2, 0x49, 0x44)
|
||||
o = append(o, 0xde, 0x0, 0x13, 0xa2, 0x49, 0x44)
|
||||
o = msgp.AppendString(o, z.ID)
|
||||
// string "Bucket"
|
||||
o = append(o, 0xa6, 0x42, 0x75, 0x63, 0x6b, 0x65, 0x74)
|
||||
@@ -378,6 +394,9 @@ func (z *listPathOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "Versioned"
|
||||
o = append(o, 0xa9, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x64)
|
||||
o = msgp.AppendBool(o, z.Versioned)
|
||||
// string "V1"
|
||||
o = append(o, 0xa2, 0x56, 0x31)
|
||||
o = msgp.AppendBool(o, z.V1)
|
||||
// string "StopDiskAtLimit"
|
||||
o = append(o, 0xaf, 0x53, 0x74, 0x6f, 0x70, 0x44, 0x69, 0x73, 0x6b, 0x41, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74)
|
||||
o = msgp.AppendBool(o, z.StopDiskAtLimit)
|
||||
@@ -498,6 +517,12 @@ func (z *listPathOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Versioned")
|
||||
return
|
||||
}
|
||||
case "V1":
|
||||
z.V1, bts, err = msgp.ReadBoolBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "V1")
|
||||
return
|
||||
}
|
||||
case "StopDiskAtLimit":
|
||||
z.StopDiskAtLimit, bts, err = msgp.ReadBoolBytes(bts)
|
||||
if err != nil {
|
||||
@@ -530,6 +555,6 @@ func (z *listPathOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *listPathOptions) Msgsize() (s int) {
|
||||
s = 3 + 3 + msgp.StringPrefixSize + len(z.ID) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 8 + msgp.StringPrefixSize + len(z.BaseDir) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 13 + msgp.StringPrefixSize + len(z.FilterPrefix) + 7 + msgp.StringPrefixSize + len(z.Marker) + 6 + msgp.IntSize + 9 + msgp.StringPrefixSize + len(z.AskDisks) + 12 + msgp.BoolSize + 10 + msgp.BoolSize + 10 + msgp.StringPrefixSize + len(z.Separator) + 7 + msgp.BoolSize + 19 + msgp.BoolSize + 10 + msgp.BoolSize + 10 + msgp.BoolSize + 16 + msgp.BoolSize + 5 + msgp.IntSize + 4 + msgp.IntSize
|
||||
s = 3 + 3 + msgp.StringPrefixSize + len(z.ID) + 7 + msgp.StringPrefixSize + len(z.Bucket) + 8 + msgp.StringPrefixSize + len(z.BaseDir) + 7 + msgp.StringPrefixSize + len(z.Prefix) + 13 + msgp.StringPrefixSize + len(z.FilterPrefix) + 7 + msgp.StringPrefixSize + len(z.Marker) + 6 + msgp.IntSize + 9 + msgp.StringPrefixSize + len(z.AskDisks) + 12 + msgp.BoolSize + 10 + msgp.BoolSize + 10 + msgp.StringPrefixSize + len(z.Separator) + 7 + msgp.BoolSize + 19 + msgp.BoolSize + 10 + msgp.BoolSize + 10 + msgp.BoolSize + 3 + msgp.BoolSize + 16 + msgp.BoolSize + 5 + msgp.IntSize + 4 + msgp.IntSize
|
||||
return
|
||||
}
|
||||
|
||||
+20
@@ -22,6 +22,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/pkg/v2/wildcard"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -73,6 +74,25 @@ func (m *mrfState) healRoutine(z *erasureServerPools) {
|
||||
return
|
||||
}
|
||||
|
||||
// We might land at .metacache, .trash, .multipart
|
||||
// no need to heal them skip, only when bucket
|
||||
// is '.minio.sys'
|
||||
if u.bucket == minioMetaBucket {
|
||||
// No MRF needed for temporary objects
|
||||
if wildcard.Match("buckets/*/.metacache/*", u.object) {
|
||||
continue
|
||||
}
|
||||
if wildcard.Match("tmp/*", u.object) {
|
||||
continue
|
||||
}
|
||||
if wildcard.Match("multipart/*", u.object) {
|
||||
continue
|
||||
}
|
||||
if wildcard.Match("tmp-old/*", u.object) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if now.Sub(u.queued) < time.Second {
|
||||
// let recently failed networks to reconnect
|
||||
|
||||
+1
-6
@@ -98,12 +98,7 @@ func mustGetLocalIP6() (ipList set.StringSet) {
|
||||
|
||||
// getHostIP returns IP address of given host.
|
||||
func getHostIP(host string) (ipList set.StringSet, err error) {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = net.DefaultResolver.LookupHost
|
||||
}
|
||||
|
||||
addrs, err := lookupHost(GlobalContext, host)
|
||||
addrs, err := globalDNSCache.LookupHost(GlobalContext, host)
|
||||
if err != nil {
|
||||
return ipList, err
|
||||
}
|
||||
|
||||
@@ -480,7 +480,7 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
|
||||
}
|
||||
opts.MTime = mtime
|
||||
opts.UserDefined = make(map[string]string)
|
||||
|
||||
opts.UserDefined[ReservedMetadataPrefix+"Actual-Object-Size"] = r.Header.Get(xhttp.MinIOReplicationActualObjectSize)
|
||||
// Transfer SSEC key in opts.EncryptFn
|
||||
if crypto.SSEC.IsRequested(r.Header) {
|
||||
key, err := ParseSSECustomerRequest(r)
|
||||
@@ -491,5 +491,8 @@ func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object
|
||||
}
|
||||
}
|
||||
}
|
||||
if _, ok := r.Header[xhttp.MinIOSourceReplicationRequest]; ok {
|
||||
opts.ReplicationRequest = true
|
||||
}
|
||||
return opts, nil
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ func checkCopyObjectPreconditions(ctx context.Context, w http.ResponseWriter, r
|
||||
// x-minio-source-etag
|
||||
func checkPreconditionsPUT(ctx context.Context, w http.ResponseWriter, r *http.Request, objInfo ObjectInfo, opts ObjectOptions) bool {
|
||||
// Return false for methods other than PUT.
|
||||
if r.Method != http.MethodPut {
|
||||
if r.Method != http.MethodPut && r.Method != http.MethodPost {
|
||||
return false
|
||||
}
|
||||
// If the object doesn't have a modtime (IsZero), or the modtime
|
||||
|
||||
+8
-10
@@ -1721,7 +1721,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
if dstOpts.ReplicationRequest {
|
||||
srcTimestamp := dstOpts.ReplicationSourceTaggingTimestamp
|
||||
if !srcTimestamp.IsZero() {
|
||||
ondiskTimestamp, err := time.Parse(lastTaggingTimestamp, time.RFC3339Nano)
|
||||
ondiskTimestamp, err := time.Parse(time.RFC3339Nano, lastTaggingTimestamp)
|
||||
// update tagging metadata only if replica timestamp is newer than what's on disk
|
||||
if err != nil || (err == nil && ondiskTimestamp.Before(srcTimestamp)) {
|
||||
srcInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp] = srcTimestamp.UTC().Format(time.RFC3339Nano)
|
||||
@@ -1748,7 +1748,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
if dstOpts.ReplicationRequest {
|
||||
srcTimestamp := dstOpts.ReplicationSourceRetentionTimestamp
|
||||
if !srcTimestamp.IsZero() {
|
||||
ondiskTimestamp, err := time.Parse(lastretentionTimestamp, time.RFC3339Nano)
|
||||
ondiskTimestamp, err := time.Parse(time.RFC3339Nano, lastretentionTimestamp)
|
||||
// update retention metadata only if replica timestamp is newer than what's on disk
|
||||
if err != nil || (err == nil && ondiskTimestamp.Before(srcTimestamp)) {
|
||||
srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockMode)] = string(retentionMode)
|
||||
@@ -1768,7 +1768,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
if dstOpts.ReplicationRequest {
|
||||
srcTimestamp := dstOpts.ReplicationSourceLegalholdTimestamp
|
||||
if !srcTimestamp.IsZero() {
|
||||
ondiskTimestamp, err := time.Parse(lastLegalHoldTimestamp, time.RFC3339Nano)
|
||||
ondiskTimestamp, err := time.Parse(time.RFC3339Nano, lastLegalHoldTimestamp)
|
||||
// update legalhold metadata only if replica timestamp is newer than what's on disk
|
||||
if err != nil || (err == nil && ondiskTimestamp.Before(srcTimestamp)) {
|
||||
srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status)
|
||||
@@ -2234,8 +2234,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
if crypto.SSEC.IsRequested(r.Header) && isCompressible(r.Header, object) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, crypto.ErrIncompatibleEncryptionWithCompression), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2649,10 +2649,6 @@ func (api objectAPIHandlers) PutObjectExtractHandler(w http.ResponseWriter, r *h
|
||||
return errInvalidEncryptionParameters
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
return errInvalidEncryptionParametersSSEC
|
||||
}
|
||||
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2830,7 +2826,9 @@ func (api objectAPIHandlers) DeleteObjectHandler(w http.ResponseWriter, r *http.
|
||||
|
||||
rcfg, _ := globalBucketObjectLockSys.Get(bucket)
|
||||
if rcfg.LockEnabled && opts.DeletePrefix {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errors.New("force-delete is forbidden in a locked-enabled bucket")), r.URL)
|
||||
apiErr := toAPIError(ctx, errInvalidArgument)
|
||||
apiErr.Description = "force-delete is forbidden on Object Locking enabled buckets"
|
||||
writeErrorResponse(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -116,14 +116,29 @@ func (api objectAPIHandlers) NewMultipartUploadHandler(w http.ResponseWriter, r
|
||||
return
|
||||
}
|
||||
|
||||
if crypto.SSEC.IsRequested(r.Header) && isReplicationEnabled(ctx, bucket) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, errInvalidEncryptionParametersSSEC), r.URL)
|
||||
if crypto.SSEC.IsRequested(r.Header) && isCompressible(r.Header, object) {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, crypto.ErrIncompatibleEncryptionWithCompression), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
_, sourceReplReq := r.Header[xhttp.MinIOSourceReplicationRequest]
|
||||
ssecRepHeaders := []string{
|
||||
"X-Minio-Replication-Server-Side-Encryption-Seal-Algorithm",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Sealed-Key",
|
||||
"X-Minio-Replication-Server-Side-Encryption-Iv",
|
||||
}
|
||||
ssecRep := false
|
||||
for _, header := range ssecRepHeaders {
|
||||
if val := r.Header.Get(header); val != "" {
|
||||
ssecRep = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !(ssecRep && sourceReplReq) {
|
||||
if err = setEncryptionMetadata(r, bucket, object, encMetadata); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Set this for multipart only operations, we need to differentiate during
|
||||
// decryption if the file was actually multipart or not.
|
||||
@@ -757,9 +772,10 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
pReader := NewPutObjReader(hashReader)
|
||||
|
||||
_, isEncrypted := crypto.IsEncrypted(mi.UserDefined)
|
||||
_, replicationStatus := mi.UserDefined[xhttp.AmzBucketReplicationStatus]
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
if isEncrypted {
|
||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) {
|
||||
if !crypto.SSEC.IsRequested(r.Header) && crypto.SSEC.IsEncrypted(mi.UserDefined) && !replicationStatus {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrSSEMultipartEncrypted), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -779,52 +795,55 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
}
|
||||
|
||||
// Calculating object encryption key
|
||||
key, err = decryptObjectMeta(key, bucket, object, mi.UserDefined)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
copy(objectEncryptionKey[:], key)
|
||||
_, sourceReplReq := r.Header[xhttp.MinIOSourceReplicationRequest]
|
||||
if !(sourceReplReq && crypto.SSEC.IsEncrypted(mi.UserDefined)) {
|
||||
// Calculating object encryption key
|
||||
key, err = decryptObjectMeta(key, bucket, object, mi.UserDefined)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
copy(objectEncryptionKey[:], key)
|
||||
|
||||
partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID))
|
||||
in := io.Reader(hashReader)
|
||||
if size > encryptBufferThreshold {
|
||||
// The encryption reads in blocks of 64KB.
|
||||
// We add a buffer on bigger files to reduce the number of syscalls upstream.
|
||||
in = bufio.NewReaderSize(hashReader, encryptBufferSize)
|
||||
}
|
||||
reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.DARECiphers()})
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
wantSize := int64(-1)
|
||||
if size >= 0 {
|
||||
info := ObjectInfo{Size: size}
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(ctx, etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err := hashReader.AddChecksum(r, true); err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||
return
|
||||
}
|
||||
partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID))
|
||||
in := io.Reader(hashReader)
|
||||
if size > encryptBufferThreshold {
|
||||
// The encryption reads in blocks of 64KB.
|
||||
// We add a buffer on bigger files to reduce the number of syscalls upstream.
|
||||
in = bufio.NewReaderSize(hashReader, encryptBufferSize)
|
||||
}
|
||||
reader, err = sio.EncryptReader(in, sio.Config{Key: partEncryptionKey[:], CipherSuites: fips.DARECiphers()})
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
wantSize := int64(-1)
|
||||
if size >= 0 {
|
||||
info := ObjectInfo{Size: size}
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(ctx, etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if err := hashReader.AddChecksum(r, true); err != nil {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if idxCb != nil {
|
||||
idxCb = compressionIndexEncrypter(objectEncryptionKey, idxCb)
|
||||
if idxCb != nil {
|
||||
idxCb = compressionIndexEncrypter(objectEncryptionKey, idxCb)
|
||||
}
|
||||
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||
}
|
||||
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
|
||||
}
|
||||
opts.IndexCB = idxCb
|
||||
|
||||
|
||||
+10
-10
@@ -368,7 +368,7 @@ func (s *peerRESTServer) LocalStorageInfoHandler(mss *grid.MSS) (*grid.JSON[madm
|
||||
|
||||
// ServerInfoHandler - returns Server Info
|
||||
func (s *peerRESTServer) ServerInfoHandler(params *grid.MSS) (*grid.JSON[madmin.ServerProperties], *grid.RemoteErr) {
|
||||
r := http.Request{Host: globalMinioHost}
|
||||
r := http.Request{Host: globalLocalNodeName}
|
||||
metrics, err := strconv.ParseBool(params.Get(peerRESTMetrics))
|
||||
if err != nil {
|
||||
return nil, grid.NewRemoteErr(err)
|
||||
@@ -379,37 +379,37 @@ func (s *peerRESTServer) ServerInfoHandler(params *grid.MSS) (*grid.JSON[madmin.
|
||||
|
||||
// GetCPUsHandler - returns CPU info.
|
||||
func (s *peerRESTServer) GetCPUsHandler(_ *grid.MSS) (*grid.JSON[madmin.CPUs], *grid.RemoteErr) {
|
||||
info := madmin.GetCPUs(context.Background(), globalMinioHost)
|
||||
info := madmin.GetCPUs(context.Background(), globalLocalNodeName)
|
||||
return madminCPUs.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetNetInfoHandler - returns network information.
|
||||
func (s *peerRESTServer) GetNetInfoHandler(_ *grid.MSS) (*grid.JSON[madmin.NetInfo], *grid.RemoteErr) {
|
||||
info := madmin.GetNetInfo(globalMinioHost, globalInternodeInterface)
|
||||
info := madmin.GetNetInfo(globalLocalNodeName, globalInternodeInterface)
|
||||
return madminNetInfo.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetPartitionsHandler - returns disk partition information.
|
||||
func (s *peerRESTServer) GetPartitionsHandler(_ *grid.MSS) (*grid.JSON[madmin.Partitions], *grid.RemoteErr) {
|
||||
info := madmin.GetPartitions(context.Background(), globalMinioHost)
|
||||
info := madmin.GetPartitions(context.Background(), globalLocalNodeName)
|
||||
return madminPartitions.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetOSInfoHandler - returns operating system's information.
|
||||
func (s *peerRESTServer) GetOSInfoHandler(_ *grid.MSS) (*grid.JSON[madmin.OSInfo], *grid.RemoteErr) {
|
||||
info := madmin.GetOSInfo(context.Background(), globalMinioHost)
|
||||
info := madmin.GetOSInfo(context.Background(), globalLocalNodeName)
|
||||
return madminOSInfo.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetProcInfoHandler - returns this MinIO process information.
|
||||
func (s *peerRESTServer) GetProcInfoHandler(_ *grid.MSS) (*grid.JSON[madmin.ProcInfo], *grid.RemoteErr) {
|
||||
info := madmin.GetProcInfo(context.Background(), globalMinioHost)
|
||||
info := madmin.GetProcInfo(context.Background(), globalLocalNodeName)
|
||||
return madminProcInfo.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetMemInfoHandler - returns memory information.
|
||||
func (s *peerRESTServer) GetMemInfoHandler(_ *grid.MSS) (*grid.JSON[madmin.MemInfo], *grid.RemoteErr) {
|
||||
info := madmin.GetMemInfo(context.Background(), globalMinioHost)
|
||||
info := madmin.GetMemInfo(context.Background(), globalLocalNodeName)
|
||||
return madminMemInfo.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
@@ -445,20 +445,20 @@ func (s *peerRESTServer) GetMetricsHandler(v *grid.URLValues) (*grid.JSON[madmin
|
||||
// GetSysConfigHandler - returns system config information.
|
||||
// (only the config that are of concern to minio)
|
||||
func (s *peerRESTServer) GetSysConfigHandler(_ *grid.MSS) (*grid.JSON[madmin.SysConfig], *grid.RemoteErr) {
|
||||
info := madmin.GetSysConfig(context.Background(), globalMinioHost)
|
||||
info := madmin.GetSysConfig(context.Background(), globalLocalNodeName)
|
||||
return madminSysConfig.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetSysServicesHandler - returns system services information.
|
||||
// (only the services that are of concern to minio)
|
||||
func (s *peerRESTServer) GetSysServicesHandler(_ *grid.MSS) (*grid.JSON[madmin.SysServices], *grid.RemoteErr) {
|
||||
info := madmin.GetSysServices(context.Background(), globalMinioHost)
|
||||
info := madmin.GetSysServices(context.Background(), globalLocalNodeName)
|
||||
return madminSysServices.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
// GetSysErrorsHandler - returns system level errors
|
||||
func (s *peerRESTServer) GetSysErrorsHandler(_ *grid.MSS) (*grid.JSON[madmin.SysErrors], *grid.RemoteErr) {
|
||||
info := madmin.GetSysErrors(context.Background(), globalMinioHost)
|
||||
info := madmin.GetSysErrors(context.Background(), globalLocalNodeName)
|
||||
return madminSysErrors.NewJSONWith(&info), nil
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -141,9 +141,14 @@ var ServerFlags = []cli.Flag{
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "dns-cache-ttl",
|
||||
Usage: "custom DNS cache TTL for baremetal setups",
|
||||
Usage: "custom DNS cache TTL",
|
||||
Hidden: true,
|
||||
Value: 10 * time.Minute,
|
||||
Value: func() time.Duration {
|
||||
if orchestrated {
|
||||
return 30 * time.Second
|
||||
}
|
||||
return 10 * time.Minute
|
||||
}(),
|
||||
EnvVar: "MINIO_DNS_CACHE_TTL",
|
||||
},
|
||||
cli.IntFlag{
|
||||
@@ -161,6 +166,12 @@ var ServerFlags = []cli.Flag{
|
||||
Name: "sftp",
|
||||
Usage: "enable and configure an SFTP server",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "crossdomain-xml",
|
||||
Usage: "provide a custom crossdomain-xml configuration to report at http://endpoint/crossdomain.xml",
|
||||
Hidden: true,
|
||||
EnvVar: "MINIO_CROSSDOMAIN_XML",
|
||||
},
|
||||
}
|
||||
|
||||
var gatewayCmd = cli.Command{
|
||||
@@ -587,12 +598,7 @@ func setGlobalInternodeInterface(interfaceName string) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = net.DefaultResolver.LookupHost
|
||||
}
|
||||
|
||||
haddrs, err := lookupHost(ctx, host)
|
||||
haddrs, err := globalDNSCache.LookupHost(ctx, host)
|
||||
if err == nil {
|
||||
ip = haddrs[0]
|
||||
}
|
||||
@@ -630,12 +636,7 @@ func getServerListenAddrs() []string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = net.DefaultResolver.LookupHost
|
||||
}
|
||||
|
||||
haddrs, err := lookupHost(ctx, host)
|
||||
haddrs, err := globalDNSCache.LookupHost(ctx, host)
|
||||
if err == nil {
|
||||
for _, addr := range haddrs {
|
||||
addrs.Add(net.JoinHostPort(addr, globalMinioPort))
|
||||
|
||||
+52
-56
@@ -46,6 +46,7 @@ import (
|
||||
sreplication "github.com/minio/minio/internal/bucket/replication"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/policy"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -2044,26 +2045,28 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
|
||||
// Followed by group policy mapping
|
||||
{
|
||||
// Replicate policy mappings on local to all peers.
|
||||
groupPolicyMap := make(map[string]MappedPolicy)
|
||||
groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
errG := globalIAMSys.store.loadMappedPolicies(ctx, unknownIAMUserType, true, groupPolicyMap)
|
||||
if errG != nil {
|
||||
return errSRBackendIssue(errG)
|
||||
}
|
||||
|
||||
for group, mp := range groupPolicyMap {
|
||||
err := c.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
var err error
|
||||
groupPolicyMap.Range(func(k string, mp MappedPolicy) bool {
|
||||
err = c.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: group,
|
||||
UserOrGroup: k,
|
||||
UserType: int(unknownIAMUserType),
|
||||
IsGroup: true,
|
||||
Policy: mp.Policies,
|
||||
},
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return errSRIAMError(err)
|
||||
}
|
||||
return err == nil
|
||||
})
|
||||
if err != nil {
|
||||
return errSRIAMError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2128,14 +2131,14 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
|
||||
// Followed by policy mapping for the userAccounts we previously synced.
|
||||
{
|
||||
// Replicate policy mappings on local to all peers.
|
||||
userPolicyMap := make(map[string]MappedPolicy)
|
||||
userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
errU := globalIAMSys.store.loadMappedPolicies(ctx, regUser, false, userPolicyMap)
|
||||
if errU != nil {
|
||||
return errSRBackendIssue(errU)
|
||||
}
|
||||
|
||||
for user, mp := range userPolicyMap {
|
||||
err := c.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
var err error
|
||||
userPolicyMap.Range(func(user string, mp MappedPolicy) bool {
|
||||
err = c.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: user,
|
||||
@@ -2145,23 +2148,25 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
|
||||
},
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return errSRIAMError(err)
|
||||
}
|
||||
return err == nil
|
||||
})
|
||||
if err != nil {
|
||||
return errSRIAMError(err)
|
||||
}
|
||||
}
|
||||
|
||||
// and finally followed by policy mappings for for STS users.
|
||||
{
|
||||
// Replicate policy mappings on local to all peers.
|
||||
stsPolicyMap := make(map[string]MappedPolicy)
|
||||
stsPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
errU := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, stsPolicyMap)
|
||||
if errU != nil {
|
||||
return errSRBackendIssue(errU)
|
||||
}
|
||||
|
||||
for user, mp := range stsPolicyMap {
|
||||
err := c.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
var err error
|
||||
stsPolicyMap.Range(func(user string, mp MappedPolicy) bool {
|
||||
err = c.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemPolicyMapping,
|
||||
PolicyMapping: &madmin.SRPolicyMapping{
|
||||
UserOrGroup: user,
|
||||
@@ -2171,9 +2176,10 @@ func (c *SiteReplicationSys) syncToAllPeers(ctx context.Context, addOpts madmin.
|
||||
},
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return errSRIAMError(err)
|
||||
}
|
||||
return err == nil
|
||||
})
|
||||
if err != nil {
|
||||
return errSRIAMError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3783,12 +3789,12 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
||||
|
||||
if opts.Users || opts.Entity == madmin.SRUserEntity {
|
||||
// Replicate policy mappings on local to all peers.
|
||||
userPolicyMap := make(map[string]MappedPolicy)
|
||||
stsPolicyMap := make(map[string]MappedPolicy)
|
||||
svcPolicyMap := make(map[string]MappedPolicy)
|
||||
userPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
stsPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
svcPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
if opts.Entity == madmin.SRUserEntity {
|
||||
if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, false); ok {
|
||||
userPolicyMap[opts.EntityValue] = mp
|
||||
userPolicyMap.Store(opts.EntityValue, mp)
|
||||
}
|
||||
} else {
|
||||
stsErr := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, false, stsPolicyMap)
|
||||
@@ -3804,34 +3810,23 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
||||
return info, errSRBackendIssue(svcErr)
|
||||
}
|
||||
}
|
||||
info.UserPolicies = make(map[string]madmin.SRPolicyMapping, len(userPolicyMap))
|
||||
for user, mp := range userPolicyMap {
|
||||
info.UserPolicies[user] = madmin.SRPolicyMapping{
|
||||
IsGroup: false,
|
||||
UserOrGroup: user,
|
||||
UserType: int(regUser),
|
||||
Policy: mp.Policies,
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
}
|
||||
}
|
||||
for stsU, mp := range stsPolicyMap {
|
||||
info.UserPolicies[stsU] = madmin.SRPolicyMapping{
|
||||
IsGroup: false,
|
||||
UserOrGroup: stsU,
|
||||
UserType: int(stsUser),
|
||||
Policy: mp.Policies,
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
}
|
||||
}
|
||||
for svcU, mp := range svcPolicyMap {
|
||||
info.UserPolicies[svcU] = madmin.SRPolicyMapping{
|
||||
IsGroup: false,
|
||||
UserOrGroup: svcU,
|
||||
UserType: int(svcUser),
|
||||
Policy: mp.Policies,
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
}
|
||||
info.UserPolicies = make(map[string]madmin.SRPolicyMapping, userPolicyMap.Size())
|
||||
addPolicy := func(t IAMUserType, mp *xsync.MapOf[string, MappedPolicy]) {
|
||||
mp.Range(func(k string, mp MappedPolicy) bool {
|
||||
info.UserPolicies[k] = madmin.SRPolicyMapping{
|
||||
IsGroup: false,
|
||||
UserOrGroup: k,
|
||||
UserType: int(t),
|
||||
Policy: mp.Policies,
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
addPolicy(regUser, userPolicyMap)
|
||||
addPolicy(stsUser, stsPolicyMap)
|
||||
addPolicy(svcUser, svcPolicyMap)
|
||||
|
||||
info.UserInfoMap = make(map[string]madmin.UserInfo)
|
||||
if opts.Entity == madmin.SRUserEntity {
|
||||
if ui, err := globalIAMSys.GetUserInfo(ctx, opts.EntityValue); err == nil {
|
||||
@@ -3875,10 +3870,10 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
||||
|
||||
if opts.Groups || opts.Entity == madmin.SRGroupEntity {
|
||||
// Replicate policy mappings on local to all peers.
|
||||
groupPolicyMap := make(map[string]MappedPolicy)
|
||||
groupPolicyMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
if opts.Entity == madmin.SRGroupEntity {
|
||||
if mp, ok := globalIAMSys.store.GetMappedPolicy(opts.EntityValue, true); ok {
|
||||
groupPolicyMap[opts.EntityValue] = mp
|
||||
groupPolicyMap.Store(opts.EntityValue, mp)
|
||||
}
|
||||
} else {
|
||||
stsErr := globalIAMSys.store.loadMappedPolicies(ctx, stsUser, true, groupPolicyMap)
|
||||
@@ -3891,15 +3886,16 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
|
||||
}
|
||||
}
|
||||
|
||||
info.GroupPolicies = make(map[string]madmin.SRPolicyMapping, len(c.state.Peers))
|
||||
for group, mp := range groupPolicyMap {
|
||||
info.GroupPolicies = make(map[string]madmin.SRPolicyMapping, groupPolicyMap.Size())
|
||||
groupPolicyMap.Range(func(group string, mp MappedPolicy) bool {
|
||||
info.GroupPolicies[group] = madmin.SRPolicyMapping{
|
||||
IsGroup: true,
|
||||
UserOrGroup: group,
|
||||
Policy: mp.Policies,
|
||||
UpdatedAt: mp.UpdatedAt,
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
info.GroupDescMap = make(map[string]madmin.GroupDesc)
|
||||
if opts.Entity == madmin.SRGroupEntity {
|
||||
if gd, err := globalIAMSys.GetGroupDescription(opts.EntityValue); err == nil {
|
||||
|
||||
@@ -38,6 +38,7 @@ func (z *BaseOptions) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z BaseOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 0
|
||||
_ = z
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -49,6 +50,7 @@ func (z BaseOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z BaseOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 0
|
||||
_ = z
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
@@ -532,6 +534,7 @@ func (z *DeleteOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
// map header, size 0
|
||||
_ = z.BaseOptions
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -576,6 +579,7 @@ func (z *DeleteOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "BaseOptions"
|
||||
o = append(o, 0x84, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
||||
// map header, size 0
|
||||
_ = z.BaseOptions
|
||||
o = append(o, 0x80)
|
||||
// string "r"
|
||||
o = append(o, 0xa1, 0x72)
|
||||
@@ -1906,6 +1910,8 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
delete(z.Metadata, key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0002 > 0 {
|
||||
zb0002--
|
||||
var za0001 string
|
||||
@@ -2362,6 +2368,8 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
delete(z.Metadata, key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0002 > 0 {
|
||||
var za0001 string
|
||||
var za0002 string
|
||||
@@ -4455,6 +4463,7 @@ func (z *RenameDataHandlerParams) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
// map header, size 0
|
||||
_ = z.Opts.BaseOptions
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -4494,6 +4503,7 @@ func (z *RenameDataHandlerParams) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "BaseOptions"
|
||||
o = append(o, 0x81, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
||||
// map header, size 0
|
||||
_ = z.Opts.BaseOptions
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
@@ -4983,6 +4993,7 @@ func (z *RenameOptions) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
// map header, size 0
|
||||
_ = z.BaseOptions
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -4997,6 +5008,7 @@ func (z *RenameOptions) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "BaseOptions"
|
||||
o = append(o, 0x81, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
|
||||
// map header, size 0
|
||||
_ = z.BaseOptions
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
|
||||
+14
-18
@@ -190,12 +190,10 @@ func (client *storageRESTClient) call(ctx context.Context, method string, values
|
||||
}
|
||||
values.Set(storageRESTDiskID, client.diskID)
|
||||
respBody, err := client.restClient.Call(ctx, method, values, body, length)
|
||||
if err == nil {
|
||||
return respBody, nil
|
||||
if err != nil {
|
||||
return nil, toStorageErr(err)
|
||||
}
|
||||
|
||||
err = toStorageErr(err)
|
||||
return nil, err
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
// Stringer provides a canonicalized representation of network device.
|
||||
@@ -403,7 +401,7 @@ func (client *storageRESTClient) CreateFile(ctx context.Context, origvolume, vol
|
||||
return err
|
||||
}
|
||||
_, err = waitForHTTPResponse(respBody)
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) error {
|
||||
@@ -641,7 +639,7 @@ func (client *storageRESTClient) ReadFile(ctx context.Context, volume string, pa
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
n, err := io.ReadFull(respBody, buf)
|
||||
return int64(n), err
|
||||
return int64(n), toStorageErr(err)
|
||||
}
|
||||
|
||||
// ListDir - lists a directory.
|
||||
@@ -709,7 +707,7 @@ func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume stri
|
||||
reader, err := waitForHTTPResponse(respBody)
|
||||
if err != nil {
|
||||
for i := range errs {
|
||||
errs[i] = err
|
||||
errs[i] = toStorageErr(err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -717,7 +715,7 @@ func (client *storageRESTClient) DeleteVersions(ctx context.Context, volume stri
|
||||
dErrResp := &DeleteVersionsErrsResp{}
|
||||
if err = gob.NewDecoder(reader).Decode(dErrResp); err != nil {
|
||||
for i := range errs {
|
||||
errs[i] = err
|
||||
errs[i] = toStorageErr(err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -759,12 +757,12 @@ func (client *storageRESTClient) VerifyFile(ctx context.Context, volume, path st
|
||||
|
||||
respReader, err := waitForHTTPResponse(respBody)
|
||||
if err != nil {
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
verifyResp := &VerifyFileResp{}
|
||||
if err = gob.NewDecoder(respReader).Decode(verifyResp); err != nil {
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
return toStorageErr(verifyResp.Err)
|
||||
@@ -782,7 +780,7 @@ func (client *storageRESTClient) StatInfoFile(ctx context.Context, volume, path
|
||||
defer xhttp.DrainBody(respBody)
|
||||
respReader, err := waitForHTTPResponse(respBody)
|
||||
if err != nil {
|
||||
return stat, err
|
||||
return stat, toStorageErr(err)
|
||||
}
|
||||
rd := msgpNewReader(respReader)
|
||||
defer readMsgpReaderPoolPut(rd)
|
||||
@@ -798,7 +796,7 @@ func (client *storageRESTClient) StatInfoFile(ctx context.Context, volume, path
|
||||
stat = append(stat, st)
|
||||
}
|
||||
|
||||
return stat, err
|
||||
return stat, toStorageErr(err)
|
||||
}
|
||||
|
||||
// ReadMultiple will read multiple files and send each back as response.
|
||||
@@ -816,9 +814,7 @@ func (client *storageRESTClient) ReadMultiple(ctx context.Context, req ReadMulti
|
||||
return err
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
pw.CloseWithError(waitForHTTPStream(respBody, pw))
|
||||
@@ -832,7 +828,7 @@ func (client *storageRESTClient) ReadMultiple(ctx context.Context, req ReadMulti
|
||||
err = nil
|
||||
}
|
||||
pr.CloseWithError(err)
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -854,7 +850,7 @@ func (client *storageRESTClient) CleanAbandonedData(ctx context.Context, volume
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
_, err = waitForHTTPResponse(respBody)
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
// Close - marks the client as closed.
|
||||
|
||||
@@ -29,6 +29,8 @@ import (
|
||||
minio "github.com/minio/minio-go/v7"
|
||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
ldap "github.com/minio/pkg/v2/ldap"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) {
|
||||
@@ -681,6 +683,7 @@ func TestIAMWithLDAPServerSuite(t *testing.T) {
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpLDAP(c, ldapServer)
|
||||
suite.TestLDAPSTS(c)
|
||||
suite.TestLDAPUnicodeVariations(c)
|
||||
suite.TestLDAPSTSServiceAccounts(c)
|
||||
suite.TestLDAPSTSServiceAccountsWithUsername(c)
|
||||
suite.TestLDAPSTSServiceAccountsWithGroups(c)
|
||||
@@ -823,6 +826,170 @@ func (s *TestSuiteIAM) TestLDAPSTS(c *check) {
|
||||
c.Assert(err.Error(), "Access Denied.")
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPUnicodeVariations(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
bucket := getRandomBucketName()
|
||||
err := s.client.MakeBucket(ctx, bucket, minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("bucket create error: %v", err)
|
||||
}
|
||||
|
||||
// Create policy
|
||||
policy := "mypolicy"
|
||||
policyBytes := []byte(fmt.Sprintf(`{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:PutObject",
|
||||
"s3:GetObject",
|
||||
"s3:ListBucket"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::%s/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`, bucket))
|
||||
err = s.adm.AddCannedPolicy(ctx, policy, policyBytes)
|
||||
if err != nil {
|
||||
c.Fatalf("policy add error: %v", err)
|
||||
}
|
||||
|
||||
ldapID := cr.LDAPIdentity{
|
||||
Client: s.TestSuiteCommon.client,
|
||||
STSEndpoint: s.endPoint,
|
||||
LDAPUsername: "svc.algorithm",
|
||||
LDAPPassword: "example",
|
||||
}
|
||||
|
||||
_, err = ldapID.Retrieve()
|
||||
if err == nil {
|
||||
c.Fatalf("Expected to fail to create STS cred with no associated policy!")
|
||||
}
|
||||
|
||||
mustNormalizeDN := func(dn string) string {
|
||||
normalizedDN, err := ldap.NormalizeDN(dn)
|
||||
if err != nil {
|
||||
c.Fatalf("normalize err: %v", err)
|
||||
}
|
||||
return normalizedDN
|
||||
}
|
||||
|
||||
actualUserDN := mustNormalizeDN("uid=svc.algorithm,OU=swengg,DC=min,DC=io")
|
||||
|
||||
// \uFE52 is the unicode dot SMALL FULL STOP used below:
|
||||
userDNWithUnicodeDot := "uid=svc﹒algorithm,OU=swengg,DC=min,DC=io"
|
||||
|
||||
_, err = s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDNWithUnicodeDot,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to set policy: %v", err)
|
||||
}
|
||||
|
||||
value, err := ldapID.Retrieve()
|
||||
if err != nil {
|
||||
c.Fatalf("Expected to generate STS creds, got err: %#v", err)
|
||||
}
|
||||
|
||||
usersList, err := s.adm.ListUsers(ctx)
|
||||
if err != nil {
|
||||
c.Fatalf("list users should not fail: %v", err)
|
||||
}
|
||||
if len(usersList) != 1 {
|
||||
c.Fatalf("expected user listing output: %#v", usersList)
|
||||
}
|
||||
uinfo := usersList[actualUserDN]
|
||||
if uinfo.PolicyName != policy || uinfo.Status != madmin.AccountEnabled {
|
||||
c.Fatalf("expected user listing content: %v", uinfo)
|
||||
}
|
||||
|
||||
minioClient, err := minio.New(s.endpoint, &minio.Options{
|
||||
Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
|
||||
Secure: s.secure,
|
||||
Transport: s.TestSuiteCommon.client.Transport,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Error initializing client: %v", err)
|
||||
}
|
||||
|
||||
// Validate that the client from sts creds can access the bucket.
|
||||
c.mustListObjects(ctx, minioClient, bucket)
|
||||
|
||||
// Validate that the client cannot remove any objects
|
||||
err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{})
|
||||
if err.Error() != "Access Denied." {
|
||||
c.Fatalf("unexpected non-access-denied err: %v", err)
|
||||
}
|
||||
|
||||
// Remove the policy assignment on the user DN:
|
||||
_, err = s.adm.DetachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: userDNWithUnicodeDot,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to remove policy setting: %v", err)
|
||||
}
|
||||
|
||||
_, err = ldapID.Retrieve()
|
||||
if err == nil {
|
||||
c.Fatalf("Expected to fail to create a user with no associated policy!")
|
||||
}
|
||||
|
||||
// Set policy via group and validate policy assignment.
|
||||
actualGroupDN := mustNormalizeDN("cn=project.c,ou=groups,ou=swengg,dc=min,dc=io")
|
||||
groupDNWithUnicodeDot := "cn=project﹒c,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
_, err = s.adm.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
Group: groupDNWithUnicodeDot,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to attach group policy: %v", err)
|
||||
}
|
||||
|
||||
value, err = ldapID.Retrieve()
|
||||
if err != nil {
|
||||
c.Fatalf("Expected to generate STS creds, got err: %#v", err)
|
||||
}
|
||||
|
||||
policyResult, err := s.adm.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{
|
||||
Policy: []string{policy},
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("GetLDAPPolicyEntities should not fail: %v", err)
|
||||
}
|
||||
{
|
||||
// Check that the mapping we created exists.
|
||||
idx := slices.IndexFunc(policyResult.PolicyMappings, func(e madmin.PolicyEntities) bool {
|
||||
return e.Policy == policy && slices.Contains(e.Groups, actualGroupDN)
|
||||
})
|
||||
if !(idx >= 0) {
|
||||
c.Fatalf("expected groupDN (%s) to be present in mapping list: %#v", actualGroupDN, policyResult)
|
||||
}
|
||||
}
|
||||
|
||||
minioClient, err = minio.New(s.endpoint, &minio.Options{
|
||||
Creds: cr.NewStaticV4(value.AccessKeyID, value.SecretAccessKey, value.SessionToken),
|
||||
Secure: s.secure,
|
||||
Transport: s.TestSuiteCommon.client.Transport,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Error initializing client: %v", err)
|
||||
}
|
||||
|
||||
// Validate that the client from sts creds can access the bucket.
|
||||
c.mustListObjects(ctx, minioClient, bucket)
|
||||
|
||||
// Validate that the client cannot remove any objects
|
||||
err = minioClient.RemoveObject(ctx, bucket, "someobject", minio.RemoveObjectOptions{})
|
||||
c.Assert(err.Error(), "Access Denied.")
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -21,6 +21,8 @@ func (z *DailyAllTierStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
delete((*z), key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0004 > 0 {
|
||||
zb0004--
|
||||
var zb0001 string
|
||||
@@ -30,8 +32,6 @@ func (z *DailyAllTierStats) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0005 uint32
|
||||
zb0005, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
@@ -167,6 +167,8 @@ func (z *DailyAllTierStats) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
delete((*z), key)
|
||||
}
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
for zb0004 > 0 {
|
||||
var zb0001 string
|
||||
var zb0002 lastDayTierStats
|
||||
@@ -176,8 +178,6 @@ func (z *DailyAllTierStats) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0005 uint32
|
||||
zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
|
||||
+12
-45
@@ -60,7 +60,7 @@ import (
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/v2/certs"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
pkgAudit "github.com/minio/pkg/v2/logger/message/audit"
|
||||
xaudit "github.com/minio/pkg/v2/logger/message/audit"
|
||||
xnet "github.com/minio/pkg/v2/net"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
@@ -572,13 +572,8 @@ func ToS3ETag(etag string) string {
|
||||
|
||||
// GetDefaultConnSettings returns default HTTP connection settings.
|
||||
func GetDefaultConnSettings() xhttp.ConnSettings {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
|
||||
return xhttp.ConnSettings{
|
||||
LookupHost: lookupHost,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: rest.DefaultTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
TCPOptions: globalTCPOptions,
|
||||
@@ -588,13 +583,8 @@ func GetDefaultConnSettings() xhttp.ConnSettings {
|
||||
// NewInternodeHTTPTransport returns a transport for internode MinIO
|
||||
// connections.
|
||||
func NewInternodeHTTPTransport(maxIdleConnsPerHost int) func() http.RoundTripper {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
|
||||
return xhttp.ConnSettings{
|
||||
LookupHost: lookupHost,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: rest.DefaultTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: fips.TLSCiphers(),
|
||||
@@ -607,13 +597,8 @@ func NewInternodeHTTPTransport(maxIdleConnsPerHost int) func() http.RoundTripper
|
||||
// NewCustomHTTPProxyTransport is used only for proxied requests, specifically
|
||||
// only supports HTTP/1.1
|
||||
func NewCustomHTTPProxyTransport() func() *http.Transport {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
|
||||
return xhttp.ConnSettings{
|
||||
LookupHost: lookupHost,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: rest.DefaultTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: fips.TLSCiphers(),
|
||||
@@ -626,13 +611,8 @@ func NewCustomHTTPProxyTransport() func() *http.Transport {
|
||||
// NewHTTPTransportWithClientCerts returns a new http configuration
|
||||
// used while communicating with the cloud backends.
|
||||
func NewHTTPTransportWithClientCerts(clientCert, clientKey string) *http.Transport {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
|
||||
s := xhttp.ConnSettings{
|
||||
LookupHost: lookupHost,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
TCPOptions: globalTCPOptions,
|
||||
@@ -663,14 +643,9 @@ const defaultDialTimeout = 5 * time.Second
|
||||
|
||||
// NewHTTPTransportWithTimeout allows setting a timeout.
|
||||
func NewHTTPTransportWithTimeout(timeout time.Duration) *http.Transport {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
|
||||
return xhttp.ConnSettings{
|
||||
DialContext: newCustomDialContext(),
|
||||
LookupHost: lookupHost,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
DialTimeout: defaultDialTimeout,
|
||||
RootCAs: globalRootCAs,
|
||||
TCPOptions: globalTCPOptions,
|
||||
@@ -702,14 +677,9 @@ func newCustomDialContext() xhttp.DialContext {
|
||||
// NewRemoteTargetHTTPTransport returns a new http configuration
|
||||
// used while communicating with the remote replication targets.
|
||||
func NewRemoteTargetHTTPTransport(insecure bool) func() *http.Transport {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
}
|
||||
|
||||
return xhttp.ConnSettings{
|
||||
DialContext: newCustomDialContext(),
|
||||
LookupHost: lookupHost,
|
||||
LookupHost: globalDNSCache.LookupHost,
|
||||
RootCAs: globalRootCAs,
|
||||
TCPOptions: globalTCPOptions,
|
||||
EnableHTTP2: false,
|
||||
@@ -976,21 +946,18 @@ func auditLogInternal(ctx context.Context, opts AuditLogOptions) {
|
||||
if len(logger.AuditTargets()) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
entry := audit.NewEntry(globalDeploymentID())
|
||||
entry.Trigger = opts.Event
|
||||
entry.Event = opts.Event
|
||||
entry.Error = opts.Error
|
||||
entry.API.Name = opts.APIName
|
||||
entry.API.Bucket = opts.Bucket
|
||||
entry.API.Objects = []pkgAudit.ObjectVersion{{ObjectName: opts.Object, VersionID: opts.VersionID}}
|
||||
entry.API.Objects = []xaudit.ObjectVersion{{ObjectName: opts.Object, VersionID: opts.VersionID}}
|
||||
entry.API.Status = opts.Status
|
||||
if len(opts.Tags) > 0 {
|
||||
entry.Tags = make(map[string]interface{}, len(opts.Tags))
|
||||
for k, v := range opts.Tags {
|
||||
entry.Tags[k] = v
|
||||
}
|
||||
} else {
|
||||
entry.Tags = make(map[string]interface{})
|
||||
entry.Tags = make(map[string]interface{}, len(opts.Tags))
|
||||
for k, v := range opts.Tags {
|
||||
entry.Tags[k] = v
|
||||
}
|
||||
|
||||
// Merge tag information if found - this is currently needed for tags
|
||||
|
||||
@@ -18,8 +18,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -35,6 +38,60 @@ type warmBackendMinIO struct {
|
||||
|
||||
var _ WarmBackend = (*warmBackendMinIO)(nil)
|
||||
|
||||
const (
|
||||
maxMultipartPutObjectSize = 1024 * 1024 * 1024 * 1024 * 5
|
||||
maxPartsCount = 10000
|
||||
maxPartSize = 1024 * 1024 * 1024 * 5
|
||||
minPartSize = 1024 * 1024 * 64 // chosen by us to be optimal for HDDs
|
||||
)
|
||||
|
||||
// optimalPartInfo - calculate the optimal part info for a given
|
||||
// object size.
|
||||
//
|
||||
// NOTE: Assumption here is that for any object to be uploaded to any S3 compatible
|
||||
// object storage it will have the following parameters as constants.
|
||||
//
|
||||
// maxPartsCount - 10000
|
||||
// maxMultipartPutObjectSize - 5TiB
|
||||
func optimalPartSize(objectSize int64) (partSize int64, err error) {
|
||||
// object size is '-1' set it to 5TiB.
|
||||
if objectSize == -1 {
|
||||
objectSize = maxMultipartPutObjectSize
|
||||
}
|
||||
|
||||
// object size is larger than supported maximum.
|
||||
if objectSize > maxMultipartPutObjectSize {
|
||||
err = errors.New("entity too large")
|
||||
return
|
||||
}
|
||||
|
||||
configuredPartSize := minPartSize
|
||||
// Use floats for part size for all calculations to avoid
|
||||
// overflows during float64 to int64 conversions.
|
||||
partSizeFlt := float64(objectSize / maxPartsCount)
|
||||
partSizeFlt = math.Ceil(partSizeFlt/float64(configuredPartSize)) * float64(configuredPartSize)
|
||||
|
||||
// Part size.
|
||||
partSize = int64(partSizeFlt)
|
||||
if partSize == 0 {
|
||||
return minPartSize, nil
|
||||
}
|
||||
return partSize, nil
|
||||
}
|
||||
|
||||
func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
|
||||
partSize, err := optimalPartSize(length)
|
||||
if err != nil {
|
||||
return remoteVersionID(""), err
|
||||
}
|
||||
res, err := m.client.PutObject(ctx, m.Bucket, m.getDest(object), r, length, minio.PutObjectOptions{
|
||||
StorageClass: m.StorageClass,
|
||||
PartSize: uint64(partSize),
|
||||
DisableContentSha256: true,
|
||||
})
|
||||
return remoteVersionID(res.VersionID), m.ToObjectError(err, object)
|
||||
}
|
||||
|
||||
func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO, error) {
|
||||
// Validation of credentials
|
||||
if conf.AccessKey == "" || conf.SecretKey == "" {
|
||||
@@ -56,9 +113,10 @@ func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO,
|
||||
getRemoteTierTargetInstanceTransport = NewHTTPTransportWithTimeout(10 * time.Minute)
|
||||
})
|
||||
opts := &minio.Options{
|
||||
Creds: creds,
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteTierTargetInstanceTransport,
|
||||
Creds: creds,
|
||||
Secure: u.Scheme == "https",
|
||||
Transport: getRemoteTierTargetInstanceTransport,
|
||||
TrailingHeaders: true,
|
||||
}
|
||||
client, err := minio.New(u.Host, opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -648,7 +648,7 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *ObjectPartInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(7)
|
||||
var zb0001Mask uint8 /* 7 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -718,7 +718,7 @@ func (z *ObjectPartInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "ModTime")
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x20) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x20) == 0 { // if not omitted
|
||||
// write "index"
|
||||
err = en.Append(0xa5, 0x69, 0x6e, 0x64, 0x65, 0x78)
|
||||
if err != nil {
|
||||
@@ -730,7 +730,7 @@ func (z *ObjectPartInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x40) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x40) == 0 { // if not omitted
|
||||
// write "crc"
|
||||
err = en.Append(0xa3, 0x63, 0x72, 0x63)
|
||||
if err != nil {
|
||||
@@ -760,7 +760,7 @@ func (z *ObjectPartInfo) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *ObjectPartInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(7)
|
||||
var zb0001Mask uint8 /* 7 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -792,12 +792,12 @@ func (z *ObjectPartInfo) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "ModTime"
|
||||
o = append(o, 0xa7, 0x4d, 0x6f, 0x64, 0x54, 0x69, 0x6d, 0x65)
|
||||
o = msgp.AppendTime(o, z.ModTime)
|
||||
if (zb0001Mask & 0x20) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x20) == 0 { // if not omitted
|
||||
// string "index"
|
||||
o = append(o, 0xa5, 0x69, 0x6e, 0x64, 0x65, 0x78)
|
||||
o = msgp.AppendBytes(o, z.Index)
|
||||
}
|
||||
if (zb0001Mask & 0x40) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x40) == 0 { // if not omitted
|
||||
// string "crc"
|
||||
o = append(o, 0xa3, 0x63, 0x72, 0x63)
|
||||
o = msgp.AppendMapHeader(o, uint32(len(z.Checksums)))
|
||||
|
||||
@@ -40,9 +40,6 @@ import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// Reject creating new versions when a single object is cross maxObjectVersions
|
||||
var maxObjectVersions = 10000
|
||||
|
||||
var (
|
||||
// XL header specifies the format
|
||||
xlHeader = [4]byte{'X', 'L', '2', ' '}
|
||||
@@ -1091,8 +1088,8 @@ func (x *xlMetaV2) addVersion(ver xlMetaV2Version) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// returns error if we have exceeded maxObjectVersions
|
||||
if len(x.versions)+1 > globalAPIConfig.getObjectMaxVersions() {
|
||||
// returns error if we have exceeded configured object max versions
|
||||
if int64(len(x.versions)+1) > globalAPIConfig.getObjectMaxVersions() {
|
||||
return errMaxVersionsExceeded
|
||||
}
|
||||
|
||||
|
||||
@@ -340,7 +340,7 @@ func (z *xlMetaDataDirDecoder) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *xlMetaDataDirDecoder) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(1)
|
||||
var zb0001Mask uint8 /* 1 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -356,7 +356,7 @@ func (z *xlMetaDataDirDecoder) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
if zb0001Len == 0 {
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x1) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x1) == 0 { // if not omitted
|
||||
// write "V2Obj"
|
||||
err = en.Append(0xa5, 0x56, 0x32, 0x4f, 0x62, 0x6a)
|
||||
if err != nil {
|
||||
@@ -387,7 +387,7 @@ func (z *xlMetaDataDirDecoder) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *xlMetaDataDirDecoder) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(1)
|
||||
var zb0001Mask uint8 /* 1 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -400,7 +400,7 @@ func (z *xlMetaDataDirDecoder) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
if zb0001Len == 0 {
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x1) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x1) == 0 { // if not omitted
|
||||
// string "V2Obj"
|
||||
o = append(o, 0xa5, 0x56, 0x32, 0x4f, 0x62, 0x6a)
|
||||
if z.ObjectV2 == nil {
|
||||
@@ -571,7 +571,7 @@ func (z *xlMetaV2DeleteMarker) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *xlMetaV2DeleteMarker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(3)
|
||||
var zb0001Mask uint8 /* 3 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -607,7 +607,7 @@ func (z *xlMetaV2DeleteMarker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "ModTime")
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x4) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x4) == 0 { // if not omitted
|
||||
// write "MetaSys"
|
||||
err = en.Append(0xa7, 0x4d, 0x65, 0x74, 0x61, 0x53, 0x79, 0x73)
|
||||
if err != nil {
|
||||
@@ -637,7 +637,7 @@ func (z *xlMetaV2DeleteMarker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *xlMetaV2DeleteMarker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(3)
|
||||
var zb0001Mask uint8 /* 3 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -656,7 +656,7 @@ func (z *xlMetaV2DeleteMarker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "MTime"
|
||||
o = append(o, 0xa5, 0x4d, 0x54, 0x69, 0x6d, 0x65)
|
||||
o = msgp.AppendInt64(o, z.ModTime)
|
||||
if (zb0001Mask & 0x4) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x4) == 0 { // if not omitted
|
||||
// string "MetaSys"
|
||||
o = append(o, 0xa7, 0x4d, 0x65, 0x74, 0x61, 0x53, 0x79, 0x73)
|
||||
o = msgp.AppendMapHeader(o, uint32(len(z.MetaSys)))
|
||||
@@ -879,7 +879,7 @@ func (z *xlMetaV2Object) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "PartETags")
|
||||
return
|
||||
}
|
||||
if cap(z.PartETags) >= int(zb0006) {
|
||||
if z.PartETags != nil && cap(z.PartETags) >= int(zb0006) {
|
||||
z.PartETags = (z.PartETags)[:zb0006]
|
||||
} else {
|
||||
z.PartETags = make([]string, zb0006)
|
||||
@@ -926,7 +926,7 @@ func (z *xlMetaV2Object) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "PartActualSizes")
|
||||
return
|
||||
}
|
||||
if cap(z.PartActualSizes) >= int(zb0008) {
|
||||
if z.PartActualSizes != nil && cap(z.PartActualSizes) >= int(zb0008) {
|
||||
z.PartActualSizes = (z.PartActualSizes)[:zb0008]
|
||||
} else {
|
||||
z.PartActualSizes = make([]int64, zb0008)
|
||||
@@ -1061,7 +1061,7 @@ func (z *xlMetaV2Object) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *xlMetaV2Object) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(18)
|
||||
var zb0001Mask uint32 /* 18 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -1256,7 +1256,7 @@ func (z *xlMetaV2Object) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x2000) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x2000) == 0 { // if not omitted
|
||||
// write "PartIdx"
|
||||
err = en.Append(0xa7, 0x50, 0x61, 0x72, 0x74, 0x49, 0x64, 0x78)
|
||||
if err != nil {
|
||||
@@ -1359,7 +1359,7 @@ func (z *xlMetaV2Object) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *xlMetaV2Object) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(18)
|
||||
var zb0001Mask uint32 /* 18 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -1434,7 +1434,7 @@ func (z *xlMetaV2Object) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.AppendInt64(o, z.PartActualSizes[za0007])
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x2000) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x2000) == 0 { // if not omitted
|
||||
// string "PartIdx"
|
||||
o = append(o, 0xa7, 0x50, 0x61, 0x72, 0x74, 0x49, 0x64, 0x78)
|
||||
o = msgp.AppendArrayHeader(o, uint32(len(z.PartIndices)))
|
||||
@@ -1596,7 +1596,7 @@ func (z *xlMetaV2Object) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "PartETags")
|
||||
return
|
||||
}
|
||||
if cap(z.PartETags) >= int(zb0006) {
|
||||
if z.PartETags != nil && cap(z.PartETags) >= int(zb0006) {
|
||||
z.PartETags = (z.PartETags)[:zb0006]
|
||||
} else {
|
||||
z.PartETags = make([]string, zb0006)
|
||||
@@ -1639,7 +1639,7 @@ func (z *xlMetaV2Object) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "PartActualSizes")
|
||||
return
|
||||
}
|
||||
if cap(z.PartActualSizes) >= int(zb0008) {
|
||||
if z.PartActualSizes != nil && cap(z.PartActualSizes) >= int(zb0008) {
|
||||
z.PartActualSizes = (z.PartActualSizes)[:zb0008]
|
||||
} else {
|
||||
z.PartActualSizes = make([]int64, zb0008)
|
||||
@@ -1893,7 +1893,7 @@ func (z *xlMetaV2Version) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(5)
|
||||
var zb0001Mask uint8 /* 5 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -1927,7 +1927,7 @@ func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Type")
|
||||
return
|
||||
}
|
||||
if (zb0001Mask & 0x2) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||
// write "V1Obj"
|
||||
err = en.Append(0xa5, 0x56, 0x31, 0x4f, 0x62, 0x6a)
|
||||
if err != nil {
|
||||
@@ -1946,7 +1946,7 @@ func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x4) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x4) == 0 { // if not omitted
|
||||
// write "V2Obj"
|
||||
err = en.Append(0xa5, 0x56, 0x32, 0x4f, 0x62, 0x6a)
|
||||
if err != nil {
|
||||
@@ -1965,7 +1965,7 @@ func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x8) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x8) == 0 { // if not omitted
|
||||
// write "DelObj"
|
||||
err = en.Append(0xa6, 0x44, 0x65, 0x6c, 0x4f, 0x62, 0x6a)
|
||||
if err != nil {
|
||||
@@ -2000,7 +2000,7 @@ func (z *xlMetaV2Version) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *xlMetaV2Version) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// omitempty: check for empty values
|
||||
// check for omitted fields
|
||||
zb0001Len := uint32(5)
|
||||
var zb0001Mask uint8 /* 5 bits */
|
||||
_ = zb0001Mask
|
||||
@@ -2024,7 +2024,7 @@ func (z *xlMetaV2Version) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
// string "Type"
|
||||
o = append(o, 0xa4, 0x54, 0x79, 0x70, 0x65)
|
||||
o = msgp.AppendUint8(o, uint8(z.Type))
|
||||
if (zb0001Mask & 0x2) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x2) == 0 { // if not omitted
|
||||
// string "V1Obj"
|
||||
o = append(o, 0xa5, 0x56, 0x31, 0x4f, 0x62, 0x6a)
|
||||
if z.ObjectV1 == nil {
|
||||
@@ -2037,7 +2037,7 @@ func (z *xlMetaV2Version) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x4) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x4) == 0 { // if not omitted
|
||||
// string "V2Obj"
|
||||
o = append(o, 0xa5, 0x56, 0x32, 0x4f, 0x62, 0x6a)
|
||||
if z.ObjectV2 == nil {
|
||||
@@ -2050,7 +2050,7 @@ func (z *xlMetaV2Version) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (zb0001Mask & 0x8) == 0 { // if not empty
|
||||
if (zb0001Mask & 0x8) == 0 { // if not omitted
|
||||
// string "DelObj"
|
||||
o = append(o, 0xa6, 0x44, 0x65, 0x6c, 0x4f, 0x62, 0x6a)
|
||||
if z.DeleteMarker == nil {
|
||||
|
||||
+45
-29
@@ -54,8 +54,6 @@ import (
|
||||
|
||||
const (
|
||||
nullVersionID = "null"
|
||||
// Largest streams threshold per shard.
|
||||
largestFileThreshold = 64 * humanize.MiByte // Optimized for HDDs
|
||||
|
||||
// Small file threshold below which data accompanies metadata from storage layer.
|
||||
smallFileThreshold = 128 * humanize.KiByte // Optimized for NVMe/SSDs
|
||||
@@ -330,6 +328,32 @@ func newXLStorage(ep Endpoint, cleanUp bool) (s *xlStorage, err error) {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Initialize DiskInfo cache
|
||||
s.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{},
|
||||
func() (DiskInfo, error) {
|
||||
dcinfo := DiskInfo{}
|
||||
di, err := getDiskInfo(s.drivePath)
|
||||
if err != nil {
|
||||
return dcinfo, err
|
||||
}
|
||||
dcinfo.Major = di.Major
|
||||
dcinfo.Minor = di.Minor
|
||||
dcinfo.Total = di.Total
|
||||
dcinfo.Free = di.Free
|
||||
dcinfo.Used = di.Used
|
||||
dcinfo.UsedInodes = di.Files - di.Ffree
|
||||
dcinfo.FreeInodes = di.Ffree
|
||||
dcinfo.FSType = di.FSType
|
||||
diskID, err := s.GetDiskID()
|
||||
// Healing is 'true' when
|
||||
// - if we found an unformatted disk (no 'format.json')
|
||||
// - if we found healing tracker 'healing.bin'
|
||||
dcinfo.Healing = errors.Is(err, errUnformattedDisk) || (s.Healing() != nil)
|
||||
dcinfo.ID = diskID
|
||||
return dcinfo, err
|
||||
},
|
||||
)
|
||||
|
||||
// Success.
|
||||
return s, nil
|
||||
}
|
||||
@@ -745,31 +769,6 @@ func (s *xlStorage) setWriteAttribute(writeCount uint64) error {
|
||||
// DiskInfo provides current information about disk space usage,
|
||||
// total free inodes and underlying filesystem.
|
||||
func (s *xlStorage) DiskInfo(_ context.Context, _ DiskInfoOptions) (info DiskInfo, err error) {
|
||||
s.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{},
|
||||
func() (DiskInfo, error) {
|
||||
dcinfo := DiskInfo{}
|
||||
di, err := getDiskInfo(s.drivePath)
|
||||
if err != nil {
|
||||
return dcinfo, err
|
||||
}
|
||||
dcinfo.Major = di.Major
|
||||
dcinfo.Minor = di.Minor
|
||||
dcinfo.Total = di.Total
|
||||
dcinfo.Free = di.Free
|
||||
dcinfo.Used = di.Used
|
||||
dcinfo.UsedInodes = di.Files - di.Ffree
|
||||
dcinfo.FreeInodes = di.Ffree
|
||||
dcinfo.FSType = di.FSType
|
||||
diskID, err := s.GetDiskID()
|
||||
// Healing is 'true' when
|
||||
// - if we found an unformatted disk (no 'format.json')
|
||||
// - if we found healing tracker 'healing.bin'
|
||||
dcinfo.Healing = errors.Is(err, errUnformattedDisk) || (s.Healing() != nil)
|
||||
dcinfo.ID = diskID
|
||||
return dcinfo, err
|
||||
},
|
||||
)
|
||||
|
||||
info, err = s.diskInfoCache.Get()
|
||||
info.NRRequests = s.nrRequests
|
||||
info.Rotational = s.rotational
|
||||
@@ -1196,6 +1195,19 @@ func (s *xlStorage) cleanupTrashImmediateCallers(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
const almostFilledPercent = 0.05
|
||||
|
||||
func (s *xlStorage) diskAlmostFilled() bool {
|
||||
info, err := s.diskInfoCache.Get()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if info.Used == 0 || info.UsedInodes == 0 {
|
||||
return false
|
||||
}
|
||||
return (float64(info.Free)/float64(info.Used)) < almostFilledPercent || (float64(info.FreeInodes)/float64(info.UsedInodes)) < almostFilledPercent
|
||||
}
|
||||
|
||||
func (s *xlStorage) moveToTrash(filePath string, recursive, immediatePurge bool) (err error) {
|
||||
pathUUID := mustGetUUID()
|
||||
targetPath := pathutil.Join(s.drivePath, minioMetaTmpDeletedBucket, pathUUID)
|
||||
@@ -1227,6 +1239,10 @@ func (s *xlStorage) moveToTrash(filePath string, recursive, immediatePurge bool)
|
||||
return err
|
||||
}
|
||||
|
||||
if !immediatePurge && s.diskAlmostFilled() {
|
||||
immediatePurge = true
|
||||
}
|
||||
|
||||
// immediately purge the target
|
||||
if immediatePurge {
|
||||
for _, target := range []string{
|
||||
@@ -2113,11 +2129,11 @@ func (s *xlStorage) writeAllDirect(ctx context.Context, filePath string, fileSiz
|
||||
|
||||
var bufp *[]byte
|
||||
switch {
|
||||
case fileSize > 0 && fileSize >= largestFileThreshold:
|
||||
case fileSize > 0 && fileSize >= xioutil.BlockSizeReallyLarge:
|
||||
// use a larger 4MiB buffer for a really large streams.
|
||||
bufp = xioutil.ODirectPoolXLarge.Get().(*[]byte)
|
||||
defer xioutil.ODirectPoolXLarge.Put(bufp)
|
||||
case fileSize <= smallFileThreshold:
|
||||
case fileSize <= xioutil.BlockSizeSmall:
|
||||
bufp = xioutil.ODirectPoolSmall.Get().(*[]byte)
|
||||
defer xioutil.ODirectPoolSmall.Put(bufp)
|
||||
default:
|
||||
|
||||
@@ -10,10 +10,10 @@ trap 'catch $LINENO' ERR
|
||||
catch() {
|
||||
if [ $# -ne 0 ]; then
|
||||
echo "error on line $1"
|
||||
echo "$site server logs ========="
|
||||
cat "/tmp/${site}_1.log"
|
||||
echo "server logs ========="
|
||||
cat "/tmp/sitea_1.log"
|
||||
echo "==========================="
|
||||
cat "/tmp/${site}_2.log"
|
||||
cat "/tmp/sitea_2.log"
|
||||
fi
|
||||
|
||||
echo "Cleaning up instances of MinIO"
|
||||
@@ -42,32 +42,34 @@ if [ ! -f ./mc ]; then
|
||||
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}" \
|
||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||
minio server --address 127.0.0.1:9002 "http://127.0.0.1:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"http://127.0.0.1:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||
minio server --address ":9001" "https://localhost:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"https://localhost:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_1.log 2>&1 &
|
||||
minio server --address ":9002" "https://localhost:9001/tmp/multisitea/data/disterasure/xl{1...4}" \
|
||||
"https://localhost:9002/tmp/multisitea/data/disterasure/xl{5...8}" >/tmp/sitea_2.log 2>&1 &
|
||||
|
||||
export MC_HOST_sitea=http://minio:minio123@127.0.0.1:9001
|
||||
sleep 60
|
||||
|
||||
./mc mb sitea/delissue
|
||||
export MC_HOST_sitea=https://minio:minio123@localhost:9001
|
||||
|
||||
./mc version enable sitea/delissue
|
||||
./mc mb sitea/delissue --insecure
|
||||
|
||||
echo hello | ./mc pipe sitea/delissue/hello
|
||||
./mc version enable sitea/delissue --insecure
|
||||
|
||||
./mc version suspend sitea/delissue
|
||||
echo hello | ./mc pipe sitea/delissue/hello --insecure
|
||||
|
||||
./mc rm sitea/delissue/hello
|
||||
./mc version suspend sitea/delissue --insecure
|
||||
|
||||
./mc version enable sitea/delissue
|
||||
./mc rm sitea/delissue/hello --insecure
|
||||
|
||||
echo hello | ./mc pipe sitea/delissue/hello
|
||||
./mc version enable sitea/delissue --insecure
|
||||
|
||||
./mc version suspend sitea/delissue
|
||||
echo hello | ./mc pipe sitea/delissue/hello --insecure
|
||||
|
||||
./mc rm sitea/delissue/hello
|
||||
./mc version suspend sitea/delissue --insecure
|
||||
|
||||
count=$(./mc ls --versions sitea/delissue | wc -l)
|
||||
./mc rm sitea/delissue/hello --insecure
|
||||
|
||||
count=$(./mc ls --versions sitea/delissue --insecure | wc -l)
|
||||
|
||||
if [ ${count} -ne 3 ]; then
|
||||
echo "BUG: expected number of versions to be '3' found ${count}"
|
||||
@@ -76,6 +78,6 @@ if [ ${count} -ne 3 ]; then
|
||||
fi
|
||||
|
||||
echo "SUCCESS:"
|
||||
./mc ls --versions sitea/delissue
|
||||
./mc ls --versions sitea/delissue --insecure
|
||||
|
||||
catch
|
||||
|
||||
@@ -15,12 +15,20 @@ Refer to the dashboard [json file here](https://raw.githubusercontent.com/minio/
|
||||
|
||||

|
||||
|
||||
Replication metrics can be viewed in the Grafana dashboard using [json file here](https://raw.githubusercontent.com/minio/minio/master/docs/metrics/prometheus/grafana/replication/minio-replication.json)
|
||||
Node level Replication metrics can be viewed in the Grafana dashboard using [json file here](https://raw.githubusercontent.com/minio/minio/master/docs/metrics/prometheus/grafana/replication/minio-replication-node.json)
|
||||
|
||||

|
||||

|
||||
|
||||
CLuster level Replication metrics can be viewed in the Grafana dashboard using [json file here](https://raw.githubusercontent.com/minio/minio/master/docs/metrics/prometheus/grafana/replication/minio-replication-cluster.json)
|
||||
|
||||

|
||||
|
||||
Bucket metrics can be viewed in the Grafana dashboard using [json file here](https://raw.githubusercontent.com/minio/minio/master/docs/metrics/prometheus/grafana/bubcket/minio-bucket.json)
|
||||
|
||||

|
||||
|
||||
Node metrics can be viewed in the Grafana dashboard using [json file here](https://raw.githubusercontent.com/minio/minio/master/docs/metrics/prometheus/grafana/node/minio-node.json)
|
||||
|
||||

|
||||
|
||||
Note: All these dashboards are provided as an example and need basis they should be customized as well as new graphs should be added.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
@@ -0,0 +1,978 @@
|
||||
{
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "grafana"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"__inputs": [
|
||||
{
|
||||
"name": "DS_PROMETHEUS",
|
||||
"label": "Prometheus",
|
||||
"description": "",
|
||||
"type": "datasource",
|
||||
"pluginId": "prometheus",
|
||||
"pluginName": "Prometheus"
|
||||
}
|
||||
],
|
||||
"description": "MinIO Nodes Grafana Dashboard - https://min.io/",
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"gnetId": 15306,
|
||||
"graphTooltip": 0,
|
||||
"id": 267,
|
||||
"links": [
|
||||
{
|
||||
"icon": "external link",
|
||||
"includeVars": true,
|
||||
"keepTime": true,
|
||||
"tags": [
|
||||
"minio"
|
||||
],
|
||||
"type": "dashboards"
|
||||
}
|
||||
],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [
|
||||
{
|
||||
"options": {
|
||||
"match": "null",
|
||||
"result": {
|
||||
"text": "N/A"
|
||||
}
|
||||
},
|
||||
"type": "special"
|
||||
}
|
||||
],
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "short"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 21,
|
||||
"maxDataPoints": 100,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"mean"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"text": {},
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "max(minio_node_drive_total{job=~\"$scrape_jobs\",server=\"$server\"})",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "",
|
||||
"metric": "process_start_time_seconds",
|
||||
"refId": "A",
|
||||
"step": 60
|
||||
}
|
||||
],
|
||||
"title": "Total Drives",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"mappings": [],
|
||||
"thresholds": {
|
||||
"mode": "percentage",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 0
|
||||
},
|
||||
"id": 22,
|
||||
"maxDataPoints": 100,
|
||||
"options": {
|
||||
"minVizHeight": 75,
|
||||
"minVizWidth": 75,
|
||||
"orientation": "auto",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showThresholdLabels": false,
|
||||
"showThresholdMarkers": true,
|
||||
"sizing": "auto"
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "max(minio_node_drive_online_total{job=~\"$scrape_jobs\",server=\"$server\"})",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": ".",
|
||||
"metric": "process_start_time_seconds",
|
||||
"range": false,
|
||||
"refId": "A",
|
||||
"step": 60
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": false,
|
||||
"expr": "max(minio_node_drive_offline_total{job=~\"$scrape_jobs\",server=\"$server\"})",
|
||||
"format": "time_series",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
"legendFormat": ".",
|
||||
"range": false,
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Total Online/Offline Drives",
|
||||
"type": "gauge"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "bytes"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 23,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_total_bytes{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "Total [{{drive}}]",
|
||||
"refId": "A"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_used_bytes{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "Used [{{drive}}]",
|
||||
"refId": "B"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_free_bytes{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "Free [{{drive}}]",
|
||||
"refId": "C"
|
||||
}
|
||||
],
|
||||
"title": "Drive Usage",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"__systemRef": "hideSeriesFrom",
|
||||
"matcher": {
|
||||
"id": "byNames",
|
||||
"options": {
|
||||
"mode": "exclude",
|
||||
"names": [
|
||||
"[play.min.io:9000:/disk4/data]"
|
||||
],
|
||||
"prefix": "All except:",
|
||||
"readOnly": true
|
||||
}
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.hideFrom",
|
||||
"value": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 0
|
||||
},
|
||||
"id": 24,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_free_inodes{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "[{{drive}}]",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Free Inodes",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 0,
|
||||
"y": 5
|
||||
},
|
||||
"id": 25,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_latency_us{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "[{{drive}}:{{api}}]",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Drive Latency (micro sec)",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 6,
|
||||
"y": 5
|
||||
},
|
||||
"id": 26,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_errors_availability{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "[{{drive}}]",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Drive Errors",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "available 10.13.1.25:9000"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "green",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "used 10.13.1.25:9000"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"fixedColor": "blue",
|
||||
"mode": "fixed"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 12,
|
||||
"y": 5
|
||||
},
|
||||
"id": 27,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_errors_timeout{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "[{{drive}}]",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "Drive Timeout Errors",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"description": "",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"axisBorderShow": false,
|
||||
"axisCenteredZero": false,
|
||||
"axisColorMode": "text",
|
||||
"axisLabel": "",
|
||||
"axisPlacement": "auto",
|
||||
"barAlignment": 0,
|
||||
"drawStyle": "line",
|
||||
"fillOpacity": 10,
|
||||
"gradientMode": "none",
|
||||
"hideFrom": {
|
||||
"legend": false,
|
||||
"tooltip": false,
|
||||
"viz": false
|
||||
},
|
||||
"insertNulls": false,
|
||||
"lineInterpolation": "linear",
|
||||
"lineWidth": 1,
|
||||
"pointSize": 5,
|
||||
"scaleDistribution": {
|
||||
"type": "linear"
|
||||
},
|
||||
"showPoints": "never",
|
||||
"spanNulls": false,
|
||||
"stacking": {
|
||||
"group": "A",
|
||||
"mode": "none"
|
||||
},
|
||||
"thresholdsStyle": {
|
||||
"mode": "off"
|
||||
}
|
||||
},
|
||||
"links": [],
|
||||
"mappings": [],
|
||||
"min": 0,
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 80
|
||||
}
|
||||
]
|
||||
},
|
||||
"unit": "none"
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 5,
|
||||
"w": 6,
|
||||
"x": 18,
|
||||
"y": 5
|
||||
},
|
||||
"id": 28,
|
||||
"options": {
|
||||
"legend": {
|
||||
"calcs": [],
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "none"
|
||||
}
|
||||
},
|
||||
"pluginVersion": "10.4.0",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_node_drive_io_waiting{job=~\"$scrape_jobs\",server=\"$server\"}",
|
||||
"interval": "",
|
||||
"legendFormat": "[{{drive}}]",
|
||||
"refId": "B"
|
||||
}
|
||||
],
|
||||
"title": "IO Operations Waiting",
|
||||
"type": "timeseries"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 39,
|
||||
"tags": [
|
||||
"minio"
|
||||
],
|
||||
"templating": {
|
||||
"list": [
|
||||
{
|
||||
"current": {
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(job)",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"multi": false,
|
||||
"name": "scrape_jobs",
|
||||
"options": [],
|
||||
"query": {
|
||||
"query": "label_values(job)",
|
||||
"refId": "StandardVariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
},
|
||||
{
|
||||
"current": {
|
||||
},
|
||||
"datasource": {
|
||||
"type": "prometheus",
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"definition": "label_values(minio_node_drive_total,server)",
|
||||
"hide": 0,
|
||||
"includeAll": false,
|
||||
"label": "Server",
|
||||
"multi": false,
|
||||
"name": "server",
|
||||
"options": [],
|
||||
"query": {
|
||||
"qryType": 1,
|
||||
"query": "label_values(minio_node_drive_total,server)",
|
||||
"refId": "PrometheusVariableQueryEditor-VariableQuery"
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": 0,
|
||||
"type": "query"
|
||||
}
|
||||
]
|
||||
},
|
||||
"time": {
|
||||
"from": "now-1h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"10s",
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"30m",
|
||||
"1h",
|
||||
"2h",
|
||||
"1d"
|
||||
],
|
||||
"time_options": [
|
||||
"5m",
|
||||
"15m",
|
||||
"1h",
|
||||
"6h",
|
||||
"12h",
|
||||
"24h",
|
||||
"2d",
|
||||
"7d",
|
||||
"30d"
|
||||
]
|
||||
},
|
||||
"timezone": "",
|
||||
"title": "MinIO Node Dashboard",
|
||||
"uid": "TgmJnnqn2k",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 444 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 228 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -84,6 +84,36 @@ For deployments with [bucket](https://min.io/docs/minio/linux/administration/buc
|
||||
|
||||
| Name | Description
|
||||
|:-----------------------------------------------------------|:---------------------------------------------------------------------------------------------------------|
|
||||
| `minio_cluster_replication_last_hour_failed_bytes` | (_Site Replication Only_) Total number of bytes failed at least once to replicate in the last full hour. |
|
||||
| `minio_cluster_replication_last_hour_failed_count` | (_Site Replication Only_) Total number of objects which failed replication in the last full hour. |
|
||||
| `minio_cluster_replication_last_minute_failed_bytes` | Total number of bytes failed at least once to replicate in the last full minute. |
|
||||
| `minio_cluster_replication_last_minute_failed_count` | Total number of objects which failed replication in the last full minute. |
|
||||
| `minio_cluster_replication_total_failed_bytes` | (_Site Replication Only_) Total number of bytes failed at least once to replicate since server start. |
|
||||
| `minio_cluster_replication_total_failed_count` | (_Site Replication Only_) Total number of objects which failed replication since server start. |
|
||||
| `minio_cluster_replication_received_bytes` | (_Site Replication Only_) Total number of bytes replicated to this cluster from another source cluster. |
|
||||
| `minio_cluster_replication_received_count` | (_Site Replication Only_) Total number of objects received by this cluster from another source cluster. |
|
||||
| `minio_cluster_replication_sent_bytes` | (_Site Replication Only_) Total number of bytes replicated to the target cluster. |
|
||||
| `minio_cluster_replication_sent_count` | (_Site Replication Only_) Total number of objects replicated to the target cluster. |
|
||||
| `minio_cluster_replication_credential_errors` | (_Site Replication Only_) Total number of replication credential errors since server start |
|
||||
| `minio_cluster_replication_proxied_get_requests_total` | (_Site Replication Only_)Number of GET requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_head_requests_total` | (_Site Replication Only_)Number of HEAD requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_delete_tagging_requests_total` | (_Site Replication Only_)Number of DELETE tagging requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_get_tagging_requests_total` | (_Site Replication Only_)Number of GET tagging requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_put_tagging_requests_total` | (_Site Replication Only_)Number of PUT tagging requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_get_requests_failures` | (_Site Replication Only_)Number of failures in GET requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_head_requests_failures` | (_Site Replication Only_)Number of failures in HEAD requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_delete_tagging_requests_failures` | (_Site Replication Only_)Number of failures proxying DELETE tagging requests to replication target |
|
||||
| `minio_cluster_replication_proxied_get_tagging_requests_failures` | (_Site Replication Only_)Number of failures proxying GET tagging requests to replication target |
|
||||
| `minio_cluster_replication_proxied_put_tagging_requests_failures` | (_Site Replication Only_)Number of failures proxying PUT tagging requests to replication target |
|
||||
|
||||
|
||||
## Node Replication Metrics
|
||||
|
||||
Metrics marked as ``Site Replication Only`` only populate on deployments with [Site Replication](https://min.io/docs/minio/linux/operations/install-deploy-manage/multi-site-replication.html) configurations.
|
||||
For deployments with [bucket](https://min.io/docs/minio/linux/administration/bucket-replication.html) or [batch](https://min.io/docs/minio/linux/administration/batch-framework.html#replicate) configurations, these metrics populate instead under the [Bucket Metrics](#bucket-metrics) endpoint.
|
||||
|
||||
| Name | Description
|
||||
|:-----------------------------------------------------------|:---------------------------------------------------------------------------------------------------------|
|
||||
| `minio_cluster_replication_current_active_workers` | Total number of active replication workers |
|
||||
| `minio_cluster_replication_average_active_workers` | Average number of active replication workers |
|
||||
| `minio_cluster_replication_max_active_workers` | Maximum number of active replication workers seen since server start |
|
||||
@@ -103,28 +133,6 @@ For deployments with [bucket](https://min.io/docs/minio/linux/administration/buc
|
||||
| `minio_cluster_replication_max_queued_bytes` | Maximum number of bytes queued for replication seen since server start |
|
||||
| `minio_cluster_replication_max_queued_count` | Maximum number of objects queued for replication seen since server start |
|
||||
| `minio_cluster_replication_recent_backlog_count` | Total number of objects seen in replication backlog in the last 5 minutes |
|
||||
| `minio_cluster_replication_last_minute_failed_bytes` | Total number of bytes failed at least once to replicate in the last full minute. |
|
||||
| `minio_cluster_replication_last_minute_failed_count` | Total number of objects which failed replication in the last full minute. |
|
||||
| `minio_cluster_replication_last_hour_failed_bytes` | (_Site Replication Only_) Total number of bytes failed at least once to replicate in the last full hour. |
|
||||
| `minio_cluster_replication_last_hour_failed_count` | (_Site Replication Only_) Total number of objects which failed replication in the last full hour. |
|
||||
| `minio_cluster_replication_total_failed_bytes` | (_Site Replication Only_) Total number of bytes failed at least once to replicate since server start. |
|
||||
| `minio_cluster_replication_total_failed_count` | (_Site Replication Only_) Total number of objects which failed replication since server start. |
|
||||
| `minio_cluster_replication_received_bytes` | (_Site Replication Only_) Total number of bytes replicated to this cluster from another source cluster. |
|
||||
| `minio_cluster_replication_received_count` | (_Site Replication Only_) Total number of objects received by this cluster from another source cluster. |
|
||||
| `minio_cluster_replication_sent_bytes` | (_Site Replication Only_) Total number of bytes replicated to the target cluster. |
|
||||
| `minio_cluster_replication_sent_count` | (_Site Replication Only_) Total number of objects replicated to the target cluster. |
|
||||
| `minio_cluster_replication_credential_errors` | (_Site Replication Only_) Total number of replication credential errors since server start |
|
||||
| `minio_cluster_replication_proxied_get_requests_total` | (_Site Replication Only_)Number of GET requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_head_requests_total` | (_Site Replication Only_)Number of HEAD requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_delete_tagging_requests_total` | (_Site Replication Only_)Number of DELETE tagging requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_get_tagging_requests_total` | (_Site Replication Only_)Number of GET tagging requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_put_tagging_requests_total` | (_Site Replication Only_)Number of PUT tagging requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_get_requests_failures` | (_Site Replication Only_)Number of failures in GET requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_head_requests_failures` | (_Site Replication Only_)Number of failures in HEAD requests proxied to replication target |
|
||||
| `minio_cluster_replication_proxied_delete_tagging_requests_failures` | (_Site Replication Only_)Number of failures proxying DELETE tagging requests to replication target |
|
||||
| `minio_cluster_replication_proxied_get_tagging_requests_failures` | (_Site Replication Only_)Number of failures proxying GET tagging requests to replication target |
|
||||
| `minio_cluster_replication_proxied_put_tagging_requests_failures` | (_Site Replication Only_)Number of failures proxying PUT tagging requests to replication target |
|
||||
|
||||
|
||||
## Healing Metrics
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
# Settings and configurations that are common for all containers
|
||||
x-minio-common: &minio-common
|
||||
image: quay.io/minio/minio:RELEASE.2024-03-10T02-53-48Z
|
||||
image: quay.io/minio/minio:RELEASE.2024-03-26T22-10-45Z
|
||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||
expose:
|
||||
- "9000"
|
||||
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck disable=SC2120
|
||||
exit_1() {
|
||||
cleanup
|
||||
|
||||
echo "minio1 ============"
|
||||
cat /tmp/minio1_1.log
|
||||
echo "minio2 ============"
|
||||
cat /tmp/minio2_1.log
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo -n "Cleaning up instances of MinIO ..."
|
||||
pkill -9 minio || sudo pkill -9 minio
|
||||
pkill -9 kes || sudo pkill -9 kes
|
||||
rm -rf ${PWD}/keys
|
||||
rm -rf /tmp/minio{1,2}
|
||||
echo "done"
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_BROWSER=off
|
||||
export MINIO_ROOT_USER="minio"
|
||||
export MINIO_ROOT_PASSWORD="minio123"
|
||||
|
||||
# Create certificates for TLS enabled MinIO
|
||||
echo -n "Setup certs for MinIO instances ..."
|
||||
wget -O certgen https://github.com/minio/certgen/releases/latest/download/certgen-linux-amd64 && chmod +x certgen
|
||||
./certgen --host localhost
|
||||
mkdir -p ~/.minio/certs
|
||||
mv public.crt ~/.minio/certs || sudo mv public.crt ~/.minio/certs
|
||||
mv private.key ~/.minio/certs || sudo mv private.key ~/.minio/certs
|
||||
echo "done"
|
||||
|
||||
# Start MinIO instances
|
||||
echo -n "Starting MinIO instances ..."
|
||||
CI=on MINIO_KMS_SECRET_KEY=minio-default-key:IyqsU3kMFloCNup4BsZtf/rmfHVcTgznO2F25CkEH1g= MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||
CI=on MINIO_KMS_SECRET_KEY=minio-default-key:IyqsU3kMFloCNup4BsZtf/rmfHVcTgznO2F25CkEH1g= MINIO_ROOT_USER=minio MINIO_ROOT_PASSWORD=minio123 minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||
echo "done"
|
||||
|
||||
if [ ! -f ./mc ]; then
|
||||
echo -n "Downloading MinIO client ..."
|
||||
wget -O mc https://dl.min.io/client/mc/release/linux-amd64/mc &&
|
||||
chmod +x mc
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
|
||||
export MC_HOST_minio1=https://minio:minio123@localhost:9001
|
||||
export MC_HOST_minio2=https://minio:minio123@localhost:9002
|
||||
|
||||
# Prepare data for tests
|
||||
echo -n "Preparing test data ..."
|
||||
mkdir -p /tmp/data
|
||||
echo "Hello from encrypted world" >/tmp/data/encrypted
|
||||
touch /tmp/data/mpartobj
|
||||
shred -s 500M /tmp/data/mpartobj
|
||||
touch /tmp/data/defpartsize
|
||||
shred -s 500M /tmp/data/defpartsize
|
||||
touch /tmp/data/custpartsize
|
||||
shred -s 500M /tmp/data/custpartsize
|
||||
echo "done"
|
||||
|
||||
# Add replication site
|
||||
./mc admin replicate add minio1 minio2 --insecure
|
||||
# sleep for replication to complete
|
||||
sleep 30
|
||||
|
||||
# Create bucket in source cluster
|
||||
echo "Create bucket in source MinIO instance"
|
||||
./mc mb minio1/test-bucket --insecure
|
||||
|
||||
# Enable SSE KMS for the bucket
|
||||
./mc encrypt set sse-kms minio-default-key minio1/test-bucket --insecure
|
||||
|
||||
# Load objects to source site
|
||||
echo "Loading objects to source MinIO instance"
|
||||
./mc cp /tmp/data/encrypted minio1/test-bucket --insecure
|
||||
./mc cp /tmp/data/mpartobj minio1/test-bucket --encrypt-key "minio1/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure
|
||||
./mc cp /tmp/data/defpartsize minio1/test-bucket --insecure
|
||||
./mc put /tmp/data/custpartsize minio1/test-bucket --insecure --part-size 50MiB
|
||||
sleep 120
|
||||
|
||||
# List the objects from source site
|
||||
echo "Objects from source instance"
|
||||
./mc ls minio1/test-bucket --insecure
|
||||
count1=$(./mc ls minio1/test-bucket/encrypted --insecure | wc -l)
|
||||
if [ "${count1}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/encrypted not found"
|
||||
exit_1
|
||||
fi
|
||||
count2=$(./mc ls minio1/test-bucket/mpartobj --insecure | wc -l)
|
||||
if [ "${count2}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/mpartobj not found"
|
||||
exit_1
|
||||
fi
|
||||
count3=$(./mc ls minio1/test-bucket/defpartsize --insecure | wc -l)
|
||||
if [ "${count3}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/defpartsize not found"
|
||||
exit_1
|
||||
fi
|
||||
count4=$(./mc ls minio1/test-bucket/custpartsize --insecure | wc -l)
|
||||
if [ "${count4}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/custpartsize not found"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# List the objects from replicated site
|
||||
echo "Objects from replicated instance"
|
||||
./mc ls minio2/test-bucket --insecure
|
||||
repcount1=$(./mc ls minio2/test-bucket/encrypted --insecure | wc -l)
|
||||
if [ "${repcount1}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/encrypted not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount2=$(./mc ls minio2/test-bucket/mpartobj --insecure | wc -l)
|
||||
if [ "${repcount2}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/mpartobj not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount3=$(./mc ls minio2/test-bucket/defpartsize --insecure | wc -l)
|
||||
if [ "${repcount3}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/defpartsize not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount4=$(./mc ls minio2/test-bucket/custpartsize --insecure | wc -l)
|
||||
if [ "${repcount4}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/custpartsize not replicated"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Stat the objects from source site
|
||||
echo "Stat minio1/test-bucket/encrypted"
|
||||
./mc stat minio1/test-bucket/encrypted --insecure --json
|
||||
stat_out1=$(./mc stat minio1/test-bucket/encrypted --insecure --json)
|
||||
src_obj1_algo=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||
src_obj1_keyid=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||
echo "Stat minio1/test-bucket/defpartsize"
|
||||
./mc stat minio1/test-bucket/defpartsize --insecure --json
|
||||
stat_out2=$(./mc stat minio1/test-bucket/defpartsize --insecure --json)
|
||||
src_obj2_algo=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||
src_obj2_keyid=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||
echo "Stat minio1/test-bucket/custpartsize"
|
||||
./mc stat minio1/test-bucket/custpartsize --insecure --json
|
||||
stat_out3=$(./mc stat minio1/test-bucket/custpartsize --insecure --json)
|
||||
src_obj3_algo=$(echo "${stat_out3}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||
src_obj3_keyid=$(echo "${stat_out3}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||
echo "Stat minio1/test-bucket/mpartobj"
|
||||
./mc stat minio1/test-bucket/mpartobj --encrypt-key "minio1/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out4=$(./mc stat minio1/test-bucket/mpartobj --encrypt-key "minio1/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
src_obj4_etag=$(echo "${stat_out4}" | jq '.etag')
|
||||
src_obj4_size=$(echo "${stat_out4}" | jq '.size')
|
||||
src_obj4_md5=$(echo "${stat_out4}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
|
||||
# Stat the objects from replicated site
|
||||
echo "Stat minio2/test-bucket/encrypted"
|
||||
./mc stat minio2/test-bucket/encrypted --insecure --json
|
||||
stat_out1_rep=$(./mc stat minio2/test-bucket/encrypted --insecure --json)
|
||||
rep_obj1_algo=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||
rep_obj1_keyid=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||
echo "Stat minio2/test-bucket/defpartsize"
|
||||
./mc stat minio2/test-bucket/defpartsize --insecure --json
|
||||
stat_out2_rep=$(./mc stat minio2/test-bucket/defpartsize --insecure --json)
|
||||
rep_obj2_algo=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||
rep_obj2_keyid=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||
echo "Stat minio2/test-bucket/custpartsize"
|
||||
./mc stat minio2/test-bucket/custpartsize --insecure --json
|
||||
stat_out3_rep=$(./mc stat minio2/test-bucket/custpartsize --insecure --json)
|
||||
rep_obj3_algo=$(echo "${stat_out3_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption"')
|
||||
rep_obj3_keyid=$(echo "${stat_out3_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Aws-Kms-Key-Id"')
|
||||
echo "Stat minio2/test-bucket/mpartobj"
|
||||
./mc stat minio2/test-bucket/mpartobj --encrypt-key "minio2/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out4_rep=$(./mc stat minio2/test-bucket/mpartobj --encrypt-key "minio2/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
rep_obj4_etag=$(echo "${stat_out4}" | jq '.etag')
|
||||
rep_obj4_size=$(echo "${stat_out4}" | jq '.size')
|
||||
rep_obj4_md5=$(echo "${stat_out4}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
|
||||
# Check the algo and keyId of replicated objects
|
||||
if [ "${rep_obj1_algo}" != "${src_obj1_algo}" ]; then
|
||||
echo "BUG: Algorithm: '${rep_obj1_algo}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_algo}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj1_keyid}" != "${src_obj1_keyid}" ]; then
|
||||
echo "BUG: KeyId: '${rep_obj1_keyid}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_keyid}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj2_algo}" != "${src_obj2_algo}" ]; then
|
||||
echo "BUG: Algorithm: '${rep_obj2_algo}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_algo}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj2_keyid}" != "${src_obj2_keyid}" ]; then
|
||||
echo "BUG: KeyId: '${rep_obj2_keyid}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_keyid}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj3_algo}" != "${src_obj3_algo}" ]; then
|
||||
echo "BUG: Algorithm: '${rep_obj3_algo}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_algo}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj3_keyid}" != "${src_obj3_keyid}" ]; then
|
||||
echo "BUG: KeyId: '${rep_obj3_keyid}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_keyid}'"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Check the etag, size and md5 of replicated SSEC object
|
||||
if [ "${rep_obj4_etag}" != "${src_obj4_etag}" ]; then
|
||||
echo "BUG: Etag: '${rep_obj4_etag}' of replicated object: 'minio2/test-bucket/mpartobj' doesn't match with source value: '${src_obj4_etag}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj4_size}" != "${src_obj4_size}" ]; then
|
||||
echo "BUG: Size: '${rep_obj4_size}' of replicated object: 'minio2/test-bucket/mpartobj' doesn't match with source value: '${src_obj4_size}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${src_obj4_md5}" != "${rep_obj4_md5}" ]; then
|
||||
echo "BUG: MD5 checksum of object 'minio2/test-bucket/mpartobj' doesn't match with source. Expected: '${src_obj4_md5}', Found: '${rep_obj4_md5}'"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Check content of replicated objects
|
||||
./mc cat minio2/test-bucket/encrypted --insecure
|
||||
./mc cat minio2/test-bucket/mpartobj --encrypt-key "minio2/test-bucket/mpartobj=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||
./mc cat minio2/test-bucket/defpartsize --insecure >/dev/null || exit_1
|
||||
./mc cat minio2/test-bucket/custpartsize --insecure >/dev/null || exit_1
|
||||
|
||||
cleanup
|
||||
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck disable=SC2120
|
||||
exit_1() {
|
||||
cleanup
|
||||
|
||||
echo "minio1 ============"
|
||||
cat /tmp/minio1_1.log
|
||||
echo "minio2 ============"
|
||||
cat /tmp/minio2_1.log
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo -n "Cleaning up instances of MinIO ..."
|
||||
pkill minio || sudo pkill minio
|
||||
pkill -9 minio || sudo pkill -9 minio
|
||||
rm -rf /tmp/minio{1,2}
|
||||
echo "done"
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_BROWSER=off
|
||||
export MINIO_ROOT_USER="minio"
|
||||
export MINIO_ROOT_PASSWORD="minio123"
|
||||
|
||||
# Create certificates for TLS enabled MinIO
|
||||
echo -n "Setup certs for MinIO instances ..."
|
||||
wget -O certgen https://github.com/minio/certgen/releases/latest/download/certgen-linux-amd64 && chmod +x certgen
|
||||
./certgen --host localhost
|
||||
mkdir -p ~/.minio/certs
|
||||
mv public.crt ~/.minio/certs || sudo mv public.crt ~/.minio/certs
|
||||
mv private.key ~/.minio/certs || sudo mv private.key ~/.minio/certs
|
||||
echo "done"
|
||||
|
||||
# Start MinIO instances
|
||||
echo -n "Starting MinIO instances ..."
|
||||
minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||
minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||
echo "done"
|
||||
|
||||
if [ ! -f ./mc ]; then
|
||||
echo -n "Downloading MinIO client ..."
|
||||
wget -O mc https://dl.min.io/client/mc/release/linux-amd64/mc &&
|
||||
chmod +x mc
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
|
||||
export MC_HOST_minio1=https://minio:minio123@localhost:9001
|
||||
export MC_HOST_minio2=https://minio:minio123@localhost:9002
|
||||
|
||||
# Prepare data for tests
|
||||
echo -n "Preparing test data ..."
|
||||
mkdir -p /tmp/data
|
||||
echo "Hello world" >/tmp/data/plainfile
|
||||
echo "Hello from encrypted world" >/tmp/data/encrypted
|
||||
touch /tmp/data/defpartsize
|
||||
shred -s 500M /tmp/data/defpartsize
|
||||
touch /tmp/data/mpartobj.txt
|
||||
shred -s 500M /tmp/data/mpartobj.txt
|
||||
echo "done"
|
||||
|
||||
# Enable compression for site minio1
|
||||
./mc admin config set minio1 compression enable=on extensions=".txt" --insecure
|
||||
./mc admin config set minio1 compression allow_encryption=on --insecure
|
||||
|
||||
# Create bucket in source cluster
|
||||
echo "Create bucket in source MinIO instance"
|
||||
./mc mb minio1/test-bucket --insecure
|
||||
|
||||
# Load objects to source site
|
||||
echo "Loading objects to source MinIO instance"
|
||||
./mc cp /tmp/data/plainfile minio1/test-bucket --insecure
|
||||
./mc cp /tmp/data/encrypted minio1/test-bucket --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||
./mc cp /tmp/data/defpartsize minio1/test-bucket --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure
|
||||
|
||||
# Below should fail as compression and SSEC used at the same time
|
||||
RESULT=$({ ./mc put /tmp/data/mpartobj.txt minio1/test-bucket --encrypt-key "minio1/test-bucket/mpartobj.txt=iliketobecrazybutnotsomuchreally" --insecure; } 2>&1)
|
||||
if [[ ${RESULT} != *"Server side encryption specified with SSE-C with compression not allowed"* ]]; then
|
||||
echo "BUG: Loading an SSE-C object to site with compression should fail. Succeeded though."
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Add replication site
|
||||
./mc admin replicate add minio1 minio2 --insecure
|
||||
# sleep for replication to complete
|
||||
sleep 30
|
||||
|
||||
# List the objects from source site
|
||||
echo "Objects from source instance"
|
||||
./mc ls minio1/test-bucket --insecure
|
||||
count1=$(./mc ls minio1/test-bucket/plainfile --insecure | wc -l)
|
||||
if [ "${count1}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/plainfile not found"
|
||||
exit_1
|
||||
fi
|
||||
count2=$(./mc ls minio1/test-bucket/encrypted --insecure | wc -l)
|
||||
if [ "${count2}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/encrypted not found"
|
||||
exit_1
|
||||
fi
|
||||
count3=$(./mc ls minio1/test-bucket/defpartsize --insecure | wc -l)
|
||||
if [ "${count3}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/defpartsize not found"
|
||||
exit_1
|
||||
fi
|
||||
sleep 120
|
||||
|
||||
# List the objects from replicated site
|
||||
echo "Objects from replicated instance"
|
||||
./mc ls minio2/test-bucket --insecure
|
||||
repcount1=$(./mc ls minio2/test-bucket/plainfile --insecure | wc -l)
|
||||
if [ "${repcount1}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/plainfile not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount2=$(./mc ls minio2/test-bucket/encrypted --insecure | wc -l)
|
||||
if [ "${repcount2}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/encrypted not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount3=$(./mc ls minio2/test-bucket/defpartsize --insecure | wc -l)
|
||||
if [ "${repcount3}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/defpartsize not replicated"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Stat the SSEC objects from source site
|
||||
echo "Stat minio1/test-bucket/encrypted"
|
||||
./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out1=$(./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
src_obj1_etag=$(echo "${stat_out1}" | jq '.etag')
|
||||
src_obj1_size=$(echo "${stat_out1}" | jq '.size')
|
||||
src_obj1_md5=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
echo "Stat minio1/test-bucket/defpartsize"
|
||||
./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out2=$(./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
src_obj2_etag=$(echo "${stat_out2}" | jq '.etag')
|
||||
src_obj2_size=$(echo "${stat_out2}" | jq '.size')
|
||||
src_obj2_md5=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
|
||||
# Stat the SSEC objects from replicated site
|
||||
echo "Stat minio2/test-bucket/encrypted"
|
||||
./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out1_rep=$(./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
rep_obj1_etag=$(echo "${stat_out1_rep}" | jq '.etag')
|
||||
rep_obj1_size=$(echo "${stat_out1_rep}" | jq '.size')
|
||||
rep_obj1_md5=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
echo "Stat minio2/test-bucket/defpartsize"
|
||||
./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out2_rep=$(./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
rep_obj2_etag=$(echo "${stat_out2_rep}" | jq '.etag')
|
||||
rep_obj2_size=$(echo "${stat_out2_rep}" | jq '.size')
|
||||
rep_obj2_md5=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
|
||||
# Check the etag and size of replicated SSEC objects
|
||||
if [ "${rep_obj1_etag}" != "${src_obj1_etag}" ]; then
|
||||
echo "BUG: Etag: '${rep_obj1_etag}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_etag}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj1_size}" != "${src_obj1_size}" ]; then
|
||||
echo "BUG: Size: '${rep_obj1_size}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_size}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj2_etag}" != "${src_obj2_etag}" ]; then
|
||||
echo "BUG: Etag: '${rep_obj2_etag}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_etag}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj2_size}" != "${src_obj2_size}" ]; then
|
||||
echo "BUG: Size: '${rep_obj2_size}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_size}'"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Check content of replicated SSEC objects
|
||||
./mc cat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||
./mc cat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||
|
||||
# Check the MD5 checksums of encrypted objects from source and target
|
||||
if [ "${src_obj1_md5}" != "${rep_obj1_md5}" ]; then
|
||||
echo "BUG: MD5 checksum of object 'minio2/test-bucket/encrypted' doesn't match with source. Expected: '${src_obj1_md5}', Found: '${rep_obj1_md5}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${src_obj2_md5}" != "${rep_obj2_md5}" ]; then
|
||||
echo "BUG: MD5 checksum of object 'minio2/test-bucket/defpartsize' doesn't match with source. Expected: '${src_obj2_md5}', Found: '${rep_obj2_md5}'"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
cleanup
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck disable=SC2120
|
||||
exit_1() {
|
||||
cleanup
|
||||
|
||||
echo "minio1 ============"
|
||||
cat /tmp/minio1_1.log
|
||||
echo "minio2 ============"
|
||||
cat /tmp/minio2_1.log
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo -n "Cleaning up instances of MinIO ..."
|
||||
pkill minio || sudo pkill minio
|
||||
pkill -9 minio || sudo pkill -9 minio
|
||||
rm -rf /tmp/minio{1,2}
|
||||
echo "done"
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
export MINIO_CI_CD=1
|
||||
export MINIO_BROWSER=off
|
||||
export MINIO_ROOT_USER="minio"
|
||||
export MINIO_ROOT_PASSWORD="minio123"
|
||||
|
||||
# Create certificates for TLS enabled MinIO
|
||||
echo -n "Setup certs for MinIO instances ..."
|
||||
wget -O certgen https://github.com/minio/certgen/releases/latest/download/certgen-linux-amd64 && chmod +x certgen
|
||||
./certgen --host localhost
|
||||
mkdir -p ~/.minio/certs
|
||||
mv public.crt ~/.minio/certs || sudo mv public.crt ~/.minio/certs
|
||||
mv private.key ~/.minio/certs || sudo mv private.key ~/.minio/certs
|
||||
echo "done"
|
||||
|
||||
# Start MinIO instances
|
||||
echo -n "Starting MinIO instances ..."
|
||||
minio server --address ":9001" --console-address ":10000" /tmp/minio1/{1...4}/disk{1...4} /tmp/minio1/{5...8}/disk{1...4} >/tmp/minio1_1.log 2>&1 &
|
||||
minio server --address ":9002" --console-address ":11000" /tmp/minio2/{1...4}/disk{1...4} /tmp/minio2/{5...8}/disk{1...4} >/tmp/minio2_1.log 2>&1 &
|
||||
echo "done"
|
||||
|
||||
if [ ! -f ./mc ]; then
|
||||
echo -n "Downloading MinIO client ..."
|
||||
wget -O mc https://dl.min.io/client/mc/release/linux-amd64/mc &&
|
||||
chmod +x mc
|
||||
echo "done"
|
||||
fi
|
||||
|
||||
sleep 10
|
||||
|
||||
export MC_HOST_minio1=https://minio:minio123@localhost:9001
|
||||
export MC_HOST_minio2=https://minio:minio123@localhost:9002
|
||||
|
||||
# Prepare data for tests
|
||||
echo -n "Preparing test data ..."
|
||||
mkdir -p /tmp/data
|
||||
echo "Hello world" >/tmp/data/plainfile
|
||||
echo "Hello from encrypted world" >/tmp/data/encrypted
|
||||
touch /tmp/data/defpartsize
|
||||
shred -s 500M /tmp/data/defpartsize
|
||||
touch /tmp/data/custpartsize
|
||||
shred -s 500M /tmp/data/custpartsize
|
||||
echo "done"
|
||||
|
||||
# Add replication site
|
||||
./mc admin replicate add minio1 minio2 --insecure
|
||||
# sleep for replication to complete
|
||||
sleep 30
|
||||
|
||||
# Create bucket in source cluster
|
||||
echo "Create bucket in source MinIO instance"
|
||||
./mc mb minio1/test-bucket --insecure
|
||||
|
||||
# Load objects to source site
|
||||
echo "Loading objects to source MinIO instance"
|
||||
./mc cp /tmp/data/plainfile minio1/test-bucket --insecure
|
||||
./mc cp /tmp/data/encrypted minio1/test-bucket --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||
./mc cp /tmp/data/defpartsize minio1/test-bucket --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure
|
||||
./mc put /tmp/data/custpartsize minio1/test-bucket --encrypt-key "minio1/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --part-size 50MiB
|
||||
sleep 120
|
||||
|
||||
# List the objects from source site
|
||||
echo "Objects from source instance"
|
||||
./mc ls minio1/test-bucket --insecure
|
||||
count1=$(./mc ls minio1/test-bucket/plainfile --insecure | wc -l)
|
||||
if [ "${count1}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/plainfile not found"
|
||||
exit_1
|
||||
fi
|
||||
count2=$(./mc ls minio1/test-bucket/encrypted --insecure | wc -l)
|
||||
if [ "${count2}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/encrypted not found"
|
||||
exit_1
|
||||
fi
|
||||
count3=$(./mc ls minio1/test-bucket/defpartsize --insecure | wc -l)
|
||||
if [ "${count3}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/defpartsize not found"
|
||||
exit_1
|
||||
fi
|
||||
count4=$(./mc ls minio1/test-bucket/custpartsize --insecure | wc -l)
|
||||
if [ "${count4}" -ne 1 ]; then
|
||||
echo "BUG: object minio1/test-bucket/custpartsize not found"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# List the objects from replicated site
|
||||
echo "Objects from replicated instance"
|
||||
./mc ls minio2/test-bucket --insecure
|
||||
repcount1=$(./mc ls minio2/test-bucket/plainfile --insecure | wc -l)
|
||||
if [ "${repcount1}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/plainfile not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount2=$(./mc ls minio2/test-bucket/encrypted --insecure | wc -l)
|
||||
if [ "${repcount2}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/encrypted not replicated"
|
||||
exit_1
|
||||
fi
|
||||
repcount3=$(./mc ls minio2/test-bucket/defpartsize --insecure | wc -l)
|
||||
if [ "${repcount3}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/defpartsize not replicated"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
repcount4=$(./mc ls minio2/test-bucket/custpartsize --insecure | wc -l)
|
||||
if [ "${repcount4}" -ne 1 ]; then
|
||||
echo "BUG: object test-bucket/custpartsize not replicated"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Stat the SSEC objects from source site
|
||||
echo "Stat minio1/test-bucket/encrypted"
|
||||
./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out1=$(./mc stat minio1/test-bucket/encrypted --encrypt-key "minio1/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
src_obj1_etag=$(echo "${stat_out1}" | jq '.etag')
|
||||
src_obj1_size=$(echo "${stat_out1}" | jq '.size')
|
||||
src_obj1_md5=$(echo "${stat_out1}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
echo "Stat minio1/test-bucket/defpartsize"
|
||||
./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out2=$(./mc stat minio1/test-bucket/defpartsize --encrypt-key "minio1/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
src_obj2_etag=$(echo "${stat_out2}" | jq '.etag')
|
||||
src_obj2_size=$(echo "${stat_out2}" | jq '.size')
|
||||
src_obj2_md5=$(echo "${stat_out2}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
echo "Stat minio1/test-bucket/custpartsize"
|
||||
./mc stat minio1/test-bucket/custpartsize --encrypt-key "minio1/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out3=$(./mc stat minio1/test-bucket/custpartsize --encrypt-key "minio1/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
src_obj3_etag=$(echo "${stat_out3}" | jq '.etag')
|
||||
src_obj3_size=$(echo "${stat_out3}" | jq '.size')
|
||||
src_obj3_md5=$(echo "${stat_out3}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
|
||||
# Stat the SSEC objects from replicated site
|
||||
echo "Stat minio2/test-bucket/encrypted"
|
||||
./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out1_rep=$(./mc stat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
rep_obj1_etag=$(echo "${stat_out1_rep}" | jq '.etag')
|
||||
rep_obj1_size=$(echo "${stat_out1_rep}" | jq '.size')
|
||||
rep_obj1_md5=$(echo "${stat_out1_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
echo "Stat minio2/test-bucket/defpartsize"
|
||||
./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out2_rep=$(./mc stat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
rep_obj2_etag=$(echo "${stat_out2_rep}" | jq '.etag')
|
||||
rep_obj2_size=$(echo "${stat_out2_rep}" | jq '.size')
|
||||
rep_obj2_md5=$(echo "${stat_out2_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
echo "Stat minio2/test-bucket/custpartsize"
|
||||
./mc stat minio2/test-bucket/custpartsize --encrypt-key "minio2/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json
|
||||
stat_out3_rep=$(./mc stat minio2/test-bucket/custpartsize --encrypt-key "minio2/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure --json)
|
||||
rep_obj3_etag=$(echo "${stat_out3_rep}" | jq '.etag')
|
||||
rep_obj3_size=$(echo "${stat_out3_rep}" | jq '.size')
|
||||
rep_obj3_md5=$(echo "${stat_out3_rep}" | jq '.metadata."X-Amz-Server-Side-Encryption-Customer-Key-Md5"')
|
||||
|
||||
# Check the etag and size of replicated SSEC objects
|
||||
if [ "${rep_obj1_etag}" != "${src_obj1_etag}" ]; then
|
||||
echo "BUG: Etag: '${rep_obj1_etag}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_etag}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj1_size}" != "${src_obj1_size}" ]; then
|
||||
echo "BUG: Size: '${rep_obj1_size}' of replicated object: 'minio2/test-bucket/encrypted' doesn't match with source value: '${src_obj1_size}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj2_etag}" != "${src_obj2_etag}" ]; then
|
||||
echo "BUG: Etag: '${rep_obj2_etag}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_etag}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj2_size}" != "${src_obj2_size}" ]; then
|
||||
echo "BUG: Size: '${rep_obj2_size}' of replicated object: 'minio2/test-bucket/defpartsize' doesn't match with source value: '${src_obj2_size}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj3_etag}" != "${src_obj3_etag}" ]; then
|
||||
echo "BUG: Etag: '${rep_obj3_etag}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_etag}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${rep_obj3_size}" != "${src_obj3_size}" ]; then
|
||||
echo "BUG: Size: '${rep_obj3_size}' of replicated object: 'minio2/test-bucket/custpartsize' doesn't match with source value: '${src_obj3_size}'"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
# Check content of replicated SSEC objects
|
||||
./mc cat minio2/test-bucket/encrypted --encrypt-key "minio2/test-bucket/encrypted=iliketobecrazybutnotsomuchreally" --insecure
|
||||
./mc cat minio2/test-bucket/defpartsize --encrypt-key "minio2/test-bucket/defpartsize=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||
./mc cat minio2/test-bucket/custpartsize --encrypt-key "minio2/test-bucket/custpartsize=iliketobecrazybutnotsomuchreally" --insecure >/dev/null || exit_1
|
||||
|
||||
# Check the MD5 checksums of encrypted objects from source and target
|
||||
if [ "${src_obj1_md5}" != "${rep_obj1_md5}" ]; then
|
||||
echo "BUG: MD5 checksum of object 'minio2/test-bucket/encrypted' doesn't match with source. Expected: '${src_obj1_md5}', Found: '${rep_obj1_md5}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${src_obj2_md5}" != "${rep_obj2_md5}" ]; then
|
||||
echo "BUG: MD5 checksum of object 'minio2/test-bucket/defpartsize' doesn't match with source. Expected: '${src_obj2_md5}', Found: '${rep_obj2_md5}'"
|
||||
exit_1
|
||||
fi
|
||||
if [ "${src_obj3_md5}" != "${rep_obj3_md5}" ]; then
|
||||
echo "BUG: MD5 checksum of object 'minio2/test-bucket/custpartsize' doesn't match with source. Expected: '${src_obj3_md5}', Found: '${rep_obj3_md5}'"
|
||||
exit_1
|
||||
fi
|
||||
|
||||
cleanup
|
||||
@@ -54,7 +54,7 @@ require (
|
||||
github.com/minio/madmin-go/v3 v3.0.50
|
||||
github.com/minio/minio-go/v7 v7.0.69
|
||||
github.com/minio/mux v1.9.0
|
||||
github.com/minio/pkg/v2 v2.0.11
|
||||
github.com/minio/pkg/v2 v2.0.14
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/minio/sha256-simd v1.0.1
|
||||
github.com/minio/simdjson-go v0.4.5
|
||||
@@ -76,6 +76,7 @@ require (
|
||||
github.com/prometheus/client_model v0.6.0
|
||||
github.com/prometheus/common v0.50.0
|
||||
github.com/prometheus/procfs v0.13.0
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0
|
||||
github.com/rabbitmq/amqp091-go v1.9.0
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||
github.com/rs/cors v1.10.1
|
||||
|
||||
@@ -458,8 +458,8 @@ github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRP
|
||||
github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ=
|
||||
github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
|
||||
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
|
||||
github.com/minio/pkg/v2 v2.0.11 h1:nlFoFrnwBaNNtVmJJBJ+t1Nzp4jbPzyqdVuc7t9clcQ=
|
||||
github.com/minio/pkg/v2 v2.0.11/go.mod h1:zbVATXCinLCo+L/4vsPyqgiA4OYPXCJb+/E4KfE396A=
|
||||
github.com/minio/pkg/v2 v2.0.14 h1:tPhDYxgvv3LNqmfCSe2zsSXrcaIj4ANyL1VRGdEkahI=
|
||||
github.com/minio/pkg/v2 v2.0.14/go.mod h1:zbVATXCinLCo+L/4vsPyqgiA4OYPXCJb+/E4KfE396A=
|
||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
@@ -588,6 +588,8 @@ github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGK
|
||||
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
|
||||
github.com/prometheus/prom2json v1.3.3 h1:IYfSMiZ7sSOfliBoo89PcufjWO4eAR0gznGcETyaUgo=
|
||||
github.com/prometheus/prom2json v1.3.3/go.mod h1:Pv4yIPktEkK7btWsrUTWDDDrnpUrAELaOCj+oFwlgmc=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0 h1:EewKT7/LNac5SLiEblJeUu8z5eERHrmRLnMQL2d7qX4=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.1.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
|
||||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
|
||||
|
||||
@@ -93,7 +93,7 @@ echo {{ tpl .accessKey $global }} > $MINIO_ACCESSKEY_SECRETKEY_TMP
|
||||
{{- if .existingSecret }}
|
||||
cat /config/secrets-svc/{{ tpl .existingSecret $global }}/{{ tpl .existingSecretKey $global }} >> $MINIO_ACCESSKEY_SECRETKEY_TMP
|
||||
# Add a new line if it doesn't exist
|
||||
sed -i '$a\' $MINIO_ACCESSKEY_SECRETKEY_TMP
|
||||
echo >> $MINIO_ACCESSKEY_SECRETKEY_TMP
|
||||
{{ else }}
|
||||
echo {{ .secretKey }} >> $MINIO_ACCESSKEY_SECRETKEY_TMP
|
||||
{{- end }}
|
||||
|
||||
@@ -38,6 +38,7 @@ func (z *BucketBandwidthReport) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z BucketBandwidthReport) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 0
|
||||
_ = z
|
||||
err = en.Append(0x80)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -49,6 +50,7 @@ func (z BucketBandwidthReport) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
func (z BucketBandwidthReport) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 0
|
||||
_ = z
|
||||
o = append(o, 0x80)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -220,9 +220,6 @@ func (c Config) GetDestination() Destination {
|
||||
|
||||
// Replicate returns true if the object should be replicated.
|
||||
func (c Config) Replicate(obj ObjectOpts) bool {
|
||||
if obj.SSEC {
|
||||
return false
|
||||
}
|
||||
for _, rule := range c.FilterActionableRules(obj) {
|
||||
if rule.Status == Disabled {
|
||||
continue
|
||||
|
||||
@@ -250,12 +250,12 @@ func TestReplicate(t *testing.T) {
|
||||
{ObjectOpts{Name: "c1test"}, cfgs[0], true}, // 2. valid ObjectOpts passing empty Filter
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid"}, cfgs[0], true}, // 3. valid ObjectOpts passing empty Filter
|
||||
|
||||
{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true}, // 5. permanent delete of version, matches DeleteReplication status - valid case
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 6. permanent delete of version, matches DeleteReplication status
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, // 7. permanent delete of version, disqualified by SSE-C
|
||||
{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], false}, // 8. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C
|
||||
{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], false}, // 9. replication of SSE-C encrypted object, disqualified
|
||||
{ObjectOpts{Name: "c1test", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 4. DeleteMarker version replication valid case - matches DeleteMarkerReplication status
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid", OpType: DeleteReplicationType}, cfgs[0], true}, // 5. permanent delete of version, matches DeleteReplication status - valid case
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 6. permanent delete of version, matches DeleteReplication status
|
||||
{ObjectOpts{Name: "c1test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 7. permanent delete of version
|
||||
{ObjectOpts{Name: "c1test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[0], true}, // 8. setting DeleteMarker on SSE-C encrypted object
|
||||
{ObjectOpts{Name: "c1test", SSEC: true}, cfgs[0], true}, // 9. replication of SSE-C encrypted object
|
||||
|
||||
// using config 2 - no filters, only replication of object, metadata enabled
|
||||
{ObjectOpts{Name: "c2test"}, cfgs[1], true}, // 10. valid ObjectOpts passing empty Filter
|
||||
@@ -264,7 +264,7 @@ func TestReplicate(t *testing.T) {
|
||||
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 13. permanent delete of DeleteMarker version, disallowed by DeleteReplication status
|
||||
{ObjectOpts{Name: "c2test", VersionID: "vid", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 14. permanent delete of version, disqualified by SSE-C & DeleteReplication status
|
||||
{ObjectOpts{Name: "c2test", DeleteMarker: true, SSEC: true, OpType: DeleteReplicationType}, cfgs[1], false}, // 15. setting DeleteMarker on SSE-C encrypted object, disqualified by SSE-C & DeleteMarkerReplication status
|
||||
{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], false}, // 16. replication of SSE-C encrypted object, disqualified by default
|
||||
{ObjectOpts{Name: "c2test", SSEC: true}, cfgs[1], true}, // 16. replication of SSE-C encrypted object
|
||||
// using config 2 - has more than one rule with overlapping prefixes
|
||||
{ObjectOpts{Name: "xy/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 17. matches rule 1 for replication of content/metadata
|
||||
{ObjectOpts{Name: "xyz/c3test", UserTags: "k1=v1"}, cfgs[2], true}, // 18. matches rule 1 for replication of content/metadata
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user