mirror of
https://github.com/pgsty/minio.git
synced 2026-10-19 07:56:09 +03:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7743d952dc | |||
| 944f3c1477 | |||
| 1d3bd02089 | |||
| 38de8e6936 | |||
| 6347fb6636 | |||
| 32e668eb94 | |||
| c51f9ef940 | |||
| 1a91edecae | |||
| c88308cf0e | |||
| 88837fb753 | |||
| d0283ff354 | |||
| f449a7ae2c | |||
| a113b2c394 | |||
| 74851834c0 | |||
| e377bb949a | |||
| c905d3fe21 | |||
| b6e9d235fe | |||
| 6968f7237a | |||
| 4a6c97463f | |||
| 6c912ac960 | |||
| 708cebe7f0 | |||
| 152023e837 | |||
| 0f16e19239 | |||
| 2c38e44e48 | |||
| 82739574b5 | |||
| f78d677ab6 | |||
| e39e2306d6 | |||
| 52229a21cb | |||
| 961f7dea82 | |||
| feeeef71f1 | |||
| 65c4d550cb | |||
| f9b4a8d6e8 | |||
| e11d851aee | |||
| ac81f0248c | |||
| 83bf15a703 | |||
| cc960adbee | |||
| c66c5828ea | |||
| 19387cafab | |||
| 7c0673279b | |||
| 7ce0d71a96 | |||
| dd2542e96c | |||
| 21d60eab7c | |||
| 4d2320ba8b | |||
| a4a74e9844 | |||
| 9588978028 | |||
| 479940b7d0 | |||
| 8cd967803c | |||
| a0e1163fb6 | |||
| 8ccd1ee34a |
@@ -0,0 +1,8 @@
|
||||
|
||||
[codespell]
|
||||
# certs_test.go - has lots of ceritificates.
|
||||
skip = go.mod,go.sum,*.txt,LICENSE,*.zip,.git,*.pdf,*.svg,.codespellrc,CREDITS,certs_test.go
|
||||
check-hidden = true
|
||||
ignore-regex = \b(newfolder/afile|filterIn|HelpES)\b
|
||||
ignore-words-list = inout,bui,to,bu,te,ot,toi,ist,parms,flate
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
@@ -46,7 +46,7 @@ lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
||||
check: test
|
||||
test: verifiers build build-debugging ## builds minio, runs linters, tests
|
||||
@echo "Running unit tests"
|
||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue ./...
|
||||
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue ./...
|
||||
|
||||
test-root-disable: install-race
|
||||
@echo "Running minio root lockdown tests"
|
||||
@@ -116,7 +116,6 @@ verify-healing: ## verify healing and replacing disks with minio binary
|
||||
@echo "Verify healing build with race"
|
||||
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||
@(env bash $(PWD)/buildscripts/verify-healing.sh)
|
||||
@(env bash $(PWD)/buildscripts/unaligned-healing.sh)
|
||||
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
|
||||
|
||||
verify-healing-with-root-disks: ## verify healing root disks
|
||||
|
||||
@@ -1,177 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
#
|
||||
|
||||
set -E
|
||||
set -o pipefail
|
||||
set -x
|
||||
|
||||
if [ ! -x "$PWD/minio" ]; then
|
||||
echo "minio executable binary not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WORK_DIR="$PWD/.verify-$RANDOM"
|
||||
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
|
||||
MINIO_OLD=("$PWD/minio.RELEASE.2021-11-24T23-19-33Z" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
|
||||
|
||||
function download_old_release() {
|
||||
if [ ! -f minio.RELEASE.2021-11-24T23-19-33Z ]; then
|
||||
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2021-11-24T23-19-33Z
|
||||
chmod a+x minio.RELEASE.2021-11-24T23-19-33Z
|
||||
fi
|
||||
}
|
||||
|
||||
function start_minio_16drive() {
|
||||
start_port=$1
|
||||
|
||||
export MINIO_ROOT_USER=minio
|
||||
export MINIO_ROOT_PASSWORD=minio123
|
||||
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
|
||||
unset MINIO_KMS_AUTO_ENCRYPTION # do not auto-encrypt objects
|
||||
export _MINIO_SHARD_DISKTIME_DELTA="5s" # do not change this as its needed for tests
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
MC_BUILD_DIR="mc-$RANDOM"
|
||||
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
|
||||
echo "failed to download https://github.com/minio/mc"
|
||||
purge "${MC_BUILD_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc")
|
||||
|
||||
# remove mc source.
|
||||
purge "${MC_BUILD_DIR}"
|
||||
|
||||
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 30
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shred --iterations=1 --size=5241856 - 1>"${WORK_DIR}/unaligned" 2>/dev/null
|
||||
"${WORK_DIR}/mc" mb minio/healing-shard-bucket --quiet
|
||||
"${WORK_DIR}/mc" cp \
|
||||
"${WORK_DIR}/unaligned" \
|
||||
minio/healing-shard-bucket/unaligned \
|
||||
--disable-multipart --quiet
|
||||
|
||||
## "unaligned" object name gets consistently distributed
|
||||
## to disks in following distribution order
|
||||
##
|
||||
## NOTE: if you change the name make sure to change the
|
||||
## distribution order present here
|
||||
##
|
||||
## [15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
|
||||
|
||||
## make sure to remove the "last" data shard
|
||||
rm -rf "${WORK_DIR}/xl14/healing-shard-bucket/unaligned"
|
||||
sleep 10
|
||||
## Heal the shard
|
||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||
## then remove any other data shard let's pick first disk
|
||||
## - 1st data shard.
|
||||
rm -rf "${WORK_DIR}/xl3/healing-shard-bucket/unaligned"
|
||||
sleep 10
|
||||
|
||||
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
|
||||
if ! s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep CORRUPTED; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
|
||||
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
|
||||
pid=$!
|
||||
disown $pid
|
||||
sleep 30
|
||||
|
||||
if ! ps -p ${pid} 1>&2 >/dev/null; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(
|
||||
cd inspects
|
||||
"${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**
|
||||
)
|
||||
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${WORK_DIR}/mc" admin heal --quiet --recursive minio/healing-shard-bucket
|
||||
|
||||
if ! s3-check-md5 \
|
||||
-debug \
|
||||
-access-key minio \
|
||||
-secret-key minio123 \
|
||||
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then
|
||||
echo "server1 log:"
|
||||
cat "${WORK_DIR}/server1.log"
|
||||
echo "FAILED"
|
||||
mkdir -p inspects
|
||||
(
|
||||
cd inspects
|
||||
"${WORK_DIR}/mc" support inspect minio/healing-shard-bucket/unaligned/**
|
||||
)
|
||||
|
||||
"${WORK_DIR}/mc" mb play/inspects
|
||||
"${WORK_DIR}/mc" mirror inspects play/inspects
|
||||
|
||||
purge "$WORK_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
pkill minio
|
||||
sleep 3
|
||||
}
|
||||
|
||||
function main() {
|
||||
download_old_release
|
||||
|
||||
start_port=$(shuf -i 10000-65000 -n 1)
|
||||
|
||||
start_minio_16drive ${start_port}
|
||||
}
|
||||
|
||||
function purge() {
|
||||
rm -rf "$1"
|
||||
}
|
||||
|
||||
(main "$@")
|
||||
rv=$?
|
||||
purge "$WORK_DIR"
|
||||
exit "$rv"
|
||||
@@ -680,24 +680,17 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
if _, ok := bucketMap[bucket]; !ok {
|
||||
opts := MakeBucketOptions{
|
||||
LockEnabled: config.Enabled(),
|
||||
ForceCreate: true, // ignore if it already exists
|
||||
}
|
||||
err = objectAPI.MakeBucket(ctx, bucket, opts)
|
||||
if err != nil {
|
||||
if _, ok := err.(BucketExists); !ok {
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
}
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
}
|
||||
v := newBucketMetadata(bucket)
|
||||
bucketMap[bucket] = &v
|
||||
}
|
||||
|
||||
// Deny object locking configuration settings on existing buckets without object lock enabled.
|
||||
if _, _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
}
|
||||
|
||||
bucketMap[bucket].ObjectLockConfigXML = configData
|
||||
bucketMap[bucket].ObjectLockConfigUpdatedAt = updatedAt
|
||||
rpt.SetStatus(bucket, fileName, nil)
|
||||
@@ -724,11 +717,11 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
continue
|
||||
}
|
||||
if _, ok := bucketMap[bucket]; !ok {
|
||||
if err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{}); err != nil {
|
||||
if _, ok := err.(BucketExists); !ok {
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
}
|
||||
if err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{
|
||||
ForceCreate: true, // ignore if it already exists
|
||||
}); err != nil {
|
||||
rpt.SetStatus(bucket, fileName, err)
|
||||
continue
|
||||
}
|
||||
v := newBucketMetadata(bucket)
|
||||
bucketMap[bucket] = &v
|
||||
@@ -776,12 +769,12 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
|
||||
|
||||
// create bucket if it does not exist yet.
|
||||
if _, ok := bucketMap[bucket]; !ok {
|
||||
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{})
|
||||
err = objectAPI.MakeBucket(ctx, bucket, MakeBucketOptions{
|
||||
ForceCreate: true, // ignore if it already exists
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := err.(BucketExists); !ok {
|
||||
rpt.SetStatus(bucket, "", err)
|
||||
continue
|
||||
}
|
||||
rpt.SetStatus(bucket, "", err)
|
||||
continue
|
||||
}
|
||||
v := newBucketMetadata(bucket)
|
||||
bucketMap[bucket] = &v
|
||||
|
||||
+345
-27
@@ -79,6 +79,213 @@ const (
|
||||
mgmtForceStop = "forceStop"
|
||||
)
|
||||
|
||||
// ServerUpdateV2Handler - POST /minio/admin/v3/update?updateURL={updateURL}&type=2
|
||||
// ----------
|
||||
// updates all minio servers and restarts them gracefully.
|
||||
func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServerUpdateAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if globalInplaceUpdateDisabled || currentReleaseTime.IsZero() {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
updateURL := vars["updateURL"]
|
||||
dryRun := r.Form.Get("dry-run") == "true"
|
||||
|
||||
mode := getMinioMode()
|
||||
if updateURL == "" {
|
||||
updateURL = minioReleaseInfoURL
|
||||
if runtime.GOOS == globalWindowsOSName {
|
||||
updateURL = minioReleaseWindowsInfoURL
|
||||
}
|
||||
}
|
||||
|
||||
u, err := url.Parse(updateURL)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := downloadReleaseURL(u, updateTimeout, mode)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
sha256Sum, lrTime, releaseInfo, err := parseReleaseData(content)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
u.Path = path.Dir(u.Path) + SlashSeparator + releaseInfo
|
||||
// Download Binary Once
|
||||
binC, bin, err := downloadBinary(u, mode)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("server update failed with %w", err))
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
updateStatus := madmin.ServerUpdateStatusV2{DryRun: dryRun}
|
||||
peerResults := make(map[string]madmin.ServerPeerUpdateStatus)
|
||||
|
||||
local := globalLocalNodeName
|
||||
if local == "" {
|
||||
local = "127.0.0.1"
|
||||
}
|
||||
|
||||
failedClients := make(map[int]struct{})
|
||||
|
||||
if globalIsDistErasure {
|
||||
// Push binary to other servers
|
||||
for idx, nerr := range globalNotificationSys.VerifyBinary(ctx, u, sha256Sum, releaseInfo, binC) {
|
||||
if nerr.Err != nil {
|
||||
peerResults[nerr.Host.String()] = madmin.ServerPeerUpdateStatus{
|
||||
Host: nerr.Host.String(),
|
||||
Err: nerr.Err.Error(),
|
||||
CurrentVersion: Version,
|
||||
}
|
||||
failedClients[idx] = struct{}{}
|
||||
} else {
|
||||
peerResults[nerr.Host.String()] = madmin.ServerPeerUpdateStatus{
|
||||
Host: nerr.Host.String(),
|
||||
CurrentVersion: Version,
|
||||
UpdatedVersion: lrTime.Format(minioReleaseTagTimeLayout),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if lrTime.Sub(currentReleaseTime) > 0 {
|
||||
if err = verifyBinary(u, sha256Sum, releaseInfo, mode, bytes.NewReader(bin)); err != nil {
|
||||
peerResults[local] = madmin.ServerPeerUpdateStatus{
|
||||
Host: local,
|
||||
Err: err.Error(),
|
||||
CurrentVersion: Version,
|
||||
}
|
||||
} else {
|
||||
peerResults[local] = madmin.ServerPeerUpdateStatus{
|
||||
Host: local,
|
||||
CurrentVersion: Version,
|
||||
UpdatedVersion: lrTime.Format(minioReleaseTagTimeLayout),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
peerResults[local] = madmin.ServerPeerUpdateStatus{
|
||||
Host: local,
|
||||
Err: fmt.Sprintf("server is already running the latest version: %s", Version),
|
||||
CurrentVersion: Version,
|
||||
}
|
||||
}
|
||||
|
||||
if !dryRun {
|
||||
if globalIsDistErasure {
|
||||
ng := WithNPeers(len(globalNotificationSys.peerClients))
|
||||
for idx, client := range globalNotificationSys.peerClients {
|
||||
_, ok := failedClients[idx]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
client := client
|
||||
ng.Go(ctx, func() error {
|
||||
return client.CommitBinary(ctx)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
|
||||
for _, nerr := range ng.Wait() {
|
||||
if nerr.Err != nil {
|
||||
prs, ok := peerResults[nerr.Host.String()]
|
||||
if ok {
|
||||
prs.Err = nerr.Err.Error()
|
||||
peerResults[nerr.Host.String()] = prs
|
||||
} else {
|
||||
peerResults[nerr.Host.String()] = madmin.ServerPeerUpdateStatus{
|
||||
Host: nerr.Host.String(),
|
||||
Err: nerr.Err.Error(),
|
||||
CurrentVersion: Version,
|
||||
UpdatedVersion: lrTime.Format(minioReleaseTagTimeLayout),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
prs := peerResults[local]
|
||||
if prs.Err == "" {
|
||||
if err = commitBinary(); err != nil {
|
||||
prs.Err = err.Error()
|
||||
}
|
||||
peerResults[local] = prs
|
||||
}
|
||||
}
|
||||
|
||||
prs, ok := peerResults[local]
|
||||
if ok {
|
||||
prs.WaitingDrives = waitingDrivesNode()
|
||||
peerResults[local] = prs
|
||||
}
|
||||
|
||||
if globalIsDistErasure {
|
||||
// Notify all other MinIO peers signal service.
|
||||
ng := WithNPeers(len(globalNotificationSys.peerClients))
|
||||
for idx, client := range globalNotificationSys.peerClients {
|
||||
_, ok := failedClients[idx]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
client := client
|
||||
ng.Go(ctx, func() error {
|
||||
prs, ok := peerResults[client.String()]
|
||||
if ok && prs.CurrentVersion != prs.UpdatedVersion && prs.UpdatedVersion != "" {
|
||||
return client.SignalService(serviceRestart, "", dryRun)
|
||||
}
|
||||
return nil
|
||||
}, idx, *client.host)
|
||||
}
|
||||
|
||||
for _, nerr := range ng.Wait() {
|
||||
if nerr.Err != nil {
|
||||
waitingDrives := map[string]madmin.DiskMetrics{}
|
||||
jerr := json.Unmarshal([]byte(nerr.Err.Error()), &waitingDrives)
|
||||
if jerr == nil {
|
||||
prs, ok := peerResults[nerr.Host.String()]
|
||||
if ok {
|
||||
prs.WaitingDrives = waitingDrives
|
||||
peerResults[nerr.Host.String()] = prs
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, pr := range peerResults {
|
||||
updateStatus.Results = append(updateStatus.Results, pr)
|
||||
}
|
||||
|
||||
// Marshal API response
|
||||
jsonBytes, err := json.Marshal(updateStatus)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, jsonBytes)
|
||||
|
||||
if !dryRun {
|
||||
if lrTime.Sub(currentReleaseTime) > 0 {
|
||||
globalServiceSignalCh <- serviceRestart
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ServerUpdateHandler - POST /minio/admin/v3/update?updateURL={updateURL}
|
||||
// ----------
|
||||
// updates all minio servers and restarts them gracefully.
|
||||
@@ -90,7 +297,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
if globalInplaceUpdateDisabled {
|
||||
if globalInplaceUpdateDisabled || currentReleaseTime.IsZero() {
|
||||
// if MINIO_UPDATE=off - inplace update is disabled, mostly in containers.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMethodNotAllowed), r.URL)
|
||||
return
|
||||
@@ -124,14 +331,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
u.Path = path.Dir(u.Path) + SlashSeparator + releaseInfo
|
||||
crTime, err := GetCurrentReleaseTime()
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if lrTime.Sub(crTime) <= 0 {
|
||||
if lrTime.Sub(currentReleaseTime) <= 0 {
|
||||
updateStatus := madmin.ServerUpdateStatus{
|
||||
CurrentVersion: Version,
|
||||
UpdatedVersion: Version,
|
||||
@@ -148,8 +348,10 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
u.Path = path.Dir(u.Path) + SlashSeparator + releaseInfo
|
||||
|
||||
// Download Binary Once
|
||||
reader, err := downloadBinary(u, mode)
|
||||
binC, bin, err := downloadBinary(u, mode)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("server update failed with %w", err))
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
@@ -157,7 +359,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
// Push binary to other servers
|
||||
for _, nerr := range globalNotificationSys.VerifyBinary(ctx, u, sha256Sum, releaseInfo, reader) {
|
||||
for _, nerr := range globalNotificationSys.VerifyBinary(ctx, u, sha256Sum, releaseInfo, binC) {
|
||||
if nerr.Err != nil {
|
||||
err := AdminError{
|
||||
Code: AdminUpdateApplyFailure,
|
||||
@@ -171,7 +373,7 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
err = verifyBinary(u, sha256Sum, releaseInfo, mode, reader)
|
||||
err = verifyBinary(u, sha256Sum, releaseInfo, mode, bytes.NewReader(bin))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("server update failed with %w", err))
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
@@ -227,8 +429,6 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
// ServiceHandler - POST /minio/admin/v3/service?action={action}
|
||||
// ----------
|
||||
// Supports following actions:
|
||||
// - restart (restarts all the MinIO instances in a setup)
|
||||
// - stop (stops all the MinIO instances in a setup)
|
||||
// - freeze (freezes all incoming S3 API calls)
|
||||
// - unfreeze (unfreezes previously frozen S3 API calls)
|
||||
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -238,7 +438,83 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
||||
action := vars["action"]
|
||||
|
||||
var serviceSig serviceSignal
|
||||
switch madmin.ServiceAction(action) {
|
||||
switch act := madmin.ServiceAction(action); act {
|
||||
case madmin.ServiceActionFreeze:
|
||||
serviceSig = serviceFreeze
|
||||
case madmin.ServiceActionUnfreeze:
|
||||
serviceSig = serviceUnFreeze
|
||||
default:
|
||||
process := act == madmin.ServiceActionRestart || act == madmin.ServiceActionStop
|
||||
if process {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = "process actions are not supported via this API anymore, please upgrade 'mc' or madmin-go to use ServiceV2() API"
|
||||
writeErrorResponseJSON(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Notify all other MinIO peers signal service.
|
||||
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
|
||||
if nerr.Err != nil {
|
||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||
logger.LogIf(ctx, nerr.Err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reply to the client before restarting, stopping MinIO server.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
|
||||
switch serviceSig {
|
||||
case serviceFreeze:
|
||||
freezeServices()
|
||||
case serviceUnFreeze:
|
||||
unfreezeServices()
|
||||
}
|
||||
}
|
||||
|
||||
type servicePeerResult struct {
|
||||
Host string `json:"host"`
|
||||
Err string `json:"err,omitempty"`
|
||||
WaitingDrives map[string]madmin.DiskMetrics `json:"waitingDrives,omitempty"`
|
||||
}
|
||||
|
||||
type serviceResult struct {
|
||||
Action madmin.ServiceAction `json:"action"`
|
||||
DryRun bool `json:"dryRun"`
|
||||
Results []servicePeerResult `json:"results,omitempty"`
|
||||
}
|
||||
|
||||
// ServiceV2Handler - POST /minio/admin/v3/service?action={action}&type=2
|
||||
// ----------
|
||||
// Supports following actions:
|
||||
// - restart (restarts all the MinIO instances in a setup)
|
||||
// - stop (stops all the MinIO instances in a setup)
|
||||
// - freeze (freezes all incoming S3 API calls)
|
||||
// - unfreeze (unfreezes previously frozen S3 API calls)
|
||||
//
|
||||
// This newer API now returns back status per remote peer and local regarding
|
||||
// if a "restart/stop" was successful or not. Service signal now supports
|
||||
// a dry-run that helps skip the nodes that may have hung drives. By default
|
||||
// restart/stop will ignore the servers that are hung on drives. You can use
|
||||
// 'force' param to force restart even with hung drives if needed.
|
||||
func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
vars := mux.Vars(r)
|
||||
action := vars["action"]
|
||||
dryRun := r.Form.Get("dry-run") == "true"
|
||||
|
||||
var serviceSig serviceSignal
|
||||
act := madmin.ServiceAction(action)
|
||||
switch act {
|
||||
case madmin.ServiceActionRestart:
|
||||
serviceSig = serviceRestart
|
||||
case madmin.ServiceActionStop:
|
||||
@@ -267,15 +543,55 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// Notify all other MinIO peers signal service.
|
||||
for _, nerr := range globalNotificationSys.SignalService(serviceSig) {
|
||||
if nerr.Err != nil {
|
||||
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||
logger.LogIf(ctx, nerr.Err)
|
||||
srvResult := serviceResult{Action: act, Results: []servicePeerResult{}}
|
||||
|
||||
process := act == madmin.ServiceActionRestart || act == madmin.ServiceActionStop
|
||||
if process {
|
||||
localhost := globalLocalNodeName
|
||||
if globalLocalNodeName == "" {
|
||||
localhost = "127.0.0.1"
|
||||
}
|
||||
waitingDrives := waitingDrivesNode()
|
||||
srvResult.Results = append(srvResult.Results, servicePeerResult{
|
||||
Host: localhost,
|
||||
WaitingDrives: waitingDrives,
|
||||
})
|
||||
}
|
||||
|
||||
if globalIsDistErasure {
|
||||
for _, nerr := range globalNotificationSys.SignalServiceV2(serviceSig, dryRun) {
|
||||
if nerr.Err != nil && process {
|
||||
waitingDrives := map[string]madmin.DiskMetrics{}
|
||||
jerr := json.Unmarshal([]byte(nerr.Err.Error()), &waitingDrives)
|
||||
if jerr == nil {
|
||||
srvResult.Results = append(srvResult.Results, servicePeerResult{
|
||||
Host: nerr.Host.String(),
|
||||
WaitingDrives: waitingDrives,
|
||||
})
|
||||
continue
|
||||
}
|
||||
}
|
||||
errStr := ""
|
||||
if nerr.Err != nil {
|
||||
errStr = nerr.Err.Error()
|
||||
}
|
||||
srvResult.Results = append(srvResult.Results, servicePeerResult{
|
||||
Host: nerr.Host.String(),
|
||||
Err: errStr,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
srvResult.DryRun = dryRun
|
||||
|
||||
buf, err := json.Marshal(srvResult)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// Reply to the client before restarting, stopping MinIO server.
|
||||
writeSuccessResponseHeadersOnly(w)
|
||||
writeSuccessResponseJSON(w, buf)
|
||||
|
||||
switch serviceSig {
|
||||
case serviceFreeze:
|
||||
@@ -283,7 +599,9 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
||||
case serviceUnFreeze:
|
||||
unfreezeServices()
|
||||
case serviceRestart, serviceStop:
|
||||
globalServiceSignalCh <- serviceSig
|
||||
if !dryRun {
|
||||
globalServiceSignalCh <- serviceSig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2339,7 +2657,7 @@ func fetchHealthInfo(healthCtx context.Context, objectAPI ObjectLayer, query *ur
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(healthInfoCh)
|
||||
defer xioutil.SafeClose(healthInfoCh)
|
||||
|
||||
partialWrite(healthInfo) // Write first message with only version and deployment id populated
|
||||
getAndWritePlatformInfo()
|
||||
@@ -2728,11 +3046,11 @@ func getClusterMetaInfo(ctx context.Context) []byte {
|
||||
resultCh := make(chan madmin.ClusterRegistrationInfo)
|
||||
|
||||
go func() {
|
||||
defer close(resultCh)
|
||||
defer xioutil.SafeClose(resultCh)
|
||||
|
||||
ci := madmin.ClusterRegistrationInfo{}
|
||||
ci.Info.NoOfServerPools = len(globalEndpoints)
|
||||
ci.Info.NoOfServers = len(globalEndpoints.Hostnames())
|
||||
ci.Info.NoOfServers = totalNodeCount()
|
||||
ci.Info.MinioVersion = Version
|
||||
|
||||
si := objectAPI.StorageInfo(ctx, true)
|
||||
@@ -2972,7 +3290,7 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
// save the format.json as part of inspect by default
|
||||
if volume != minioMetaBucket && file != formatConfigFile {
|
||||
if !(volume == minioMetaBucket && file == formatConfigFile) {
|
||||
err = o.GetRawData(ctx, minioMetaBucket, formatConfigFile, rawDataFn)
|
||||
}
|
||||
if !errors.Is(err, errFileNotFound) {
|
||||
@@ -3054,7 +3372,7 @@ func createHostAnonymizerForFSMode() map[string]string {
|
||||
// anonymizeHost - Add entries related to given endpoint in the host anonymizer map
|
||||
// The health report data can contain the hostname in various forms e.g. host, host:port,
|
||||
// host:port/drivepath, full url (http://host:port/drivepath)
|
||||
// The anonymizer map will have mappings for all these varients for efficiently replacing
|
||||
// The anonymizer map will have mappings for all these variants for efficiently replacing
|
||||
// any of these strings to the anonymized versions at the time of health report generation.
|
||||
func anonymizeHost(hostAnonymizer map[string]string, endpoint Endpoint, poolNum int, srvrNum int) {
|
||||
if len(endpoint.Host) == 0 {
|
||||
@@ -3102,7 +3420,7 @@ func anonymizeHost(hostAnonymizer map[string]string, endpoint Endpoint, poolNum
|
||||
}
|
||||
}
|
||||
|
||||
// createHostAnonymizer - Creats a map of various strings to corresponding anonymized names
|
||||
// createHostAnonymizer - Creates a map of various strings to corresponding anonymized names
|
||||
func createHostAnonymizer() map[string]string {
|
||||
if !globalIsDistErasure {
|
||||
return createHostAnonymizerForFSMode()
|
||||
|
||||
@@ -168,6 +168,7 @@ func testServiceSignalReceiver(cmd cmdType, t *testing.T) {
|
||||
func getServiceCmdRequest(cmd cmdType, cred auth.Credentials) (*http.Request, error) {
|
||||
queryVal := url.Values{}
|
||||
queryVal.Set("action", string(cmd.toServiceAction()))
|
||||
queryVal.Set("type", "2")
|
||||
resource := adminPathPrefix + adminAPIVersionPrefix + "/service?" + queryVal.Encode()
|
||||
req, err := newTestRequest(http.MethodPost, resource, 0, nil)
|
||||
if err != nil {
|
||||
@@ -220,12 +221,18 @@ func testServicesCmdHandler(cmd cmdType, t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
adminTestBed.router.ServeHTTP(rec, req)
|
||||
|
||||
resp, _ := io.ReadAll(rec.Body)
|
||||
if rec.Code != http.StatusOK {
|
||||
resp, _ := io.ReadAll(rec.Body)
|
||||
t.Errorf("Expected to receive %d status code but received %d. Body (%s)",
|
||||
http.StatusOK, rec.Code, string(resp))
|
||||
}
|
||||
|
||||
result := &serviceResult{}
|
||||
if err := json.Unmarshal(resp, result); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_ = result
|
||||
|
||||
// Wait until testServiceSignalReceiver() called in a goroutine quits.
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
@@ -800,7 +801,7 @@ func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
|
||||
func (h *healSequence) traverseAndHeal(objAPI ObjectLayer) {
|
||||
bucketsOnly := false // Heals buckets and objects also.
|
||||
h.traverseAndHealDoneCh <- h.healItems(objAPI, bucketsOnly)
|
||||
close(h.traverseAndHealDoneCh)
|
||||
xioutil.SafeClose(h.traverseAndHealDoneCh)
|
||||
}
|
||||
|
||||
// healMinioSysMeta - heals all files under a given meta prefix, returns a function
|
||||
|
||||
+9
-2
@@ -154,9 +154,16 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
}
|
||||
|
||||
for _, adminVersion := range adminVersions {
|
||||
// Restart and stop MinIO service.
|
||||
// Restart and stop MinIO service type=2
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(adminMiddleware(adminAPI.ServiceV2Handler, traceAllFlag)).Queries("action", "{action:.*}", "type", "2")
|
||||
|
||||
// Deprecated: Restart and stop MinIO service.
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(adminMiddleware(adminAPI.ServiceHandler, traceAllFlag)).Queries("action", "{action:.*}")
|
||||
// Update MinIO servers.
|
||||
|
||||
// Update all MinIO servers type=2
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(adminMiddleware(adminAPI.ServerUpdateV2Handler, traceAllFlag)).Queries("updateURL", "{updateURL:.*}", "type", "2")
|
||||
|
||||
// Deprecated: Update MinIO servers.
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(adminMiddleware(adminAPI.ServerUpdateHandler, traceAllFlag)).Queries("updateURL", "{updateURL:.*}")
|
||||
|
||||
// Info operations
|
||||
|
||||
+1
-1
@@ -959,7 +959,7 @@ var errorCodes = errorCodeMap{
|
||||
},
|
||||
ErrReplicationBandwidthLimitError: {
|
||||
Code: "XMinioAdminReplicationBandwidthLimitError",
|
||||
Description: "Bandwidth limit for remote target must be atleast 100MBps",
|
||||
Description: "Bandwidth limit for remote target must be at least 100MBps",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrReplicationNoExistingObjects: {
|
||||
|
||||
@@ -287,7 +287,7 @@ func mustNewSignedRequest(method string, urlStr string, contentLength int64, bod
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
cred := globalActiveCred
|
||||
if err := signRequestV4(req, cred.AccessKey, cred.SecretKey); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
t.Fatalf("Unable to initialized new signed http request %s", err)
|
||||
}
|
||||
return req
|
||||
}
|
||||
@@ -298,7 +298,7 @@ func mustNewSignedV2Request(method string, urlStr string, contentLength int64, b
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
cred := globalActiveCred
|
||||
if err := signRequestV2(req, cred.AccessKey, cred.SecretKey); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
t.Fatalf("Unable to initialized new signed http request %s", err)
|
||||
}
|
||||
return req
|
||||
}
|
||||
@@ -309,7 +309,7 @@ func mustNewPresignedV2Request(method string, urlStr string, contentLength int64
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
cred := globalActiveCred
|
||||
if err := preSignV2(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
t.Fatalf("Unable to initialized new signed http request %s", err)
|
||||
}
|
||||
return req
|
||||
}
|
||||
@@ -320,7 +320,7 @@ func mustNewPresignedRequest(method string, urlStr string, contentLength int64,
|
||||
req := mustNewRequest(method, urlStr, contentLength, body, t)
|
||||
cred := globalActiveCred
|
||||
if err := preSignV4(req, cred.AccessKey, cred.SecretKey, time.Now().Add(10*time.Minute).Unix()); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
t.Fatalf("Unable to initialized new signed http request %s", err)
|
||||
}
|
||||
return req
|
||||
}
|
||||
@@ -491,7 +491,7 @@ func TestValidateAdminSignature(t *testing.T) {
|
||||
for i, testCase := range testCases {
|
||||
req := mustNewRequest(http.MethodGet, "http://localhost:9000/", 0, nil, t)
|
||||
if err := signRequestV4(req, testCase.AccessKey, testCase.SecretKey); err != nil {
|
||||
t.Fatalf("Unable to inititalized new signed http request %s", err)
|
||||
t.Fatalf("Unable to initialized new signed http request %s", err)
|
||||
}
|
||||
_, _, s3Error := validateAdminSignature(ctx, req, globalMinioDefaultRegion)
|
||||
if s3Error != testCase.ErrCode {
|
||||
|
||||
@@ -376,7 +376,7 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||
|
||||
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
|
||||
disk, format, err := connectEndpoint(endpoint)
|
||||
disk, format, _, err := connectEndpoint(endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error: %w, %s", err, endpoint)
|
||||
}
|
||||
@@ -490,6 +490,10 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
|
||||
}
|
||||
|
||||
for _, disk := range disks {
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := loadHealingTracker(ctx, disk)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errFileNotFound) {
|
||||
|
||||
+5
-4
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/bucket/versioning"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
"github.com/minio/pkg/v2/wildcard"
|
||||
@@ -44,7 +45,7 @@ import (
|
||||
// bucket: mybucket # Bucket where this batch job will expire matching objects from
|
||||
// prefix: myprefix # (Optional) Prefix under which this job will expire objects matching the rules below.
|
||||
// rules:
|
||||
// - type: object # regular objects with zero ore more older versions
|
||||
// - type: object # regular objects with zero or more older versions
|
||||
// name: NAME # match object names that satisfy the wildcard expression.
|
||||
// olderThan: 70h # match objects older than this value
|
||||
// createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||
@@ -558,7 +559,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 10*time.Second, job))
|
||||
|
||||
case <-ctx.Done():
|
||||
// persist in-memory state immediately before exiting due to context cancelation.
|
||||
// persist in-memory state immediately before exiting due to context cancellation.
|
||||
logger.LogIf(ctx, ri.updateAfter(ctx, api, 0, job))
|
||||
return
|
||||
|
||||
@@ -648,7 +649,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
||||
case expireCh <- toDel:
|
||||
}
|
||||
}
|
||||
close(expireCh)
|
||||
xioutil.SafeClose(expireCh)
|
||||
|
||||
wk.Wait() // waits for all expire goroutines to complete
|
||||
|
||||
@@ -658,7 +659,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
|
||||
|
||||
// Close the saverQuitCh - this also triggers saving in-memory state
|
||||
// immediately one last time before we exit this method.
|
||||
close(saverQuitCh)
|
||||
xioutil.SafeClose(saverQuitCh)
|
||||
|
||||
// Notify expire jobs final status to the configured endpoint
|
||||
buf, _ := json.Marshal(ri)
|
||||
|
||||
@@ -30,7 +30,7 @@ expire: # Expire objects that match a condition
|
||||
bucket: mybucket # Bucket where this batch job will expire matching objects from
|
||||
prefix: myprefix # (Optional) Prefix under which this job will expire objects matching the rules below.
|
||||
rules:
|
||||
- type: object # regular objects with zero ore more older versions
|
||||
- type: object # regular objects with zero or more older versions
|
||||
name: NAME # match object names that satisfy the wildcard expression.
|
||||
olderThan: 70h # match objects older than this value
|
||||
createdBefore: "2006-01-02T15:04:05.00Z" # match objects created before "date"
|
||||
|
||||
@@ -47,6 +47,7 @@ import (
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/ioutil"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
@@ -545,7 +546,7 @@ func (r BatchJobReplicateV1) writeAsArchive(ctx context.Context, objAPI ObjectLa
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(input)
|
||||
defer xioutil.SafeClose(input)
|
||||
|
||||
for _, entry := range entries {
|
||||
gr, err := objAPI.GetObjectNInfo(ctx, r.Source.Bucket,
|
||||
@@ -1038,7 +1039,7 @@ 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 close(slowCh)
|
||||
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{
|
||||
@@ -1809,7 +1810,7 @@ func (j *BatchJobPool) queueJob(req *BatchJobRequest) error {
|
||||
select {
|
||||
case <-j.ctx.Done():
|
||||
j.once.Do(func() {
|
||||
close(j.jobCh)
|
||||
xioutil.SafeClose(j.jobCh)
|
||||
})
|
||||
case j.jobCh <- req:
|
||||
default:
|
||||
|
||||
@@ -134,7 +134,7 @@ func (b *streamingBitrotReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
if closer, ok := b.rc.(io.Closer); ok {
|
||||
// drain the body for connection re-use at network layer.
|
||||
// drain the body for connection reuse at network layer.
|
||||
xhttp.DrainBody(struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
|
||||
+87
-148
@@ -19,71 +19,47 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
)
|
||||
|
||||
const (
|
||||
bootstrapRESTVersion = "v1"
|
||||
bootstrapRESTVersionPrefix = SlashSeparator + bootstrapRESTVersion
|
||||
bootstrapRESTPrefix = minioReservedBucketPath + "/bootstrap"
|
||||
bootstrapRESTPath = bootstrapRESTPrefix + bootstrapRESTVersionPrefix
|
||||
)
|
||||
|
||||
const (
|
||||
bootstrapRESTMethodHealth = "/health"
|
||||
bootstrapRESTMethodVerify = "/verify"
|
||||
)
|
||||
|
||||
// To abstract a node over network.
|
||||
type bootstrapRESTServer struct{}
|
||||
|
||||
//go:generate msgp -file=$GOFILE
|
||||
|
||||
// ServerSystemConfig - captures information about server configuration.
|
||||
type ServerSystemConfig struct {
|
||||
MinioEndpoints EndpointServerPools
|
||||
MinioEnv map[string]string
|
||||
NEndpoints int
|
||||
CmdLines []string
|
||||
MinioEnv map[string]string
|
||||
}
|
||||
|
||||
// Diff - returns error on first difference found in two configs.
|
||||
func (s1 ServerSystemConfig) Diff(s2 ServerSystemConfig) error {
|
||||
if s1.MinioEndpoints.NEndpoints() != s2.MinioEndpoints.NEndpoints() {
|
||||
return fmt.Errorf("Expected number of endpoints %d, seen %d", s1.MinioEndpoints.NEndpoints(),
|
||||
s2.MinioEndpoints.NEndpoints())
|
||||
func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
|
||||
ns1 := s1.NEndpoints
|
||||
ns2 := s2.NEndpoints
|
||||
if ns1 != ns2 {
|
||||
return fmt.Errorf("Expected number of endpoints %d, seen %d", ns1, ns2)
|
||||
}
|
||||
|
||||
for i, ep := range s1.MinioEndpoints {
|
||||
if ep.CmdLine != s2.MinioEndpoints[i].CmdLine {
|
||||
return fmt.Errorf("Expected command line argument %s, seen %s", ep.CmdLine,
|
||||
s2.MinioEndpoints[i].CmdLine)
|
||||
}
|
||||
if ep.SetCount != s2.MinioEndpoints[i].SetCount {
|
||||
return fmt.Errorf("Expected set count %d, seen %d", ep.SetCount,
|
||||
s2.MinioEndpoints[i].SetCount)
|
||||
}
|
||||
if ep.DrivesPerSet != s2.MinioEndpoints[i].DrivesPerSet {
|
||||
return fmt.Errorf("Expected drives pet set %d, seen %d", ep.DrivesPerSet,
|
||||
s2.MinioEndpoints[i].DrivesPerSet)
|
||||
}
|
||||
if ep.Platform != s2.MinioEndpoints[i].Platform {
|
||||
return fmt.Errorf("Expected platform '%s', found to be on '%s'",
|
||||
ep.Platform, s2.MinioEndpoints[i].Platform)
|
||||
for i, cmdLine := range s1.CmdLines {
|
||||
if cmdLine != s2.CmdLines[i] {
|
||||
return fmt.Errorf("Expected command line argument %s, seen %s", cmdLine,
|
||||
s2.CmdLines[i])
|
||||
}
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(s1.MinioEnv, s2.MinioEnv) {
|
||||
return nil
|
||||
}
|
||||
@@ -131,7 +107,7 @@ var skipEnvs = map[string]struct{}{
|
||||
"MINIO_SECRET_KEY": {},
|
||||
}
|
||||
|
||||
func getServerSystemCfg() ServerSystemConfig {
|
||||
func getServerSystemCfg() *ServerSystemConfig {
|
||||
envs := env.List("MINIO_")
|
||||
envValues := make(map[string]string, len(envs))
|
||||
for _, envK := range envs {
|
||||
@@ -144,131 +120,113 @@ func getServerSystemCfg() ServerSystemConfig {
|
||||
}
|
||||
envValues[envK] = logger.HashString(env.Get(envK, ""))
|
||||
}
|
||||
return ServerSystemConfig{
|
||||
MinioEndpoints: globalEndpoints,
|
||||
MinioEnv: envValues,
|
||||
scfg := &ServerSystemConfig{NEndpoints: globalEndpoints.NEndpoints(), MinioEnv: envValues}
|
||||
var cmdLines []string
|
||||
for _, ep := range globalEndpoints {
|
||||
cmdLines = append(cmdLines, ep.CmdLine)
|
||||
}
|
||||
scfg.CmdLines = cmdLines
|
||||
return scfg
|
||||
}
|
||||
|
||||
func (b *bootstrapRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
w.Write([]byte(err.Error()))
|
||||
func (s *bootstrapRESTServer) VerifyHandler(params *grid.MSS) (*ServerSystemConfig, *grid.RemoteErr) {
|
||||
return getServerSystemCfg(), nil
|
||||
}
|
||||
|
||||
// HealthHandler returns success if request is valid
|
||||
func (b *bootstrapRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request) {}
|
||||
|
||||
func (b *bootstrapRESTServer) VerifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := newContext(r, w, "VerifyHandler")
|
||||
|
||||
if err := storageServerRequestValidate(r); err != nil {
|
||||
b.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
cfg := getServerSystemCfg()
|
||||
logger.LogIf(ctx, json.NewEncoder(w).Encode(&cfg))
|
||||
}
|
||||
var serverVerifyHandler = grid.NewSingleHandler[*grid.MSS, *ServerSystemConfig](grid.HandlerServerVerify, grid.NewMSS, func() *ServerSystemConfig { return &ServerSystemConfig{} })
|
||||
|
||||
// registerBootstrapRESTHandlers - register bootstrap rest router.
|
||||
func registerBootstrapRESTHandlers(router *mux.Router) {
|
||||
h := func(f http.HandlerFunc) http.HandlerFunc {
|
||||
return collectInternodeStats(httpTraceHdrs(f))
|
||||
}
|
||||
|
||||
func registerBootstrapRESTHandlers(gm *grid.Manager) {
|
||||
server := &bootstrapRESTServer{}
|
||||
subrouter := router.PathPrefix(bootstrapRESTPrefix).Subrouter()
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodHealth).HandlerFunc(
|
||||
h(server.HealthHandler))
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(bootstrapRESTVersionPrefix + bootstrapRESTMethodVerify).HandlerFunc(
|
||||
h(server.VerifyHandler))
|
||||
logger.FatalIf(serverVerifyHandler.Register(gm, server.VerifyHandler), "unable to register handler")
|
||||
}
|
||||
|
||||
// client to talk to bootstrap NEndpoints.
|
||||
type bootstrapRESTClient struct {
|
||||
endpoint Endpoint
|
||||
restClient *rest.Client
|
||||
gridConn *grid.Connection
|
||||
}
|
||||
|
||||
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
||||
// permanently. The only way to restore the connection is at the xl-sets layer by xlsets.monitorAndConnectEndpoints()
|
||||
// after verifying format.json
|
||||
func (client *bootstrapRESTClient) callWithContext(ctx context.Context, method string, values url.Values, body io.Reader, length int64) (respBody io.ReadCloser, err error) {
|
||||
if values == nil {
|
||||
values = make(url.Values)
|
||||
// Verify function verifies the server config.
|
||||
func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg *ServerSystemConfig) (err error) {
|
||||
if newObjectLayerFn() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
respBody, err = client.restClient.Call(ctx, method, values, body, length)
|
||||
if err == nil {
|
||||
return respBody, nil
|
||||
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return srcCfg.Diff(recvCfg)
|
||||
}
|
||||
|
||||
// Stringer provides a canonicalized representation of node.
|
||||
func (client *bootstrapRESTClient) String() string {
|
||||
return client.endpoint.String()
|
||||
return client.gridConn.String()
|
||||
}
|
||||
|
||||
// Verify - fetches system server config.
|
||||
func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg ServerSystemConfig) (err error) {
|
||||
if newObjectLayerFn() != nil {
|
||||
return nil
|
||||
}
|
||||
respBody, err := client.callWithContext(ctx, bootstrapRESTMethodVerify, nil, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
recvCfg := ServerSystemConfig{}
|
||||
if err = json.NewDecoder(respBody).Decode(&recvCfg); err != nil {
|
||||
return err
|
||||
}
|
||||
return srcCfg.Diff(recvCfg)
|
||||
}
|
||||
|
||||
func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools) error {
|
||||
func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools, gm *grid.Manager) error {
|
||||
srcCfg := getServerSystemCfg()
|
||||
clnts := newBootstrapRESTClients(endpointServerPools)
|
||||
clnts := newBootstrapRESTClients(endpointServerPools, gm)
|
||||
var onlineServers int
|
||||
var offlineEndpoints []error
|
||||
var incorrectConfigs []error
|
||||
var retries int
|
||||
var mu sync.Mutex
|
||||
for onlineServers < len(clnts)/2 {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(clnts))
|
||||
onlineServers = 0
|
||||
for _, clnt := range clnts {
|
||||
if err := clnt.Verify(ctx, srcCfg); err != nil {
|
||||
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %v", err, clnt.endpoint))
|
||||
if !isNetworkError(err) {
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err), clnt.String())
|
||||
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt.String(), err))
|
||||
} else {
|
||||
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt.String(), err))
|
||||
go func(clnt *bootstrapRESTClient) {
|
||||
defer wg.Done()
|
||||
|
||||
if clnt.gridConn.State() != grid.StateConnected {
|
||||
mu.Lock()
|
||||
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt, grid.ErrDisconnected))
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
onlineServers++
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := clnt.Verify(ctx, srcCfg)
|
||||
mu.Lock()
|
||||
if err != nil {
|
||||
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %s", err, clnt))
|
||||
if !isNetworkError(err) {
|
||||
logger.LogOnceIf(context.Background(), fmt.Errorf("%s has incorrect configuration: %w", clnt, err), "incorrect_"+clnt.String())
|
||||
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt, err))
|
||||
} else {
|
||||
offlineEndpoints = append(offlineEndpoints, fmt.Errorf("%s is unreachable: %w", clnt, err))
|
||||
}
|
||||
} else {
|
||||
onlineServers++
|
||||
}
|
||||
mu.Unlock()
|
||||
}(clnt)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
// Sleep for a while - so that we don't go into
|
||||
// 100% CPU when half the endpoints are offline.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// Sleep and stagger to avoid blocked CPU and thundering
|
||||
// herd upon start up sequence.
|
||||
time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond))))
|
||||
retries++
|
||||
// after 20 retries start logging that servers are not reachable yet
|
||||
if retries >= 20 {
|
||||
logger.Info(fmt.Sprintf("Waiting for atleast %d remote servers with valid configuration to be online", len(clnts)/2))
|
||||
logger.Info(fmt.Sprintf("Waiting for at least %d remote servers with valid configuration to be online", len(clnts)/2))
|
||||
if len(offlineEndpoints) > 0 {
|
||||
logger.Info(fmt.Sprintf("Following servers are currently offline or unreachable %s", offlineEndpoints))
|
||||
}
|
||||
if len(incorrectConfigs) > 0 {
|
||||
logger.Info(fmt.Sprintf("Following servers have mismatching configuration %s", incorrectConfigs))
|
||||
}
|
||||
retries = 0 // reset to log again after 5 retries.
|
||||
retries = 0 // reset to log again after 20 retries.
|
||||
}
|
||||
offlineEndpoints = nil
|
||||
incorrectConfigs = nil
|
||||
@@ -277,39 +235,20 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBootstrapRESTClients(endpointServerPools EndpointServerPools) []*bootstrapRESTClient {
|
||||
seenHosts := set.NewStringSet()
|
||||
func newBootstrapRESTClients(endpointServerPools EndpointServerPools, gm *grid.Manager) []*bootstrapRESTClient {
|
||||
seenClient := set.NewStringSet()
|
||||
var clnts []*bootstrapRESTClient
|
||||
for _, ep := range endpointServerPools {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
if seenHosts.Contains(endpoint.Host) {
|
||||
if endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
seenHosts.Add(endpoint.Host)
|
||||
|
||||
// Only proceed for remote endpoints.
|
||||
if !endpoint.IsLocal {
|
||||
cl := newBootstrapRESTClient(endpoint)
|
||||
if serverDebugLog {
|
||||
cl.restClient.TraceOutput = os.Stdout
|
||||
}
|
||||
clnts = append(clnts, cl)
|
||||
if seenClient.Contains(endpoint.Host) {
|
||||
continue
|
||||
}
|
||||
seenClient.Add(endpoint.Host)
|
||||
clnts = append(clnts, &bootstrapRESTClient{gm.Connection(endpoint.GridHost())})
|
||||
}
|
||||
}
|
||||
return clnts
|
||||
}
|
||||
|
||||
// Returns a new bootstrap client.
|
||||
func newBootstrapRESTClient(endpoint Endpoint) *bootstrapRESTClient {
|
||||
serverURL := &url.URL{
|
||||
Scheme: endpoint.Scheme,
|
||||
Host: endpoint.Host,
|
||||
Path: bootstrapRESTPath,
|
||||
}
|
||||
|
||||
restClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
|
||||
restClient.HealthCheckFn = nil
|
||||
|
||||
return &bootstrapRESTClient{endpoint: endpoint, restClient: restClient}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
package cmd
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *ServerSystemConfig) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, err = dc.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "NEndpoints":
|
||||
z.NEndpoints, err = dc.ReadInt()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "NEndpoints")
|
||||
return
|
||||
}
|
||||
case "CmdLines":
|
||||
var zb0002 uint32
|
||||
zb0002, err = dc.ReadArrayHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CmdLines")
|
||||
return
|
||||
}
|
||||
if cap(z.CmdLines) >= int(zb0002) {
|
||||
z.CmdLines = (z.CmdLines)[:zb0002]
|
||||
} else {
|
||||
z.CmdLines = make([]string, zb0002)
|
||||
}
|
||||
for za0001 := range z.CmdLines {
|
||||
z.CmdLines[za0001], err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CmdLines", za0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
case "MinioEnv":
|
||||
var zb0003 uint32
|
||||
zb0003, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv")
|
||||
return
|
||||
}
|
||||
if z.MinioEnv == nil {
|
||||
z.MinioEnv = make(map[string]string, zb0003)
|
||||
} else if len(z.MinioEnv) > 0 {
|
||||
for key := range z.MinioEnv {
|
||||
delete(z.MinioEnv, key)
|
||||
}
|
||||
}
|
||||
for zb0003 > 0 {
|
||||
zb0003--
|
||||
var za0002 string
|
||||
var za0003 string
|
||||
za0002, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv")
|
||||
return
|
||||
}
|
||||
za0003, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv", za0002)
|
||||
return
|
||||
}
|
||||
z.MinioEnv[za0002] = za0003
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *ServerSystemConfig) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 3
|
||||
// write "NEndpoints"
|
||||
err = en.Append(0x83, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteInt(z.NEndpoints)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "NEndpoints")
|
||||
return
|
||||
}
|
||||
// write "CmdLines"
|
||||
err = en.Append(0xa8, 0x43, 0x6d, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteArrayHeader(uint32(len(z.CmdLines)))
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CmdLines")
|
||||
return
|
||||
}
|
||||
for za0001 := range z.CmdLines {
|
||||
err = en.WriteString(z.CmdLines[za0001])
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CmdLines", za0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
// write "MinioEnv"
|
||||
err = en.Append(0xa8, 0x4d, 0x69, 0x6e, 0x69, 0x6f, 0x45, 0x6e, 0x76)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteMapHeader(uint32(len(z.MinioEnv)))
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv")
|
||||
return
|
||||
}
|
||||
for za0002, za0003 := range z.MinioEnv {
|
||||
err = en.WriteString(za0002)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv")
|
||||
return
|
||||
}
|
||||
err = en.WriteString(za0003)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv", za0002)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *ServerSystemConfig) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 3
|
||||
// string "NEndpoints"
|
||||
o = append(o, 0x83, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
|
||||
o = msgp.AppendInt(o, z.NEndpoints)
|
||||
// string "CmdLines"
|
||||
o = append(o, 0xa8, 0x43, 0x6d, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x73)
|
||||
o = msgp.AppendArrayHeader(o, uint32(len(z.CmdLines)))
|
||||
for za0001 := range z.CmdLines {
|
||||
o = msgp.AppendString(o, z.CmdLines[za0001])
|
||||
}
|
||||
// string "MinioEnv"
|
||||
o = append(o, 0xa8, 0x4d, 0x69, 0x6e, 0x69, 0x6f, 0x45, 0x6e, 0x76)
|
||||
o = msgp.AppendMapHeader(o, uint32(len(z.MinioEnv)))
|
||||
for za0002, za0003 := range z.MinioEnv {
|
||||
o = msgp.AppendString(o, za0002)
|
||||
o = msgp.AppendString(o, za0003)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *ServerSystemConfig) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "NEndpoints":
|
||||
z.NEndpoints, bts, err = msgp.ReadIntBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "NEndpoints")
|
||||
return
|
||||
}
|
||||
case "CmdLines":
|
||||
var zb0002 uint32
|
||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CmdLines")
|
||||
return
|
||||
}
|
||||
if cap(z.CmdLines) >= int(zb0002) {
|
||||
z.CmdLines = (z.CmdLines)[:zb0002]
|
||||
} else {
|
||||
z.CmdLines = make([]string, zb0002)
|
||||
}
|
||||
for za0001 := range z.CmdLines {
|
||||
z.CmdLines[za0001], bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "CmdLines", za0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
case "MinioEnv":
|
||||
var zb0003 uint32
|
||||
zb0003, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv")
|
||||
return
|
||||
}
|
||||
if z.MinioEnv == nil {
|
||||
z.MinioEnv = make(map[string]string, zb0003)
|
||||
} else if len(z.MinioEnv) > 0 {
|
||||
for key := range z.MinioEnv {
|
||||
delete(z.MinioEnv, key)
|
||||
}
|
||||
}
|
||||
for zb0003 > 0 {
|
||||
var za0002 string
|
||||
var za0003 string
|
||||
zb0003--
|
||||
za0002, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv")
|
||||
return
|
||||
}
|
||||
za0003, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "MinioEnv", za0002)
|
||||
return
|
||||
}
|
||||
z.MinioEnv[za0002] = za0003
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *ServerSystemConfig) Msgsize() (s int) {
|
||||
s = 1 + 11 + msgp.IntSize + 9 + msgp.ArrayHeaderSize
|
||||
for za0001 := range z.CmdLines {
|
||||
s += msgp.StringPrefixSize + len(z.CmdLines[za0001])
|
||||
}
|
||||
s += 9 + msgp.MapHeaderSize
|
||||
if z.MinioEnv != nil {
|
||||
for za0002, za0003 := range z.MinioEnv {
|
||||
_ = za0003
|
||||
s += msgp.StringPrefixSize + len(za0002) + msgp.StringPrefixSize + len(za0003)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package cmd
|
||||
|
||||
// Code generated by github.com/tinylib/msgp DO NOT EDIT.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
func TestMarshalUnmarshalServerSystemConfig(t *testing.T) {
|
||||
v := ServerSystemConfig{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||
}
|
||||
|
||||
left, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMsgServerSystemConfig(b *testing.B) {
|
||||
v := ServerSystemConfig{}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.MarshalMsg(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendMsgServerSystemConfig(b *testing.B) {
|
||||
v := ServerSystemConfig{}
|
||||
bts := make([]byte, 0, v.Msgsize())
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalServerSystemConfig(b *testing.B) {
|
||||
v := ServerSystemConfig{}
|
||||
bts, _ := v.MarshalMsg(nil)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeServerSystemConfig(t *testing.T) {
|
||||
v := ServerSystemConfig{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
|
||||
m := v.Msgsize()
|
||||
if buf.Len() > m {
|
||||
t.Log("WARNING: TestEncodeDecodeServerSystemConfig Msgsize() is inaccurate")
|
||||
}
|
||||
|
||||
vn := ServerSystemConfig{}
|
||||
err := msgp.Decode(&buf, &vn)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
msgp.Encode(&buf, &v)
|
||||
err = msgp.NewReader(&buf).Skip()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeServerSystemConfig(b *testing.B) {
|
||||
v := ServerSystemConfig{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
en := msgp.NewWriter(msgp.Nowhere)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.EncodeMsg(en)
|
||||
}
|
||||
en.Flush()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeServerSystemConfig(b *testing.B) {
|
||||
v := ServerSystemConfig{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||
dc := msgp.NewReader(rd)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := v.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,7 +424,7 @@ func (api objectAPIHandlers) DeleteMultipleObjectsHandler(w http.ResponseWriter,
|
||||
return
|
||||
}
|
||||
|
||||
// Content-Md5 is requied should be set
|
||||
// Content-Md5 is required should be set
|
||||
// http://docs.aws.amazon.com/AmazonS3/latest/API/multiobjectdeleteapi.html
|
||||
if _, ok := r.Header[xhttp.ContentMD5]; !ok {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMissingContentMD5), r.URL)
|
||||
|
||||
@@ -252,7 +252,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
|
||||
},
|
||||
// Test case - 3.
|
||||
// Testing for signature mismatch error.
|
||||
// setting invalid acess and secret key.
|
||||
// setting invalid access and secret key.
|
||||
{
|
||||
bucketName: bucketName,
|
||||
accessKey: "abcd",
|
||||
@@ -415,7 +415,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
shouldPass: false,
|
||||
},
|
||||
// Test case - 6.
|
||||
// Setting a negative value to max-uploads paramater, should result in http.StatusBadRequest.
|
||||
// Setting a negative value to max-uploads parameter, should result in http.StatusBadRequest.
|
||||
{
|
||||
bucket: bucketName,
|
||||
prefix: "",
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
"github.com/minio/minio/internal/event"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/s3select"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
@@ -118,8 +119,8 @@ func (es *expiryState) PendingTasks() int {
|
||||
// close closes work channels exactly once.
|
||||
func (es *expiryState) close() {
|
||||
es.once.Do(func() {
|
||||
close(es.byDaysCh)
|
||||
close(es.byNewerNoncurrentCh)
|
||||
xioutil.SafeClose(es.byDaysCh)
|
||||
xioutil.SafeClose(es.byNewerNoncurrentCh)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ func (api objectAPIHandlers) listObjectVersionsHandler(w http.ResponseWriter, r
|
||||
|
||||
listObjectVersions := objectAPI.ListObjectVersions
|
||||
|
||||
// Inititate a list object versions operation based on the input params.
|
||||
// Initiate a list object versions operation based on the input params.
|
||||
// On success would return back ListObjectsInfo object to be
|
||||
// marshaled into S3 compatible XML header.
|
||||
listObjectVersionsInfo, err := listObjectVersions(ctx, bucket, prefix, marker, versionIDMarker, delimiter, maxkeys)
|
||||
@@ -201,10 +201,10 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
|
||||
)
|
||||
|
||||
if r.Header.Get(xMinIOExtract) == "true" && strings.Contains(prefix, archivePattern) {
|
||||
// Inititate a list objects operation inside a zip file based in the input params
|
||||
// Initiate a list objects operation inside a zip file based in the input params
|
||||
listObjectsV2Info, err = listObjectsV2InArchive(ctx, objectAPI, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter)
|
||||
} else {
|
||||
// Inititate a list objects operation based on the input params.
|
||||
// Initiate a list objects operation based on the input params.
|
||||
// On success would return back ListObjectsInfo object to be
|
||||
// marshaled into S3 compatible XML header.
|
||||
listObjectsV2Info, err = objectAPI.ListObjectsV2(ctx, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter)
|
||||
@@ -304,7 +304,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
|
||||
|
||||
listObjects := objectAPI.ListObjects
|
||||
|
||||
// Inititate a list objects operation based on the input params.
|
||||
// Initiate a list objects operation based on the input params.
|
||||
// On success would return back ListObjectsInfo object to be
|
||||
// marshaled into S3 compatible XML header.
|
||||
listObjectsInfo, err := listObjects(ctx, bucket, prefix, marker, delimiter, maxKeys)
|
||||
|
||||
@@ -317,7 +317,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
if recV4.Code != testCase.expectedRespStatus {
|
||||
@@ -331,7 +331,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, instanceType, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
if recV2.Code != testCase.expectedRespStatus {
|
||||
@@ -407,7 +407,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV4, reqV4)
|
||||
if recV4.Code != testPolicy.expectedRespStatus {
|
||||
@@ -421,7 +421,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: Failed to create HTTP request for PutBucketPolicyHandler: <ERROR> %v", i+1, err)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(recV2, reqV2)
|
||||
if recV2.Code != testPolicy.expectedRespStatus {
|
||||
|
||||
@@ -219,7 +219,7 @@ type ReplicateDecision struct {
|
||||
targetsMap map[string]replicateTargetDecision
|
||||
}
|
||||
|
||||
// ReplicateAny returns true if atleast one target qualifies for replication
|
||||
// ReplicateAny returns true if at least one target qualifies for replication
|
||||
func (d ReplicateDecision) ReplicateAny() bool {
|
||||
for _, t := range d.targetsMap {
|
||||
if t.Replicate {
|
||||
@@ -229,7 +229,7 @@ func (d ReplicateDecision) ReplicateAny() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Synchronous returns true if atleast one target qualifies for synchronous replication
|
||||
// Synchronous returns true if at least one target qualifies for synchronous replication
|
||||
func (d ReplicateDecision) Synchronous() bool {
|
||||
for _, t := range d.targetsMap {
|
||||
if t.Synchronous {
|
||||
@@ -336,7 +336,7 @@ type ReplicationState struct {
|
||||
DeleteMarker bool // represents DeleteMarker replication state
|
||||
ReplicationTimeStamp time.Time // timestamp when last replication activity happened
|
||||
ReplicationStatusInternal string // stringified representation of all replication activity
|
||||
// VersionPurgeStatusInternal is internally in the format "arn1=PENDING;arn2=COMMPLETED;"
|
||||
// VersionPurgeStatusInternal is internally in the format "arn1=PENDING;arn2=COMPLETED;"
|
||||
VersionPurgeStatusInternal string // stringified representation of all version purge statuses
|
||||
ReplicateDecisionStr string // stringified representation of replication decision for each target
|
||||
Targets map[string]replication.StatusType // map of ARN->replication status for ongoing replication activity
|
||||
|
||||
+23
-22
@@ -47,6 +47,7 @@ import (
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
"github.com/zeebo/xxh3"
|
||||
@@ -495,7 +496,7 @@ func replicateDelete(ctx context.Context, dobj DeletedObjectReplicationInfo, obj
|
||||
}
|
||||
tgtClnt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtEntry.Arn)
|
||||
if tgtClnt == nil {
|
||||
// Skip stale targets if any and log them to be missing atleast once.
|
||||
// Skip stale targets if any and log them to be missing at least once.
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("failed to get target for bucket:%s arn:%s", bucket, tgtEntry.Arn), tgtEntry.Arn)
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
@@ -683,7 +684,7 @@ func replicateDeleteToTarget(ctx context.Context, dobj DeletedObjectReplicationI
|
||||
} else {
|
||||
rinfo.VersionPurgeStatus = Failed
|
||||
}
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate delete marker to %s/%s(%s): %s", tgt.Bucket, dobj.ObjectName, versionID, rmErr))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate delete marker to %s: %s/%s(%s): %w", tgt.EndpointURL(), tgt.Bucket, dobj.ObjectName, versionID, rmErr))
|
||||
if rmErr != nil && minio.IsNetworkOrHostDown(rmErr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
|
||||
globalBucketTargetSys.markOffline(tgt.EndpointURL())
|
||||
}
|
||||
@@ -1204,7 +1205,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||
}
|
||||
|
||||
if tgt.Bucket == "" {
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty", objInfo.Name, objInfo.VersionID))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty for target %s", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1230,7 +1231,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||
|
||||
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s err:%w", bucket, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1265,14 +1266,14 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
|
||||
r, objInfo, putOpts); rinfo.Err != nil {
|
||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s (target: %s)", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err, tgt.EndpointURL()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts); rinfo.Err != nil {
|
||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s (target: %s)", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err, tgt.EndpointURL()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1336,7 +1337,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
UserAgent: "Internal: [Replication]",
|
||||
Host: globalLocalNodeName,
|
||||
})
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to update replicate metadata for %s/%s(%s): %w", bucket, object, objInfo.VersionID, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate to target %s for %s/%s(%s): %w", tgt.EndpointURL(), bucket, object, objInfo.VersionID, err))
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1367,7 +1368,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
}
|
||||
|
||||
if tgt.Bucket == "" {
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty", objInfo.Name, objInfo.VersionID))
|
||||
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{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1397,7 +1398,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
if rAction == replicateNone {
|
||||
if ri.OpType == replication.ExistingObjectReplicationType &&
|
||||
objInfo.ModTime.Unix() > oi.LastModified.Unix() && objInfo.VersionID == nullVersionID {
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate %s/%s (null). Newer version exists on target", bucket, object))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate %s/%s (null). Newer version exists on target %s", bucket, object, tgt.EndpointURL()))
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1487,13 +1488,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
}
|
||||
if _, rinfo.Err = c.CopyObject(ctx, tgt.Bucket, object, tgt.Bucket, object, getCopyObjMetadata(objInfo, tgt.StorageClass), srcOpts, dstOpts); rinfo.Err != nil {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||
}
|
||||
} else {
|
||||
var putOpts minio.PutObjectOptions
|
||||
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("failed to get target for replication bucket:%s err:%w", bucket, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
|
||||
sendEvent(eventArgs{
|
||||
EventName: event.ObjectReplicationNotTracked,
|
||||
BucketName: bucket,
|
||||
@@ -1527,7 +1528,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
r, objInfo, putOpts); rinfo.Err != nil {
|
||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||
} else {
|
||||
rinfo.ReplicationStatus = replication.Completed
|
||||
}
|
||||
@@ -1536,7 +1537,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
if _, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts); rinfo.Err != nil {
|
||||
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
|
||||
} else {
|
||||
rinfo.ReplicationStatus = replication.Completed
|
||||
}
|
||||
@@ -1894,7 +1895,7 @@ func (p *ReplicationPool) AddLargeWorkers() {
|
||||
go func() {
|
||||
<-p.ctx.Done()
|
||||
for i := 0; i < LargeWorkerCount; i++ {
|
||||
close(p.lrgworkers[i])
|
||||
xioutil.SafeClose(p.lrgworkers[i])
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1953,7 +1954,7 @@ func (p *ReplicationPool) ResizeWorkers(n, checkOld int) {
|
||||
for len(p.workers) > n {
|
||||
worker := p.workers[len(p.workers)-1]
|
||||
p.workers = p.workers[:len(p.workers)-1]
|
||||
close(worker)
|
||||
xioutil.SafeClose(worker)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2619,7 +2620,7 @@ func (s *replicationResyncer) PersistToDisk(ctx context.Context, objectAPI Objec
|
||||
}
|
||||
if updt {
|
||||
if err := saveResyncStatus(ctx, bucket, brs, objectAPI); err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("Could not save resync metadata to drive for %s - %w", bucket, err))
|
||||
logger.LogIf(ctx, fmt.Errorf("could not save resync metadata to drive for %s - %w", bucket, err))
|
||||
} else {
|
||||
lastResyncStatusSave[bucket] = brs.LastUpdate
|
||||
}
|
||||
@@ -2755,7 +2756,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
||||
}
|
||||
workers := make([]chan ReplicateObjectInfo, resyncParallelRoutines)
|
||||
resultCh := make(chan TargetReplicationResyncStatus, 1)
|
||||
defer close(resultCh)
|
||||
defer xioutil.SafeClose(resultCh)
|
||||
go func() {
|
||||
for r := range resultCh {
|
||||
s.incStats(r, opts)
|
||||
@@ -2867,7 +2868,7 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
|
||||
}
|
||||
}
|
||||
for i := 0; i < resyncParallelRoutines; i++ {
|
||||
close(workers[i])
|
||||
xioutil.SafeClose(workers[i])
|
||||
}
|
||||
wg.Wait()
|
||||
resyncStatus = ResyncCompleted
|
||||
@@ -2997,7 +2998,7 @@ func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []Buck
|
||||
}
|
||||
duration := time.Duration(r.Float64() * float64(time.Minute))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||
// Make sure to sleep at least a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
@@ -3123,7 +3124,7 @@ func getReplicationDiff(ctx context.Context, objAPI ObjectLayer, bucket string,
|
||||
}
|
||||
diffCh := make(chan madmin.DiffInfo, 4000)
|
||||
go func() {
|
||||
defer close(diffCh)
|
||||
defer xioutil.SafeClose(diffCh)
|
||||
for obj := range objInfoCh {
|
||||
if contextCanceled(ctx) {
|
||||
// Just consume input...
|
||||
@@ -3316,7 +3317,7 @@ func (p *ReplicationPool) persistMRF() {
|
||||
mTimer.Reset(mrfSaveInterval)
|
||||
case <-p.ctx.Done():
|
||||
p.mrfStopCh <- struct{}{}
|
||||
close(p.mrfSaveCh)
|
||||
xioutil.SafeClose(p.mrfSaveCh)
|
||||
// We try to save if possible, but we don't care beyond that.
|
||||
saveMRFToDisk()
|
||||
return
|
||||
@@ -3551,7 +3552,7 @@ func (p *ReplicationPool) getMRF(ctx context.Context, bucket string) (ch <-chan
|
||||
|
||||
mrfCh := make(chan madmin.ReplicationMRF, 100)
|
||||
go func() {
|
||||
defer close(mrfCh)
|
||||
defer xioutil.SafeClose(mrfCh)
|
||||
for vID, e := range mrfRec.Entries {
|
||||
if bucket != "" && e.Bucket != bucket {
|
||||
continue
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ func initCallhome(ctx context.Context, objAPI ObjectLayer) {
|
||||
// sleep for some time and try again.
|
||||
duration := time.Duration(r.Float64() * float64(globalCallhomeConfig.FrequencyDur()))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||
// Make sure to sleep at least a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
|
||||
+10
-17
@@ -69,7 +69,7 @@ import (
|
||||
// serverDebugLog will enable debug printing
|
||||
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||
|
||||
var shardDiskTimeDelta time.Duration
|
||||
var currentReleaseTime time.Time
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -107,16 +107,12 @@ func init() {
|
||||
gob.Register(madmin.XFSErrorConfigs{})
|
||||
gob.Register(map[string]interface{}{})
|
||||
|
||||
var err error
|
||||
shardDiskTimeDelta, err = time.ParseDuration(env.Get("_MINIO_SHARD_DISKTIME_DELTA", "1m"))
|
||||
if err != nil {
|
||||
shardDiskTimeDelta = 1 * time.Minute
|
||||
}
|
||||
|
||||
// All minio-go and madmin-go API operations shall be performed only once,
|
||||
// another way to look at this is we are turning off retries.
|
||||
minio.MaxRetry = 1
|
||||
madmin.MaxRetry = 1
|
||||
|
||||
currentReleaseTime, _ = GetCurrentReleaseTime()
|
||||
}
|
||||
|
||||
const consolePrefix = "CONSOLE_"
|
||||
@@ -301,9 +297,7 @@ func checkUpdate(mode string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Its OK to ignore any errors during doUpdate() here.
|
||||
crTime, err := GetCurrentReleaseTime()
|
||||
if err != nil {
|
||||
if currentReleaseTime.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -314,8 +308,8 @@ func checkUpdate(mode string) {
|
||||
|
||||
var older time.Duration
|
||||
var downloadURL string
|
||||
if lrTime.After(crTime) {
|
||||
older = lrTime.Sub(crTime)
|
||||
if lrTime.After(currentReleaseTime) {
|
||||
older = lrTime.Sub(currentReleaseTime)
|
||||
downloadURL = getDownloadURL(releaseTimeToReleaseTag(lrTime))
|
||||
}
|
||||
|
||||
@@ -324,7 +318,7 @@ func checkUpdate(mode string) {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
|
||||
logger.Info(prepareUpdateMessage("Run `mc admin update ALIAS`", lrTime.Sub(currentReleaseTime)))
|
||||
}
|
||||
|
||||
func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) {
|
||||
@@ -373,7 +367,6 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
||||
|
||||
// Check "no-compat" flag from command line argument.
|
||||
ctxt.StrictS3Compat = !(ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat"))
|
||||
ctxt.PreAllocate = ctx.IsSet("pre-allocate") || ctx.GlobalIsSet("pre-allocate")
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("config-dir"):
|
||||
@@ -847,7 +840,7 @@ func loadRootCredentials() {
|
||||
// It depends on KMS env variables and global cli flags.
|
||||
func handleKMSConfig() {
|
||||
if env.IsSet(kms.EnvKMSSecretKey) && env.IsSet(kms.EnvKESEndpoint) {
|
||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKMSSecretKey, kms.EnvKESEndpoint))
|
||||
logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKMSSecretKey, kms.EnvKESEndpoint))
|
||||
}
|
||||
|
||||
if env.IsSet(kms.EnvKMSSecretKey) {
|
||||
@@ -860,10 +853,10 @@ func handleKMSConfig() {
|
||||
if env.IsSet(kms.EnvKESEndpoint) {
|
||||
if env.IsSet(kms.EnvKESAPIKey) {
|
||||
if env.IsSet(kms.EnvKESClientKey) {
|
||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientKey))
|
||||
logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientKey))
|
||||
}
|
||||
if env.IsSet(kms.EnvKESClientCert) {
|
||||
logger.Fatal(errors.New("ambigious KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
|
||||
logger.Fatal(errors.New("ambiguous KMS configuration"), fmt.Sprintf("The environment contains %q as well as %q", kms.EnvKESAPIKey, kms.EnvKESClientCert))
|
||||
}
|
||||
}
|
||||
if !env.IsSet(kms.EnvKESKeyName) {
|
||||
|
||||
@@ -499,7 +499,7 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
|
||||
|
||||
if len(globalDomainNames) != 0 && !globalDomainIPs.IsEmpty() && globalEtcdClient != nil {
|
||||
if globalDNSConfig != nil {
|
||||
// if global DNS is already configured, indicate with a warning, incase
|
||||
// if global DNS is already configured, indicate with a warning, in case
|
||||
// users are confused.
|
||||
logger.LogIf(ctx, fmt.Errorf("DNS store is already configured with %s, etcd is not used for DNS store", globalDNSConfig))
|
||||
} else {
|
||||
|
||||
+5
-4
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/config/heal"
|
||||
"github.com/minio/minio/internal/event"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
uatomic "go.uber.org/atomic"
|
||||
@@ -79,7 +80,7 @@ func initDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
runDataScanner(ctx, objAPI)
|
||||
duration := time.Duration(r.Float64() * float64(scannerCycle.Load()))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||
// Make sure to sleep at least a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
@@ -710,7 +711,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
partial: func(entries metaCacheEntries, errs []error) {
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if !ok {
|
||||
// check if we can get one entry atleast
|
||||
// check if we can get one entry at least
|
||||
// proceed to heal nonetheless, since
|
||||
// this object might be dangling.
|
||||
entry, _ = entries.firstFound()
|
||||
@@ -1471,7 +1472,7 @@ func (d *dynamicSleeper) Sleep(ctx context.Context, base time.Duration) {
|
||||
}
|
||||
|
||||
// Update the current settings and cycle all waiting.
|
||||
// Parameters are the same as in the contructor.
|
||||
// Parameters are the same as in the constructor.
|
||||
func (d *dynamicSleeper) Update(factor float64, maxWait time.Duration) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
@@ -1479,7 +1480,7 @@ func (d *dynamicSleeper) Update(factor float64, maxWait time.Duration) error {
|
||||
return nil
|
||||
}
|
||||
// Update values and cycle waiting.
|
||||
close(d.cycle)
|
||||
xioutil.SafeClose(d.cycle)
|
||||
d.factor = factor
|
||||
d.maxSleep = maxWait
|
||||
d.cycle = make(chan struct{})
|
||||
|
||||
@@ -980,7 +980,7 @@ type objectIO interface {
|
||||
// The loader is optimistic and has no locking, but tries 5 times before giving up.
|
||||
// If the object is not found, a nil error with empty data usage cache is returned.
|
||||
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
||||
// By defaut, empty data usage cache
|
||||
// By default, empty data usage cache
|
||||
*d = dataUsageCache{}
|
||||
|
||||
load := func(name string, timeout time.Duration) (bool, error) {
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ const (
|
||||
dataUsageCacheName = ".usage-cache.bin"
|
||||
)
|
||||
|
||||
// storeDataUsageInBackend will store all objects sent on the gui channel until closed.
|
||||
// storeDataUsageInBackend will store all objects sent on the dui channel until closed.
|
||||
func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan DataUsageInfo) {
|
||||
attempts := 1
|
||||
for dataUsageInfo := range dui {
|
||||
|
||||
@@ -664,7 +664,7 @@ func (d *DecryptBlocksReader) buildDecrypter(partID int) error {
|
||||
mac.Write(partIDbin[:])
|
||||
partEncryptionKey := mac.Sum(nil)
|
||||
|
||||
// Limit the reader, so the decryptor doesnt receive bytes
|
||||
// Limit the reader, so the decryptor doesn't receive bytes
|
||||
// from the next part (different DARE stream)
|
||||
encLenToRead := d.parts[d.partIndex].Size - d.partEncRelOffset
|
||||
decrypter, err := newDecryptReaderWithObjectKey(io.LimitReader(d.reader, encLenToRead), partEncryptionKey, d.startSeqNum)
|
||||
@@ -751,7 +751,7 @@ func (o ObjectInfo) DecryptedSize() (int64, error) {
|
||||
// However, DecryptETag does not try to decrypt the ETag if
|
||||
// it consists of a 128 bit hex value (32 hex chars) and exactly
|
||||
// one '-' followed by a 32-bit number.
|
||||
// This special case adresses randomly-generated ETags generated
|
||||
// This special case addresses randomly-generated ETags generated
|
||||
// by the MinIO server when running in non-compat mode. These
|
||||
// random ETags are not encrypt.
|
||||
//
|
||||
|
||||
+28
-1
@@ -386,6 +386,31 @@ func (l EndpointServerPools) NEndpoints() (count int) {
|
||||
return count
|
||||
}
|
||||
|
||||
// GridHosts will return all peers, including local.
|
||||
// in websocket grid compatible format, The local peer
|
||||
// is returned as a separate string.
|
||||
func (l EndpointServerPools) GridHosts() (gridHosts []string, gridLocal string) {
|
||||
seenHosts := set.NewStringSet()
|
||||
for _, ep := range l {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
u := endpoint.GridHost()
|
||||
if seenHosts.Contains(u) {
|
||||
continue
|
||||
}
|
||||
seenHosts.Add(u)
|
||||
|
||||
// Set local endpoint
|
||||
if endpoint.IsLocal {
|
||||
gridLocal = u
|
||||
}
|
||||
|
||||
gridHosts = append(gridHosts, u)
|
||||
}
|
||||
}
|
||||
|
||||
return gridHosts, gridLocal
|
||||
}
|
||||
|
||||
// Hostnames - returns list of unique hostnames
|
||||
func (l EndpointServerPools) Hostnames() []string {
|
||||
foundSet := set.NewStringSet()
|
||||
@@ -435,7 +460,9 @@ func (l EndpointServerPools) peers() (peers []string, local string) {
|
||||
peer := endpoint.Host
|
||||
if endpoint.IsLocal {
|
||||
if _, port := mustSplitHostPort(peer); port == globalMinioPort {
|
||||
local = peer
|
||||
if local == "" {
|
||||
local = peer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
|
||||
if disks[i] == nil {
|
||||
return
|
||||
}
|
||||
di, err := disks[i].DiskInfo(context.Background(), false)
|
||||
di, err := disks[i].DiskInfo(context.Background(), DiskInfoOptions{})
|
||||
if err != nil || di.Healing {
|
||||
// - Do not consume disks which are not reachable
|
||||
// unformatted or simply not accessible for some reason.
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
@@ -118,7 +119,7 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
||||
}
|
||||
|
||||
readTriggerCh := make(chan bool, len(p.readers))
|
||||
defer close(readTriggerCh) // close the channel upon return
|
||||
defer xioutil.SafeClose(readTriggerCh) // close the channel upon return
|
||||
|
||||
for i := 0; i < p.dataBlocks; i++ {
|
||||
// Setup read triggers for p.dataBlocks number of reads so that it reads in parallel.
|
||||
@@ -158,7 +159,7 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
||||
bufIdx := p.readerToBuf[i]
|
||||
if p.buf[bufIdx] == nil {
|
||||
// Reading first time on this disk, hence the buffer needs to be allocated.
|
||||
// Subsequent reads will re-use this buffer.
|
||||
// Subsequent reads will reuse this buffer.
|
||||
p.buf[bufIdx] = make([]byte, p.shardSize)
|
||||
}
|
||||
// For the last shard, the shardsize might be less than previous shard sizes.
|
||||
|
||||
@@ -144,7 +144,7 @@ func TestErasureDecode(t *testing.T) {
|
||||
}
|
||||
if err == nil {
|
||||
if content := writer.Bytes(); !bytes.Equal(content, data[test.offset:test.offset+test.length]) {
|
||||
t.Errorf("Test %d: read retruns wrong file content.", i)
|
||||
t.Errorf("Test %d: read returns wrong file content.", i)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,19 +191,6 @@ func filterOnlineDisksInplace(fi FileInfo, partsMetadata []FileInfo, onlineDisks
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts list of disk mtimes from FileInfo slice and returns, skips
|
||||
// slice elements that have errors.
|
||||
func listObjectDiskMtimes(partsMetadata []FileInfo) (diskMTimes []time.Time) {
|
||||
diskMTimes = bootModtimes(len(partsMetadata))
|
||||
for index, metadata := range partsMetadata {
|
||||
if metadata.IsValid() {
|
||||
// Once the file is found, save the disk mtime saved on disk.
|
||||
diskMTimes[index] = metadata.DiskMTime
|
||||
}
|
||||
}
|
||||
return diskMTimes
|
||||
}
|
||||
|
||||
// Notes:
|
||||
// There are 5 possible states a disk could be in,
|
||||
// 1. __online__ - has the latest copy of xl.meta - returned by listOnlineDisks
|
||||
@@ -277,13 +264,6 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
||||
errs []error, latestMeta FileInfo, bucket, object string,
|
||||
scanMode madmin.HealScanMode) ([]StorageAPI, []error, time.Time,
|
||||
) {
|
||||
var diskMTime time.Time
|
||||
var shardFix bool
|
||||
if !latestMeta.DataShardFixed() {
|
||||
diskMTime = pickValidDiskTimeWithQuorum(partsMetadata,
|
||||
latestMeta.Erasure.DataBlocks)
|
||||
}
|
||||
|
||||
availableDisks := make([]StorageAPI, len(onlineDisks))
|
||||
dataErrs := make([]error, len(onlineDisks))
|
||||
|
||||
@@ -351,23 +331,6 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
||||
}
|
||||
}
|
||||
|
||||
if !diskMTime.Equal(timeSentinel) && !diskMTime.IsZero() {
|
||||
if !partsMetadata[i].AcceptableDelta(diskMTime, shardDiskTimeDelta) {
|
||||
// not with in acceptable delta, skip.
|
||||
// If disk mTime mismatches it is considered outdated
|
||||
// https://github.com/minio/minio/pull/13803
|
||||
//
|
||||
// This check only is active if we could find maximally
|
||||
// occurring disk mtimes that are somewhat same across
|
||||
// the quorum. Allowing to skip those shards which we
|
||||
// might think are wrong.
|
||||
shardFix = true
|
||||
partsMetadata[i] = FileInfo{}
|
||||
dataErrs[i] = errFileCorrupt
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Always check data, if we got it.
|
||||
if (len(meta.Data) > 0 || meta.Size == 0) && len(meta.Parts) > 0 {
|
||||
checksumInfo := meta.Erasure.GetChecksumInfo(meta.Parts[0].Number)
|
||||
@@ -410,10 +373,5 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
||||
}
|
||||
}
|
||||
|
||||
if shardFix {
|
||||
// Only when shard is fixed return an appropriate disk mtime value.
|
||||
return availableDisks, dataErrs, diskMTime
|
||||
} // else return timeSentinel for disk mtime
|
||||
|
||||
return availableDisks, dataErrs, timeSentinel
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
// Returns the latest updated FileInfo files and error in case of failure.
|
||||
func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, defaultParityCount int, errs []error) (FileInfo, error) {
|
||||
// There should be atleast half correct entries, if not return failure
|
||||
// There should be at least half correct entries, if not return failure
|
||||
expectedRQuorum := len(partsMetadata) / 2
|
||||
if defaultParityCount == 0 {
|
||||
// if parity count is '0', we expected all entries to be present.
|
||||
@@ -57,7 +57,7 @@ func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, defaultPar
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
// Interate through all the modTimes and count the FileInfo(s) with latest time.
|
||||
// Iterate through all the modTimes and count the FileInfo(s) with latest time.
|
||||
for index, t := range modTimes {
|
||||
if partsMetadata[index].IsValid() && t.Equal(modTime) {
|
||||
latestFileInfo = partsMetadata[index]
|
||||
|
||||
+16
-48
@@ -33,8 +33,6 @@ import (
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
)
|
||||
|
||||
const reservedMetadataPrefixLowerDataShardFix = ReservedMetadataPrefixLower + "data-shard-fix"
|
||||
|
||||
//go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE
|
||||
|
||||
type healingMetric uint8
|
||||
@@ -45,29 +43,6 @@ const (
|
||||
healingMetricCheckAbandonedParts
|
||||
)
|
||||
|
||||
// AcceptableDelta returns 'true' if the fi.DiskMTime is under
|
||||
// acceptable delta of "delta" duration with maxTime.
|
||||
//
|
||||
// This code is primarily used for heuristic detection of
|
||||
// incorrect shards, as per https://github.com/minio/minio/pull/13803
|
||||
//
|
||||
// This check only is active if we could find maximally
|
||||
// occurring disk mtimes that are somewhat same across
|
||||
// the quorum. Allowing to skip those shards which we
|
||||
// might think are wrong.
|
||||
func (fi FileInfo) AcceptableDelta(maxTime time.Time, delta time.Duration) bool {
|
||||
diff := maxTime.Sub(fi.DiskMTime)
|
||||
if diff < 0 {
|
||||
diff = -diff
|
||||
}
|
||||
return diff < delta
|
||||
}
|
||||
|
||||
// DataShardFixed - data shard fixed?
|
||||
func (fi FileInfo) DataShardFixed() bool {
|
||||
return fi.Metadata[reservedMetadataPrefixLowerDataShardFix] == "true"
|
||||
}
|
||||
|
||||
func (er erasureObjects) listAndHeal(bucket, prefix string, scanMode madmin.HealScanMode, healEntry func(string, metaCacheEntry, madmin.HealScanMode) error) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -108,7 +83,7 @@ func (er erasureObjects) listAndHeal(bucket, prefix string, scanMode madmin.Heal
|
||||
partial: func(entries metaCacheEntries, _ []error) {
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if !ok {
|
||||
// check if we can get one entry atleast
|
||||
// check if we can get one entry at least
|
||||
// proceed to heal nonetheless.
|
||||
entry, _ = entries.firstFound()
|
||||
}
|
||||
@@ -311,7 +286,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
// used here for reconstruction. This is done to ensure that
|
||||
// we do not skip drives that have inconsistent metadata to be
|
||||
// skipped from purging when they are stale.
|
||||
availableDisks, dataErrs, diskMTime := disksWithAllParts(ctx, onlineDisks, partsMetadata,
|
||||
availableDisks, dataErrs, _ := disksWithAllParts(ctx, onlineDisks, partsMetadata,
|
||||
errs, latestMeta, bucket, object, scanMode)
|
||||
|
||||
var erasure Erasure
|
||||
@@ -614,7 +589,10 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
// - Remove any remaining parts from outdated disks from before transition.
|
||||
if recreate || partsMetadata[i].IsRemote() {
|
||||
rmDataDir := partsMetadata[i].DataDir
|
||||
disk.DeleteVol(ctx, pathJoin(bucket, encodeDirObject(object), rmDataDir), true)
|
||||
disk.Delete(ctx, bucket, pathJoin(encodeDirObject(object), rmDataDir), DeleteOptions{
|
||||
Immediate: true,
|
||||
Recursive: true,
|
||||
})
|
||||
}
|
||||
|
||||
for i, v := range result.Before.Drives {
|
||||
@@ -624,20 +602,6 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
}
|
||||
}
|
||||
|
||||
if !diskMTime.Equal(timeSentinel) && !diskMTime.IsZero() {
|
||||
// Update metadata to indicate special fix.
|
||||
_, err = er.PutObjectMetadata(ctx, bucket, object, ObjectOptions{
|
||||
NoLock: true,
|
||||
UserDefined: map[string]string{
|
||||
reservedMetadataPrefixLowerDataShardFix: "true",
|
||||
// another reserved metadata to capture original disk-mtime
|
||||
// captured for this version of the object, to be used
|
||||
// possibly in future to heal other versions if possible.
|
||||
ReservedMetadataPrefixLower + "disk-mtime": diskMTime.String(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -916,21 +880,25 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
// or when xl.meta is not readable in read quorum disks.
|
||||
danglingErrsCount := func(cerrs []error) (int, int, int) {
|
||||
var (
|
||||
notFoundCount int
|
||||
corruptedCount int
|
||||
diskNotFoundCount int
|
||||
notFoundCount int
|
||||
corruptedCount int
|
||||
driveNotFoundCount int
|
||||
)
|
||||
for _, readErr := range cerrs {
|
||||
if readErr == nil {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound):
|
||||
notFoundCount++
|
||||
case errors.Is(readErr, errFileCorrupt):
|
||||
corruptedCount++
|
||||
case errors.Is(readErr, errDiskNotFound):
|
||||
diskNotFoundCount++
|
||||
default:
|
||||
// All other errors are non-actionable
|
||||
driveNotFoundCount++
|
||||
}
|
||||
}
|
||||
return notFoundCount, corruptedCount, diskNotFoundCount
|
||||
return notFoundCount, corruptedCount, driveNotFoundCount
|
||||
}
|
||||
|
||||
ndataErrs := make([]error, len(dataErrs))
|
||||
|
||||
@@ -1067,7 +1067,7 @@ func TestHealObjectCorruptedPools(t *testing.T) {
|
||||
for i := 0; i < (nfi.Erasure.DataBlocks + nfi.Erasure.ParityBlocks); i++ {
|
||||
stats, _ := erasureDisks[i].StatInfoFile(context.Background(), bucket, pathJoin(object, xlStorageFormatFile), false)
|
||||
if len(stats) != 0 {
|
||||
t.Errorf("Expected xl.meta file to be not present, but succeeeded")
|
||||
t.Errorf("Expected xl.meta file to be not present, but succeeded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ func reduceErrs(errs []error, ignoredErrs []error) (maxCount int, maxErr error)
|
||||
if IsErrIgnored(err, ignoredErrs...) {
|
||||
continue
|
||||
}
|
||||
// Errors due to context cancelation may be wrapped - group them by context.Canceled.
|
||||
// Errors due to context cancellation may be wrapped - group them by context.Canceled.
|
||||
if errors.Is(err, context.Canceled) {
|
||||
errorCounts[context.Canceled]++
|
||||
continue
|
||||
|
||||
@@ -94,7 +94,7 @@ func TestReduceErrs(t *testing.T) {
|
||||
},
|
||||
nil, nil,
|
||||
},
|
||||
// Checks if wrapped context cancelation errors are grouped as one.
|
||||
// Checks if wrapped context cancellation errors are grouped as one.
|
||||
{canceledErrs, nil, context.Canceled},
|
||||
}
|
||||
// Validates list of all the testcases for returning valid errors.
|
||||
|
||||
+1
-12
@@ -374,17 +374,6 @@ func findFileInfoInQuorum(ctx context.Context, metaArr []FileInfo, modTime time.
|
||||
return FileInfo{}, errErasureReadQuorum
|
||||
}
|
||||
|
||||
func pickValidDiskTimeWithQuorum(metaArr []FileInfo, quorum int) time.Time {
|
||||
diskMTimes := listObjectDiskMtimes(metaArr)
|
||||
|
||||
diskMTime, diskMaxima := commonTimeAndOccurence(diskMTimes, shardDiskTimeDelta)
|
||||
if diskMaxima >= quorum {
|
||||
return diskMTime
|
||||
}
|
||||
|
||||
return timeSentinel
|
||||
}
|
||||
|
||||
// pickValidFileInfo - picks one valid FileInfo content and returns from a
|
||||
// slice of FileInfo.
|
||||
func pickValidFileInfo(ctx context.Context, metaArr []FileInfo, modTime time.Time, etag string, quorum int) (FileInfo, error) {
|
||||
@@ -483,7 +472,7 @@ func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int)
|
||||
// readQuorum is the min required disks to read data.
|
||||
// writeQuorum is the min required disks to write data.
|
||||
func objectQuorumFromMeta(ctx context.Context, partsMetaData []FileInfo, errs []error, defaultParityCount int) (objectReadQuorum, objectWriteQuorum int, err error) {
|
||||
// There should be atleast half correct entries, if not return failure
|
||||
// There should be at least half correct entries, if not return failure
|
||||
expectedRQuorum := len(partsMetaData) / 2
|
||||
if defaultParityCount == 0 {
|
||||
// if parity count is '0', we expected all entries to be present.
|
||||
|
||||
@@ -103,7 +103,7 @@ func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object
|
||||
return fi, partsMetadata, err
|
||||
}
|
||||
|
||||
// Removes part.meta given by partName belonging to a mulitpart upload from minioMetaBucket
|
||||
// Removes part.meta given by partName belonging to a multipart upload from minioMetaBucket
|
||||
func (er erasureObjects) removePartMeta(bucket, object, uploadID, dataDir string, partNumber int) {
|
||||
uploadIDPath := er.getUploadIDDir(bucket, object, uploadID)
|
||||
curpartPath := pathJoin(uploadIDPath, dataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
@@ -127,7 +127,7 @@ func (er erasureObjects) removePartMeta(bucket, object, uploadID, dataDir string
|
||||
g.Wait()
|
||||
}
|
||||
|
||||
// Removes part given by partName belonging to a mulitpart upload from minioMetaBucket
|
||||
// Removes part given by partName belonging to a multipart upload from minioMetaBucket
|
||||
func (er erasureObjects) removeObjectPart(bucket, object, uploadID, dataDir string, partNumber int) {
|
||||
uploadIDPath := er.getUploadIDDir(bucket, object, uploadID)
|
||||
curpartPath := pathJoin(uploadIDPath, dataDir, fmt.Sprintf("part.%d", partNumber))
|
||||
@@ -656,7 +656,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
||||
var buffer []byte
|
||||
switch size := data.Size(); {
|
||||
case size == 0:
|
||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||
buffer = make([]byte, 1) // Allocate at least a byte to reach EOF
|
||||
case size == -1:
|
||||
if size := data.ActualSize(); size > 0 && size < fi.Erasure.BlockSize {
|
||||
// Account for padding and forced compression overhead and encryption.
|
||||
|
||||
+50
-58
@@ -227,30 +227,6 @@ func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object stri
|
||||
return nil, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
if !fi.DataShardFixed() {
|
||||
diskMTime := pickValidDiskTimeWithQuorum(metaArr, fi.Erasure.DataBlocks)
|
||||
if !diskMTime.Equal(timeSentinel) && !diskMTime.IsZero() {
|
||||
for index := range onlineDisks {
|
||||
if onlineDisks[index] == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
if !metaArr[index].IsValid() {
|
||||
continue
|
||||
}
|
||||
if !metaArr[index].AcceptableDelta(diskMTime, shardDiskTimeDelta) {
|
||||
// If disk mTime mismatches it is considered outdated
|
||||
// https://github.com/minio/minio/pull/13803
|
||||
//
|
||||
// This check only is active if we could find maximally
|
||||
// occurring disk mtimes that are somewhat same across
|
||||
// the quorum. Allowing to skip those shards which we
|
||||
// might think are wrong.
|
||||
onlineDisks[index] = OfflineDisk
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
objInfo := fi.ToObjectInfo(bucket, object, opts.Versioned || opts.VersionSuspended)
|
||||
if objInfo.DeleteMarker {
|
||||
if opts.VersionID == "" {
|
||||
@@ -511,7 +487,7 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
||||
// count the number of offline disks
|
||||
offline := 0
|
||||
for i := 0; i < max(len(errs), len(dataErrs)); i++ {
|
||||
if i < len(errs) && errs[i] == errDiskNotFound || i < len(dataErrs) && dataErrs[i] == errDiskNotFound {
|
||||
if i < len(errs) && errors.Is(errs[i], errDiskNotFound) || i < len(dataErrs) && errors.Is(dataErrs[i], errDiskNotFound) {
|
||||
offline++
|
||||
}
|
||||
}
|
||||
@@ -768,8 +744,6 @@ func readAllXL(ctx context.Context, disks []StorageAPI, bucket, object string, r
|
||||
}
|
||||
|
||||
func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object string, opts ObjectOptions, readData bool) (FileInfo, []FileInfo, []StorageAPI, error) {
|
||||
var mu sync.Mutex
|
||||
|
||||
rawArr := make([]RawFileInfo, er.setDriveCount)
|
||||
metaArr := make([]FileInfo, er.setDriveCount)
|
||||
errs := make([]error, er.setDriveCount)
|
||||
@@ -780,14 +754,16 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
done := make(chan bool, er.setDriveCount)
|
||||
disks := er.getDisks()
|
||||
|
||||
mrfCheck := make(chan FileInfo)
|
||||
defer close(mrfCheck)
|
||||
|
||||
ropts := ReadOptions{
|
||||
ReadData: readData,
|
||||
Healing: false,
|
||||
}
|
||||
|
||||
mrfCheck := make(chan FileInfo)
|
||||
defer xioutil.SafeClose(mrfCheck)
|
||||
|
||||
var rw sync.Mutex
|
||||
|
||||
// Ask for all disks first;
|
||||
go func() {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
@@ -799,56 +775,65 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
done <- false
|
||||
continue
|
||||
}
|
||||
if !disk.IsOnline() {
|
||||
done <- false
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int, disk StorageAPI) {
|
||||
defer wg.Done()
|
||||
var fi FileInfo
|
||||
var err error
|
||||
|
||||
var (
|
||||
fi FileInfo
|
||||
rfi RawFileInfo
|
||||
err error
|
||||
)
|
||||
|
||||
if opts.VersionID != "" {
|
||||
// Read a specific version ID
|
||||
fi, err = readFileInfo(ctx, disk, bucket, object, opts.VersionID, ropts)
|
||||
mu.Lock()
|
||||
metaArr[i], errs[i] = fi, err
|
||||
mu.Unlock()
|
||||
} else {
|
||||
// Read the latest version
|
||||
var ri RawFileInfo
|
||||
ri, err = readRawFileInfo(ctx, disk, bucket, object, readData)
|
||||
mu.Lock()
|
||||
rawArr[i], errs[i] = ri, err
|
||||
mu.Unlock()
|
||||
rfi, err = readRawFileInfo(ctx, disk, bucket, object, readData)
|
||||
if err == nil {
|
||||
fi, err = fileInfoFromRaw(ri, bucket, object, readData, opts.InclFreeVersions, true)
|
||||
mu.Lock()
|
||||
metaArr[i], errs[i] = fi, err
|
||||
mu.Unlock()
|
||||
fi, err = fileInfoFromRaw(rfi, bucket, object, readData, opts.InclFreeVersions, true)
|
||||
}
|
||||
}
|
||||
|
||||
rw.Lock()
|
||||
rawArr[i] = rfi
|
||||
metaArr[i], errs[i] = fi, err
|
||||
rw.Unlock()
|
||||
|
||||
done <- err == nil
|
||||
}(i, disk)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(done)
|
||||
xioutil.SafeClose(done)
|
||||
|
||||
fi, ok := <-mrfCheck
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if fi.Deleted {
|
||||
return
|
||||
}
|
||||
|
||||
// if one of the disk is offline, return right here no need
|
||||
// to attempt a heal on the object.
|
||||
if countErrs(errs, errDiskNotFound) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var missingBlocks int
|
||||
for i := range errs {
|
||||
if errors.Is(errs[i], errFileNotFound) {
|
||||
missingBlocks++
|
||||
}
|
||||
}
|
||||
|
||||
// if missing metadata can be reconstructed, attempt to reconstruct.
|
||||
// additionally do not heal delete markers inline, let them be
|
||||
// healed upon regular heal process.
|
||||
@@ -879,13 +864,12 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
minDisks = er.setDriveCount - er.defaultParityCount
|
||||
}
|
||||
|
||||
calcQuorum := func() (FileInfo, []FileInfo, []StorageAPI, time.Time, string, error) {
|
||||
calcQuorum := func(metaArr []FileInfo, errs []error) (FileInfo, []FileInfo, []StorageAPI, time.Time, string, error) {
|
||||
readQuorum, _, err := objectQuorumFromMeta(ctx, metaArr, errs, er.defaultParityCount)
|
||||
if err != nil {
|
||||
return FileInfo{}, nil, nil, time.Time{}, "", err
|
||||
}
|
||||
err = reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum)
|
||||
if err != nil {
|
||||
if err := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); err != nil {
|
||||
return FileInfo{}, nil, nil, time.Time{}, "", err
|
||||
}
|
||||
onlineDisks, modTime, etag := listOnlineDisks(disks, metaArr, errs, readQuorum)
|
||||
@@ -895,7 +879,11 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
}
|
||||
|
||||
onlineMeta := make([]FileInfo, len(metaArr))
|
||||
copy(onlineMeta, metaArr)
|
||||
for i, disk := range onlineDisks {
|
||||
if disk != nil {
|
||||
onlineMeta[i] = metaArr[i]
|
||||
}
|
||||
}
|
||||
|
||||
return fi, onlineMeta, onlineDisks, modTime, etag, nil
|
||||
}
|
||||
@@ -922,20 +910,24 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
rw.Lock()
|
||||
if opts.VersionID == "" && totalResp == er.setDriveCount {
|
||||
// Disks cannot agree about the latest version, pass this to a more advanced code
|
||||
metaArr, errs = pickLatestQuorumFilesInfo(ctx, rawArr, errs, bucket, object, readData, opts.InclFreeVersions, true)
|
||||
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum(pickLatestQuorumFilesInfo(ctx,
|
||||
rawArr, errs, bucket, object, readData, opts.InclFreeVersions, true))
|
||||
} else {
|
||||
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum(metaArr, errs)
|
||||
}
|
||||
mu.Lock()
|
||||
fi, onlineMeta, onlineDisks, modTime, etag, err = calcQuorum()
|
||||
mu.Unlock()
|
||||
rw.Unlock()
|
||||
if err == nil && fi.InlineData() {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if shouldCheckForDangling(err, errs, bucket) {
|
||||
// We can only look for dangling if we received all the responses, if we did
|
||||
// not we simply ignore it, since we can't tell for sure if its dangling object.
|
||||
if totalResp == er.setDriveCount && shouldCheckForDangling(err, errs, bucket) {
|
||||
_, derr := er.deleteIfDangling(context.Background(), bucket, object, metaArr, errs, nil, opts)
|
||||
if derr != nil {
|
||||
err = derr
|
||||
@@ -955,7 +947,7 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
for i := range onlineMeta {
|
||||
// verify metadata is valid, it has similar erasure info
|
||||
// as well as common modtime, if modtime is not possible
|
||||
// verify if it has common "etag" atleast.
|
||||
// verify if it has common "etag" at least.
|
||||
if onlineMeta[i].IsValid() && onlineMeta[i].Erasure.Equal(fi.Erasure) {
|
||||
ok := onlineMeta[i].ModTime.Equal(modTime)
|
||||
if modTime.IsZero() || modTime.Equal(timeSentinel) {
|
||||
@@ -1150,7 +1142,7 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
|
||||
var buffer []byte
|
||||
switch size := data.Size(); {
|
||||
case size == 0:
|
||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||
buffer = make([]byte, 1) // Allocate at least a byte to reach EOF
|
||||
case size >= fi.Erasure.BlockSize:
|
||||
buffer = globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(buffer)
|
||||
@@ -1401,7 +1393,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
var buffer []byte
|
||||
switch size := data.Size(); {
|
||||
case size == 0:
|
||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||
buffer = make([]byte, 1) // Allocate at least a byte to reach EOF
|
||||
case size >= fi.Erasure.BlockSize || size == -1:
|
||||
buffer = globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(buffer)
|
||||
|
||||
@@ -799,7 +799,7 @@ func TestPutObjectNoQuorumSmall(t *testing.T) {
|
||||
}
|
||||
|
||||
// Test PutObject twice, one small and another bigger
|
||||
// than small data thresold and checks reading them again
|
||||
// than small data threshold and checks reading them again
|
||||
func TestPutObjectSmallInlineData(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -936,7 +936,8 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
|
||||
bi.Name,
|
||||
encodeDirObject(entry.name),
|
||||
ObjectOptions{
|
||||
DeletePrefix: true, // use prefix delete to delete all versions at once.
|
||||
DeletePrefix: true, // use prefix delete to delete all versions at once.
|
||||
DeletePrefixObject: true, // use prefix delete on exact object (this is an optimization to avoid fan-out calls)
|
||||
},
|
||||
)
|
||||
stopFn(err)
|
||||
|
||||
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/lithammer/shortuuid/v4"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
)
|
||||
@@ -114,12 +115,60 @@ func (z *erasureServerPools) loadRebalanceMeta(ctx context.Context) error {
|
||||
}
|
||||
|
||||
z.rebalMu.Lock()
|
||||
z.rebalMeta = r
|
||||
if len(r.PoolStats) == len(z.serverPools) {
|
||||
z.rebalMeta = r
|
||||
} else {
|
||||
z.updateRebalanceStats(ctx)
|
||||
}
|
||||
z.rebalMu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates rebalance.bin from let's say 2 pool setup in the middle
|
||||
// of a rebalance, was expanded can cause z.rebalMeta to be outdated
|
||||
// due to a missing new pool. This function tries to handle this
|
||||
// scenario, albeit rare it seems to have occurred in the wild.
|
||||
//
|
||||
// since we do not explicitly disallow it, but it is okay for them
|
||||
// expand and then we continue to rebalance.
|
||||
func (z *erasureServerPools) updateRebalanceStats(ctx context.Context) error {
|
||||
var ok bool
|
||||
for i := range z.serverPools {
|
||||
if z.findIndex(i) == -1 {
|
||||
// Also ensure to initialize rebalanceStats to indicate
|
||||
// its a new pool that can receive rebalanced data.
|
||||
z.rebalMeta.PoolStats = append(z.rebalMeta.PoolStats, &rebalanceStats{})
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
lock := z.serverPools[0].NewNSLock(minioMetaBucket, rebalMetaName)
|
||||
lkCtx, err := lock.GetLock(ctx, globalOperationTimeout)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("failed to acquire write lock on %s/%s: %w", minioMetaBucket, rebalMetaName, err))
|
||||
return err
|
||||
}
|
||||
defer lock.Unlock(lkCtx)
|
||||
|
||||
ctx = lkCtx.Context()
|
||||
|
||||
noLockOpts := ObjectOptions{NoLock: true}
|
||||
return z.rebalMeta.saveWithOpts(ctx, z.serverPools[0], noLockOpts)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) findIndex(index int) int {
|
||||
for i := 0; i < len(z.rebalMeta.PoolStats); i++ {
|
||||
if i == index {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// initRebalanceMeta initializes rebalance metadata for a new rebalance
|
||||
// operation and saves it in the object store.
|
||||
func (z *erasureServerPools) initRebalanceMeta(ctx context.Context, buckets []string) (arn string, err error) {
|
||||
@@ -324,7 +373,7 @@ func (z *erasureServerPools) IsPoolRebalancing(poolIndex int) bool {
|
||||
|
||||
func (z *erasureServerPools) rebalanceBuckets(ctx context.Context, poolIdx int) (err error) {
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
defer xioutil.SafeClose(doneCh)
|
||||
|
||||
// Save rebalance.bin periodically.
|
||||
go func() {
|
||||
@@ -510,7 +559,6 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
|
||||
|
||||
// Apply lifecycle rules on the objects that are expired.
|
||||
if filterLifecycle(bucket, version.Name, version) {
|
||||
rebalanced++
|
||||
expired++
|
||||
continue
|
||||
}
|
||||
@@ -609,7 +657,8 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
|
||||
bucket,
|
||||
encodeDirObject(entry.name),
|
||||
ObjectOptions{
|
||||
DeletePrefix: true, // use prefix delete to delete all versions at once.
|
||||
DeletePrefix: true, // use prefix delete to delete all versions at once.
|
||||
DeletePrefixObject: true, // use prefix delete on exact object (this is an optimization to avoid fan-out calls)
|
||||
},
|
||||
)
|
||||
stopFn(err)
|
||||
@@ -695,8 +744,7 @@ func (z *erasureServerPools) saveRebalanceStats(ctx context.Context, poolIdx int
|
||||
}
|
||||
z.rebalMeta = r
|
||||
|
||||
err = z.rebalMeta.saveWithOpts(ctx, z.serverPools[0], noLockOpts)
|
||||
return err
|
||||
return z.rebalMeta.saveWithOpts(ctx, z.serverPools[0], noLockOpts)
|
||||
}
|
||||
|
||||
func auditLogRebalance(ctx context.Context, apiName, bucket, object, versionID string, err error) {
|
||||
|
||||
+33
-83
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/bpool"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
"github.com/minio/pkg/v2/wildcard"
|
||||
@@ -90,13 +91,14 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
n = 2048
|
||||
}
|
||||
|
||||
if globalIsCICD {
|
||||
n = 256 // 256MiB for CI/CD environments is sufficient
|
||||
}
|
||||
|
||||
// Initialize byte pool once for all sets, bpool size is set to
|
||||
// setCount * setDriveCount with each memory upto blockSizeV2.
|
||||
globalBytePoolCap = bpool.NewBytePoolCap(n, blockSizeV2, blockSizeV2*2)
|
||||
|
||||
if globalServerCtxt.PreAllocate {
|
||||
globalBytePoolCap.Populate()
|
||||
}
|
||||
globalBytePoolCap.Populate()
|
||||
|
||||
var localDrives []StorageAPI
|
||||
local := endpointServerPools.FirstLocal()
|
||||
@@ -117,8 +119,10 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
return nil, fmt.Errorf("parity validation returned an error: %w <- (%d, %d), for pool(%s)", err, commonParityDrives, ep.DrivesPerSet, humanize.Ordinal(i+1))
|
||||
}
|
||||
|
||||
storageDisks[i], formats[i], err = waitForFormatErasure(local, ep.Endpoints, i+1,
|
||||
ep.SetCount, ep.DrivesPerSet, deploymentID, distributionAlgo)
|
||||
bootstrapTrace("waitForFormatErasure: loading disks", func() {
|
||||
storageDisks[i], formats[i], err = waitForFormatErasure(local, ep.Endpoints, i+1,
|
||||
ep.SetCount, ep.DrivesPerSet, deploymentID, distributionAlgo)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -137,7 +141,9 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
return nil, fmt.Errorf("all pools must have same deployment ID - expected %s, got %s for pool(%s)", deploymentID, formats[i].ID, humanize.Ordinal(i+1))
|
||||
}
|
||||
|
||||
z.serverPools[i], err = newErasureSets(ctx, ep, storageDisks[i], formats[i], commonParityDrives, i)
|
||||
bootstrapTrace(fmt.Sprintf("newErasureSets: initializing %s pool", humanize.Ordinal(i+1)), func() {
|
||||
z.serverPools[i], err = newErasureSets(ctx, ep, storageDisks[i], formats[i], commonParityDrives, i)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -175,8 +181,12 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
setObjectLayer(z)
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
attempt := 1
|
||||
for {
|
||||
err := z.Init(ctx) // Initializes all pools.
|
||||
var err error
|
||||
bootstrapTrace(fmt.Sprintf("poolMeta.Init: loading pool metadata, attempt: %d", attempt), func() {
|
||||
err = z.Init(ctx) // Initializes all pools.
|
||||
})
|
||||
if err != nil {
|
||||
if !configRetriableErrors(err) {
|
||||
logger.Fatal(err, "Unable to initialize backend")
|
||||
@@ -184,6 +194,7 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
retry := time.Duration(r.Float64() * float64(5*time.Second))
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize backend: %w, retrying in %s", err, retry))
|
||||
time.Sleep(retry)
|
||||
attempt++
|
||||
continue
|
||||
}
|
||||
break
|
||||
@@ -643,7 +654,7 @@ func (z *erasureServerPools) StorageInfo(ctx context.Context, metrics bool) Stor
|
||||
|
||||
func (z *erasureServerPools) NSScanner(ctx context.Context, updates chan<- DataUsageInfo, wantCycle uint32, healScanMode madmin.HealScanMode) error {
|
||||
// Updates must be closed before we return.
|
||||
defer close(updates)
|
||||
defer xioutil.SafeClose(updates)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
@@ -670,7 +681,7 @@ func (z *erasureServerPools) NSScanner(ctx context.Context, updates chan<- DataU
|
||||
results = append(results, dataUsageCache{})
|
||||
go func(i int, erObj *erasureObjects) {
|
||||
updates := make(chan dataUsageCache, 1)
|
||||
defer close(updates)
|
||||
defer xioutil.SafeClose(updates)
|
||||
// Start update collector.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -729,7 +740,7 @@ func (z *erasureServerPools) NSScanner(ctx context.Context, updates chan<- DataU
|
||||
return
|
||||
case v := <-updateCloser:
|
||||
update()
|
||||
close(v)
|
||||
xioutil.SafeClose(v)
|
||||
return
|
||||
case <-updateTicker.C:
|
||||
update()
|
||||
@@ -1931,76 +1942,12 @@ func (z *erasureServerPools) HealFormat(ctx context.Context, dryRun bool) (madmi
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||
r := madmin.HealResultItem{
|
||||
Type: madmin.HealItemBucket,
|
||||
Bucket: bucket,
|
||||
}
|
||||
|
||||
// Attempt heal on the bucket metadata, ignore any failures
|
||||
hopts := opts
|
||||
hopts.Recreate = false
|
||||
defer z.HealObject(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, bucketMetadataFile), "", hopts)
|
||||
|
||||
type DiskStat struct {
|
||||
VolInfos []VolInfo
|
||||
Errs []error
|
||||
}
|
||||
|
||||
for _, pool := range z.serverPools {
|
||||
// map of node wise disk stats
|
||||
diskStats := make(map[string]DiskStat)
|
||||
for _, set := range pool.sets {
|
||||
for _, disk := range set.getDisks() {
|
||||
if disk == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
vi, err := disk.StatVol(ctx, bucket)
|
||||
hostName := disk.Hostname()
|
||||
if disk.IsLocal() {
|
||||
hostName = "local"
|
||||
}
|
||||
ds, ok := diskStats[hostName]
|
||||
if !ok {
|
||||
newds := DiskStat{
|
||||
VolInfos: []VolInfo{vi},
|
||||
Errs: []error{err},
|
||||
}
|
||||
diskStats[hostName] = newds
|
||||
} else {
|
||||
ds.VolInfos = append(ds.VolInfos, vi)
|
||||
ds.Errs = append(ds.Errs, err)
|
||||
diskStats[hostName] = ds
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeCount := len(diskStats)
|
||||
bktNotFoundCount := 0
|
||||
for _, ds := range diskStats {
|
||||
if isAllBucketsNotFound(ds.Errs) {
|
||||
bktNotFoundCount++
|
||||
}
|
||||
}
|
||||
// if the bucket if not found on more than hslf the no of nodes, its dangling
|
||||
if bktNotFoundCount > nodeCount/2 {
|
||||
opts.Remove = true
|
||||
} else {
|
||||
opts.Recreate = true
|
||||
}
|
||||
|
||||
result, err := z.s3Peer.HealBucket(ctx, bucket, opts)
|
||||
if err != nil {
|
||||
if _, ok := err.(BucketNotFound); ok {
|
||||
continue
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
r.DiskCount += result.DiskCount
|
||||
r.SetCount += result.SetCount
|
||||
r.Before.Drives = append(r.Before.Drives, result.Before.Drives...)
|
||||
r.After.Drives = append(r.After.Drives, result.After.Drives...)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
return z.s3Peer.HealBucket(ctx, bucket, opts)
|
||||
}
|
||||
|
||||
// Walk a bucket, optionally prefix recursively, until we have returned
|
||||
@@ -2011,7 +1958,7 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts
|
||||
func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, results chan<- ObjectInfo, opts WalkOptions) error {
|
||||
if err := checkListObjsArgs(ctx, bucket, prefix, "", z); err != nil {
|
||||
// Upon error close the channel.
|
||||
close(results)
|
||||
xioutil.SafeClose(results)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2020,7 +1967,7 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
go func() {
|
||||
defer cancel()
|
||||
defer close(results)
|
||||
defer xioutil.SafeClose(results)
|
||||
|
||||
for _, erasureSet := range z.serverPools {
|
||||
var wg sync.WaitGroup
|
||||
@@ -2324,7 +2271,7 @@ const (
|
||||
vmware = "VMWare"
|
||||
)
|
||||
|
||||
// HealthOptions takes input options to return sepcific information
|
||||
// HealthOptions takes input options to return specific information
|
||||
type HealthOptions struct {
|
||||
Maintenance bool
|
||||
DeploymentType string
|
||||
@@ -2339,6 +2286,7 @@ type HealthResult struct {
|
||||
ESHealth []struct {
|
||||
Maintenance bool
|
||||
PoolID, SetID int
|
||||
Healthy bool
|
||||
HealthyDrives int
|
||||
HealingDrives int
|
||||
ReadQuorum int
|
||||
@@ -2462,23 +2410,25 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
result.ESHealth = append(result.ESHealth, struct {
|
||||
Maintenance bool
|
||||
PoolID, SetID int
|
||||
Healthy bool
|
||||
HealthyDrives, HealingDrives int
|
||||
ReadQuorum, WriteQuorum int
|
||||
}{
|
||||
Maintenance: opts.Maintenance,
|
||||
SetID: setIdx,
|
||||
PoolID: poolIdx,
|
||||
Healthy: erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx],
|
||||
HealthyDrives: erasureSetUpCount[poolIdx][setIdx].online,
|
||||
HealingDrives: erasureSetUpCount[poolIdx][setIdx].healing,
|
||||
ReadQuorum: poolReadQuorums[poolIdx],
|
||||
WriteQuorum: poolWriteQuorums[poolIdx],
|
||||
})
|
||||
|
||||
if erasureSetUpCount[poolIdx][setIdx].online < poolWriteQuorums[poolIdx] {
|
||||
result.Healthy = erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx]
|
||||
if !result.Healthy {
|
||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||
fmt.Errorf("Write quorum may be lost on pool: %d, set: %d, expected write quorum: %d",
|
||||
poolIdx, setIdx, poolWriteQuorums[poolIdx]))
|
||||
result.Healthy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2499,7 +2449,7 @@ func (z *erasureServerPools) PutObjectMetadata(ctx context.Context, bucket, obje
|
||||
}
|
||||
|
||||
opts.MetadataChg = true
|
||||
// We don't know the size here set 1GiB atleast.
|
||||
// We don't know the size here set 1GiB at least.
|
||||
idx, err := z.getPoolIdxExistingWithOpts(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
@@ -2517,7 +2467,7 @@ func (z *erasureServerPools) PutObjectTags(ctx context.Context, bucket, object s
|
||||
|
||||
opts.MetadataChg = true
|
||||
|
||||
// We don't know the size here set 1GiB atleast.
|
||||
// We don't know the size here set 1GiB at least.
|
||||
idx, err := z.getPoolIdxExistingWithOpts(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
|
||||
+36
-67
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
@@ -114,26 +115,26 @@ func (s *erasureSets) getDiskMap() map[Endpoint]StorageAPI {
|
||||
|
||||
// Initializes a new StorageAPI from the endpoint argument, returns
|
||||
// StorageAPI and also `format` which exists on the disk.
|
||||
func connectEndpoint(endpoint Endpoint) (StorageAPI, *formatErasureV3, error) {
|
||||
func connectEndpoint(endpoint Endpoint) (StorageAPI, *formatErasureV3, []byte, error) {
|
||||
disk, err := newStorageAPI(endpoint, storageOpts{
|
||||
cleanUp: false,
|
||||
healthCheck: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
format, err := loadFormatErasure(disk)
|
||||
format, formatData, err := loadFormatErasureWithData(disk, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, errUnformattedDisk) {
|
||||
info, derr := disk.DiskInfo(context.TODO(), false)
|
||||
info, derr := disk.DiskInfo(context.TODO(), DiskInfoOptions{})
|
||||
if derr != nil && info.RootDisk {
|
||||
disk.Close()
|
||||
return nil, nil, fmt.Errorf("Drive: %s is a root drive", disk)
|
||||
return nil, nil, nil, fmt.Errorf("Drive: %s is a root drive", disk)
|
||||
}
|
||||
}
|
||||
disk.Close()
|
||||
return nil, nil, fmt.Errorf("Drive: %s returned %w", disk, err) // make sure to '%w' to wrap the error
|
||||
return nil, nil, nil, fmt.Errorf("Drive: %s returned %w", disk, err) // make sure to '%w' to wrap the error
|
||||
}
|
||||
|
||||
disk.Close()
|
||||
@@ -142,10 +143,10 @@ func connectEndpoint(endpoint Endpoint) (StorageAPI, *formatErasureV3, error) {
|
||||
healthCheck: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return disk, format, nil
|
||||
return disk, format, formatData, nil
|
||||
}
|
||||
|
||||
// findDiskIndex - returns the i,j'th position of the input `diskID` against the reference
|
||||
@@ -226,7 +227,7 @@ func (s *erasureSets) connectDisks() {
|
||||
wg.Add(1)
|
||||
go func(endpoint Endpoint) {
|
||||
defer wg.Done()
|
||||
disk, format, err := connectEndpoint(endpoint)
|
||||
disk, format, formatData, err := connectEndpoint(endpoint)
|
||||
if err != nil {
|
||||
if endpoint.IsLocal && errors.Is(err, errUnformattedDisk) {
|
||||
globalBackgroundHealState.pushHealLocalDisks(endpoint)
|
||||
@@ -261,6 +262,7 @@ func (s *erasureSets) connectDisks() {
|
||||
|
||||
disk.SetDiskID(format.Erasure.This)
|
||||
disk.SetDiskLoc(s.poolIndex, setIndex, diskIndex)
|
||||
disk.SetFormatData(formatData)
|
||||
s.erasureDisks[setIndex][diskIndex] = disk
|
||||
s.erasureDisksMu.Unlock()
|
||||
|
||||
@@ -383,19 +385,27 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
||||
}
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var lk sync.Mutex
|
||||
for i := 0; i < setCount; i++ {
|
||||
lockerEpSet := set.NewStringSet()
|
||||
for j := 0; j < setDriveCount; j++ {
|
||||
endpoint := endpoints.Endpoints[i*setDriveCount+j]
|
||||
// Only add lockers only one per endpoint and per erasure set.
|
||||
if locker, ok := erasureLockers[endpoint.Host]; ok && !lockerEpSet.Contains(endpoint.Host) {
|
||||
lockerEpSet.Add(endpoint.Host)
|
||||
s.erasureLockers[i] = append(s.erasureLockers[i], locker)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int, endpoint Endpoint) {
|
||||
defer wg.Done()
|
||||
|
||||
lk.Lock()
|
||||
// Only add lockers only one per endpoint and per erasure set.
|
||||
if locker, ok := erasureLockers[endpoint.Host]; ok && !lockerEpSet.Contains(endpoint.Host) {
|
||||
lockerEpSet.Add(endpoint.Host)
|
||||
s.erasureLockers[i] = append(s.erasureLockers[i], locker)
|
||||
}
|
||||
lk.Unlock()
|
||||
}(i, endpoints.Endpoints[i*setDriveCount+j])
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < setCount; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
@@ -658,10 +668,10 @@ func (s *erasureSets) Shutdown(ctx context.Context) error {
|
||||
select {
|
||||
case _, ok := <-s.setReconnectEvent:
|
||||
if ok {
|
||||
close(s.setReconnectEvent)
|
||||
xioutil.SafeClose(s.setReconnectEvent)
|
||||
}
|
||||
default:
|
||||
close(s.setReconnectEvent)
|
||||
xioutil.SafeClose(s.setReconnectEvent)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1020,52 +1030,10 @@ func formatsToDrivesInfo(endpoints Endpoints, formats []*formatErasureV3, sErrs
|
||||
return beforeDrives
|
||||
}
|
||||
|
||||
func getHealDiskInfos(storageDisks []StorageAPI, errs []error) ([]DiskInfo, []error) {
|
||||
infos := make([]DiskInfo, len(storageDisks))
|
||||
g := errgroup.WithNErrs(len(storageDisks))
|
||||
for index := range storageDisks {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if errs[index] != nil && errs[index] != errUnformattedDisk {
|
||||
return errs[index]
|
||||
}
|
||||
if storageDisks[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
var err error
|
||||
infos[index], err = storageDisks[index].DiskInfo(context.TODO(), false)
|
||||
return err
|
||||
}, index)
|
||||
}
|
||||
return infos, g.Wait()
|
||||
}
|
||||
|
||||
// Mark root disks as down so as not to heal them.
|
||||
func markRootDisksAsDown(storageDisks []StorageAPI, errs []error) {
|
||||
if globalIsCICD {
|
||||
// Do nothing
|
||||
return
|
||||
}
|
||||
infos, ierrs := getHealDiskInfos(storageDisks, errs)
|
||||
for i := range storageDisks {
|
||||
if ierrs[i] != nil && ierrs[i] != errUnformattedDisk {
|
||||
storageDisks[i] = nil
|
||||
continue
|
||||
}
|
||||
if storageDisks[i] != nil && infos[i].RootDisk {
|
||||
// We should not heal on root disk. i.e in a situation where the minio-administrator has unmounted a
|
||||
// defective drive we should not heal a path on the root disk.
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive `%s` is part of root drive, will not be used", storageDisks[i]))
|
||||
storageDisks[i].Close()
|
||||
storageDisks[i] = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HealFormat - heals missing `format.json` on fresh unformatted disks.
|
||||
func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.HealResultItem, err error) {
|
||||
storageDisks, _ := initStorageDisksWithErrors(s.endpoints.Endpoints, storageOpts{
|
||||
cleanUp: true,
|
||||
cleanUp: false,
|
||||
healthCheck: false,
|
||||
})
|
||||
|
||||
@@ -1080,9 +1048,6 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
||||
return madmin.HealResultItem{}, err
|
||||
}
|
||||
|
||||
// Mark all root disks down
|
||||
markRootDisksAsDown(storageDisks, sErrs)
|
||||
|
||||
refFormat, err := getFormatErasureInQuorum(formats)
|
||||
if err != nil {
|
||||
return res, err
|
||||
@@ -1111,6 +1076,13 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
||||
return res, errNoHealRequired
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(s.format, refFormat) {
|
||||
// Format is corrupted and unrecognized by the running instance.
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to heal the newly replaced drives due to format.json inconsistencies, please engage MinIO support for further assistance: %w",
|
||||
errCorruptedFormat))
|
||||
return res, errCorruptedFormat
|
||||
}
|
||||
|
||||
formatOpID := mustGetUUID()
|
||||
|
||||
// Initialize a new set of set formats which will be written to disk.
|
||||
@@ -1192,9 +1164,6 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
||||
}
|
||||
}
|
||||
|
||||
// Replace reference format with what was loaded from disks.
|
||||
s.format = refFormat
|
||||
|
||||
s.erasureDisksMu.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
|
||||
if write < int64(len(block)) {
|
||||
n, err := dst.Write(block[:write])
|
||||
if err != nil {
|
||||
// The writer will be closed incase of range queries, which will emit ErrClosedPipe.
|
||||
// The writer will be closed in case of range queries, which will emit ErrClosedPipe.
|
||||
// The reader pipe might be closed at ListObjects io.EOF ignore it.
|
||||
if err != io.ErrClosedPipe && err != io.EOF {
|
||||
logger.LogIf(ctx, err)
|
||||
@@ -99,7 +99,7 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
|
||||
// Copy the block.
|
||||
n, err := dst.Write(block)
|
||||
if err != nil {
|
||||
// The writer will be closed incase of range queries, which will emit ErrClosedPipe.
|
||||
// The writer will be closed in case of range queries, which will emit ErrClosedPipe.
|
||||
// The reader pipe might be closed at ListObjects io.EOF ignore it.
|
||||
if err != io.ErrClosedPipe && err != io.EOF {
|
||||
logger.LogIf(ctx, err)
|
||||
|
||||
+6
-4
@@ -178,7 +178,7 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint, metrics bool) (disks
|
||||
disksInfo[index] = di
|
||||
return nil
|
||||
}
|
||||
info, err := disks[index].DiskInfo(context.TODO(), metrics)
|
||||
info, err := disks[index].DiskInfo(context.TODO(), DiskInfoOptions{Metrics: metrics})
|
||||
di.DrivePath = info.MountPath
|
||||
di.TotalSpace = info.Total
|
||||
di.UsedSpace = info.Used
|
||||
@@ -203,6 +203,8 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint, metrics bool) (disks
|
||||
APICalls: make(map[string]uint64, len(info.Metrics.APICalls)),
|
||||
TotalErrorsAvailability: info.Metrics.TotalErrorsAvailability,
|
||||
TotalErrorsTimeout: info.Metrics.TotalErrorsTimeout,
|
||||
TotalTokens: info.Metrics.TotalTokens,
|
||||
TotalWaiting: info.Metrics.TotalWaiting,
|
||||
}
|
||||
for k, v := range info.Metrics.LastMinute {
|
||||
if v.N > 0 {
|
||||
@@ -288,7 +290,7 @@ func (er erasureObjects) getOnlineDisksWithHealingAndInfo(inclHealing bool) (new
|
||||
return
|
||||
}
|
||||
|
||||
di, err := disk.DiskInfo(context.Background(), false)
|
||||
di, err := disk.DiskInfo(context.Background(), DiskInfoOptions{})
|
||||
infos[i] = di
|
||||
if err != nil {
|
||||
// - Do not consume disks which are not reachable
|
||||
@@ -421,7 +423,7 @@ func (er erasureObjects) nsScanner(ctx context.Context, buckets []BucketInfo, wa
|
||||
bucketCh <- b
|
||||
}
|
||||
}
|
||||
close(bucketCh)
|
||||
xioutil.SafeClose(bucketCh)
|
||||
|
||||
bucketResults := make(chan dataUsageEntryInfo, len(disks))
|
||||
|
||||
@@ -558,7 +560,7 @@ func (er erasureObjects) nsScanner(ctx context.Context, buckets []BucketInfo, wa
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(bucketResults)
|
||||
xioutil.SafeClose(bucketResults)
|
||||
saverWg.Wait()
|
||||
|
||||
return nil
|
||||
|
||||
+52
-46
@@ -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
|
||||
//
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
@@ -325,20 +326,16 @@ func loadFormatErasureAll(storageDisks []StorageAPI, heal bool) ([]*formatErasur
|
||||
if storageDisks[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
format, err := loadFormatErasure(storageDisks[index])
|
||||
format, formatData, err := loadFormatErasureWithData(storageDisks[index], heal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := storageDisks[index].DiskInfo(context.Background(), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
format.Info = info
|
||||
formats[index] = format
|
||||
if !heal {
|
||||
// If no healing required, make the disks valid and
|
||||
// online.
|
||||
storageDisks[index].SetDiskID(format.Erasure.This)
|
||||
storageDisks[index].SetFormatData(formatData)
|
||||
}
|
||||
return nil
|
||||
}, index)
|
||||
@@ -353,14 +350,8 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3, healID string)
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
diskID := format.Erasure.This
|
||||
|
||||
if err := makeFormatErasureMetaVolumes(disk); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Marshal and write to disk.
|
||||
formatBytes, err := json.Marshal(format)
|
||||
formatData, err := json.Marshal(format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -374,7 +365,7 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3, healID string)
|
||||
})
|
||||
|
||||
// write to unique file.
|
||||
if err = disk.WriteAll(context.TODO(), minioMetaBucket, tmpFormat, formatBytes); err != nil {
|
||||
if err = disk.WriteAll(context.TODO(), minioMetaBucket, tmpFormat, formatData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -383,7 +374,8 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3, healID string)
|
||||
return err
|
||||
}
|
||||
|
||||
disk.SetDiskID(diskID)
|
||||
disk.SetDiskID(format.Erasure.This)
|
||||
disk.SetFormatData(formatData)
|
||||
if healID != "" {
|
||||
ctx := context.Background()
|
||||
ht := initHealingTracker(disk, healID)
|
||||
@@ -392,14 +384,38 @@ func saveFormatErasure(disk StorageAPI, format *formatErasureV3, healID string)
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadFormatErasureWithData - loads format.json from disk.
|
||||
func loadFormatErasureWithData(disk StorageAPI, heal bool) (format *formatErasureV3, data []byte, err error) {
|
||||
data, err = disk.ReadAll(context.TODO(), minioMetaBucket, formatConfigFile)
|
||||
if err != nil {
|
||||
// 'file not found' and 'volume not found' as
|
||||
// same. 'volume not found' usually means its a fresh disk.
|
||||
if errors.Is(err, errFileNotFound) || errors.Is(err, errVolumeNotFound) {
|
||||
return nil, nil, errUnformattedDisk
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Try to decode format json into formatConfigV1 struct.
|
||||
format = &formatErasureV3{}
|
||||
if err = json.Unmarshal(data, format); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if heal {
|
||||
info, err := disk.DiskInfo(context.Background(), DiskInfoOptions{NoOp: heal})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
format.Info = info
|
||||
}
|
||||
|
||||
// Success.
|
||||
return format, data, nil
|
||||
}
|
||||
|
||||
// loadFormatErasure - loads format.json from disk.
|
||||
func loadFormatErasure(disk StorageAPI) (format *formatErasureV3, err error) {
|
||||
// Ensure that the grid is online.
|
||||
if _, err := disk.DiskInfo(context.Background(), false); err != nil {
|
||||
if errors.Is(err, errDiskNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
buf, err := disk.ReadAll(context.TODO(), minioMetaBucket, formatConfigFile)
|
||||
if err != nil {
|
||||
// 'file not found' and 'volume not found' as
|
||||
@@ -410,6 +426,8 @@ func loadFormatErasure(disk StorageAPI) (format *formatErasureV3, err error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
// Try to decode format json into formatConfigV1 struct.
|
||||
format = &formatErasureV3{}
|
||||
if err = json.Unmarshal(buf, format); err != nil {
|
||||
@@ -486,9 +504,7 @@ func formatErasureGetDeploymentID(refFormat *formatErasureV3, formats []*formatE
|
||||
}
|
||||
|
||||
// formatErasureFixDeploymentID - Add deployment id if it is not present.
|
||||
func formatErasureFixDeploymentID(endpoints Endpoints, storageDisks []StorageAPI, refFormat *formatErasureV3) (err error) {
|
||||
// Attempt to load all `format.json` from all disks.
|
||||
formats, _ := loadFormatErasureAll(storageDisks, false)
|
||||
func formatErasureFixDeploymentID(endpoints Endpoints, storageDisks []StorageAPI, refFormat *formatErasureV3, formats []*formatErasureV3) (err error) {
|
||||
for index := range formats {
|
||||
// If the Erasure sets do not match, set those formats to nil,
|
||||
// We do not have to update the ID on those format.json file.
|
||||
@@ -787,15 +803,18 @@ func initFormatErasure(ctx context.Context, storageDisks []StorageAPI, setCount,
|
||||
logger.Info(" - Drive: %s", disk.String())
|
||||
}
|
||||
})
|
||||
logger.Info(color.Yellow("WARNING:")+" Host %v has more than %v drives of set. "+
|
||||
"A host failure will result in data becoming unavailable.", host, wantAtMost)
|
||||
var warning string
|
||||
if wantAtMost == 0 {
|
||||
warning = fmt.Sprintf("Host %v has all drives of set. ", host)
|
||||
} else {
|
||||
warning = fmt.Sprintf("Host %v has more than %v drives of set. ", host, wantAtMost)
|
||||
}
|
||||
logger.Info(color.Yellow("WARNING: ") + warning +
|
||||
"A host failure will result in data becoming unavailable.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all root disks down
|
||||
markRootDisksAsDown(storageDisks, sErrs)
|
||||
|
||||
// Save formats `format.json` across all disks.
|
||||
if err := saveFormatErasureAll(ctx, storageDisks, formats); err != nil {
|
||||
return nil, err
|
||||
@@ -814,21 +833,6 @@ func ecDrivesNoConfig(setDriveCount int) (int, error) {
|
||||
return sc.GetParityForSC(storageclass.STANDARD), nil
|
||||
}
|
||||
|
||||
// Make Erasure backend meta volumes.
|
||||
func makeFormatErasureMetaVolumes(disk StorageAPI) error {
|
||||
if disk == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
volumes := []string{
|
||||
minioMetaTmpDeletedBucket, // creates .minio.sys/tmp as well as .minio.sys/tmp/.trash
|
||||
minioMetaMultipartBucket, // creates .minio.sys/multipart
|
||||
dataUsageBucket, // creates .minio.sys/buckets
|
||||
minioConfigBucket, // creates .minio.sys/config
|
||||
}
|
||||
// Attempt to create MinIO internal buckets.
|
||||
return disk.MakeVolBulk(context.TODO(), volumes...)
|
||||
}
|
||||
|
||||
// Initialize a new set of set formats which will be written to all disks.
|
||||
func newHealFormatSets(refFormat *formatErasureV3, setCount, setDriveCount int, formats []*formatErasureV3, errs []error) ([][]*formatErasureV3, [][]DiskInfo) {
|
||||
newFormats := make([][]*formatErasureV3, setCount)
|
||||
@@ -852,7 +856,9 @@ func newHealFormatSets(refFormat *formatErasureV3, setCount, setDriveCount int,
|
||||
newFormats[i][j].Erasure.DistributionAlgo = refFormat.Erasure.DistributionAlgo
|
||||
}
|
||||
if format := formats[i*setDriveCount+j]; format != nil && (errs[i*setDriveCount+j] == nil) {
|
||||
currentDisksInfo[i][j] = format.Info
|
||||
if format.Info.Endpoint != "" {
|
||||
currentDisksInfo[i][j] = format.Info
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ func TestGetErasureID(t *testing.T) {
|
||||
|
||||
formats[2].ID = "bad-id"
|
||||
if _, err = formatErasureGetDeploymentID(quorumFormat, formats); !errors.Is(err, errCorruptedFormat) {
|
||||
t.Fatalf("Unexpect error %s", err)
|
||||
t.Fatalf("Unexpected error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
ftp "goftp.io/server/v2"
|
||||
)
|
||||
@@ -386,7 +387,7 @@ func (driver *ftpDriver) DeleteDir(ctx *ftp.Context, path string) (err error) {
|
||||
|
||||
// Send object names that are needed to be removed to objectsCh
|
||||
go func() {
|
||||
defer close(objectsCh)
|
||||
defer xioutil.SafeClose(objectsCh)
|
||||
opts := minio.ListObjectsOptions{
|
||||
Prefix: prefix,
|
||||
Recursive: true,
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@ func (log *minioLogger) Printf(sessionID string, format string, v ...interface{}
|
||||
}
|
||||
}
|
||||
|
||||
// PrintCommand impelment Logger
|
||||
// PrintCommand implement Logger
|
||||
func (log *minioLogger) PrintCommand(sessionID string, command string, params string) {
|
||||
if serverDebugLog {
|
||||
if command == "PASS" {
|
||||
@@ -61,7 +61,7 @@ func (log *minioLogger) PrintCommand(sessionID string, command string, params st
|
||||
}
|
||||
}
|
||||
|
||||
// PrintResponse impelment Logger
|
||||
// PrintResponse implement Logger
|
||||
func (log *minioLogger) PrintResponse(sessionID string, code int, message string) {
|
||||
if serverDebugLog {
|
||||
logger.Info("%s < %d %s", sessionID, code, message)
|
||||
|
||||
@@ -263,7 +263,7 @@ func isKMSReq(r *http.Request) bool {
|
||||
|
||||
// Supported Amz date headers.
|
||||
var amzDateHeaders = []string{
|
||||
// Do not chane this order, x-amz-date value should be
|
||||
// Do not change this order, x-amz-date value should be
|
||||
// validated first.
|
||||
"x-amz-date",
|
||||
"date",
|
||||
|
||||
+4
-3
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
"github.com/minio/pkg/v2/wildcard"
|
||||
@@ -156,7 +157,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
}
|
||||
}
|
||||
|
||||
info, err := tracker.disk.DiskInfo(ctx, false)
|
||||
info, err := tracker.disk.DiskInfo(ctx, DiskInfoOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to get disk information before healing it: %w", err)
|
||||
}
|
||||
@@ -433,7 +434,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
partial: func(entries metaCacheEntries, _ []error) {
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if !ok {
|
||||
// check if we can get one entry atleast
|
||||
// check if we can get one entry at least
|
||||
// proceed to heal nonetheless.
|
||||
entry, _ = entries.firstFound()
|
||||
}
|
||||
@@ -443,7 +444,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
finished: nil,
|
||||
})
|
||||
jt.Wait() // synchronize all the concurrent heal jobs
|
||||
close(results)
|
||||
xioutil.SafeClose(results)
|
||||
if err != nil {
|
||||
// Set this such that when we return this function
|
||||
// we let the caller retry this disk again for the
|
||||
|
||||
+1
-2
@@ -143,7 +143,6 @@ type serverCtxt struct {
|
||||
JSON, Quiet bool
|
||||
Anonymous bool
|
||||
StrictS3Compat bool
|
||||
PreAllocate bool
|
||||
Addr, ConsoleAddr string
|
||||
ConfigDir, CertsDir string
|
||||
configDirSet, certsDirSet bool
|
||||
@@ -293,7 +292,7 @@ var (
|
||||
// Global server's network statistics
|
||||
globalConnStats = newConnStats()
|
||||
|
||||
// Global HTTP request statisitics
|
||||
// Global HTTP request statistics
|
||||
globalHTTPStats = newHTTPStats()
|
||||
|
||||
// Global bucket network and API statistics
|
||||
|
||||
+1
-19
@@ -22,7 +22,6 @@ import (
|
||||
"crypto/tls"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/fips"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
@@ -36,28 +35,11 @@ var globalGrid atomic.Pointer[grid.Manager]
|
||||
var globalGridStart = make(chan struct{})
|
||||
|
||||
func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||
seenHosts := set.NewStringSet()
|
||||
var hosts []string
|
||||
var local string
|
||||
for _, ep := range eps {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
u := endpoint.GridHost()
|
||||
if seenHosts.Contains(u) {
|
||||
continue
|
||||
}
|
||||
seenHosts.Add(u)
|
||||
|
||||
// Set local endpoint
|
||||
if endpoint.IsLocal {
|
||||
local = u
|
||||
}
|
||||
hosts = append(hosts, u)
|
||||
}
|
||||
}
|
||||
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))),
|
||||
Local: local,
|
||||
|
||||
+2
-2
@@ -133,8 +133,8 @@ func (t *apiConfig) init(cfg api.Config, setDriveCounts []int) {
|
||||
}
|
||||
} else {
|
||||
apiRequestsMaxPerNode = cfg.RequestsMax
|
||||
if len(globalEndpoints.Hostnames()) > 0 {
|
||||
apiRequestsMaxPerNode /= len(globalEndpoints.Hostnames())
|
||||
if n := totalNodeCount(); n > 0 {
|
||||
apiRequestsMaxPerNode /= n
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,7 @@ func getReqAccessCred(r *http.Request, region string) (cred auth.Credentials) {
|
||||
return cred
|
||||
}
|
||||
|
||||
// Extract request params to be sent with event notifiation.
|
||||
// Extract request params to be sent with event notification.
|
||||
func extractReqParams(r *http.Request) map[string]string {
|
||||
if r == nil {
|
||||
return nil
|
||||
@@ -237,7 +237,7 @@ func extractReqParams(r *http.Request) map[string]string {
|
||||
return m
|
||||
}
|
||||
|
||||
// Extract response elements to be sent with event notifiation.
|
||||
// Extract response elements to be sent with event notification.
|
||||
func extractRespElements(w http.ResponseWriter) map[string]string {
|
||||
if w == nil {
|
||||
return map[string]string{}
|
||||
|
||||
@@ -152,7 +152,7 @@ func ReadinessCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// LivenessCheckHandler checks whether MinIO is up. It differs from the
|
||||
// readiness handler since a failing liveness check causes pod restarts
|
||||
// in K8S enviromnents. Therefore, it does not contact external systems.
|
||||
// in K8S environments. Therefore, it does not contact external systems.
|
||||
func LivenessCheckHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if objLayer := newObjectLayerFn(); objLayer == nil {
|
||||
w.Header().Set(xhttp.MinIOServerStatus, unavailable) // Service not initialized yet
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
@@ -575,7 +576,7 @@ func listIAMConfigItems(ctx context.Context, objAPI ObjectLayer, pathPrefix stri
|
||||
ch := make(chan itemOrErr)
|
||||
|
||||
go func() {
|
||||
defer close(ch)
|
||||
defer xioutil.SafeClose(ch)
|
||||
|
||||
// Allocate new results channel to receive ObjectInfo.
|
||||
objInfoCh := make(chan ObjectInfo)
|
||||
|
||||
+27
-8
@@ -1017,18 +1017,37 @@ func (store *IAMStoreSys) PolicyDBUpdate(ctx context.Context, name string, isGro
|
||||
newPolicyMapping.UpdatedAt = UTCNow()
|
||||
addedOrRemoved = policiesToUpdate.ToSlice()
|
||||
|
||||
if err = store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil {
|
||||
return
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
cache.iamSTSPolicyMap[name] = newPolicyMapping
|
||||
// In case of detach operation, it is possible that no policies are mapped -
|
||||
// in this case, we delete the mapping from the store.
|
||||
if len(newPolicies) == 0 {
|
||||
if err = store.deleteMappedPolicy(ctx, name, userType, isGroup); err != nil && !errors.Is(err, errNoSuchPolicy) {
|
||||
return
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
delete(cache.iamSTSPolicyMap, name)
|
||||
} else {
|
||||
delete(cache.iamUserPolicyMap, name)
|
||||
}
|
||||
} else {
|
||||
cache.iamUserPolicyMap[name] = newPolicyMapping
|
||||
delete(cache.iamGroupPolicyMap, name)
|
||||
}
|
||||
} else {
|
||||
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
||||
|
||||
if err = store.saveMappedPolicy(ctx, name, userType, isGroup, newPolicyMapping); err != nil {
|
||||
return
|
||||
}
|
||||
if !isGroup {
|
||||
if userType == stsUser {
|
||||
cache.iamSTSPolicyMap[name] = newPolicyMapping
|
||||
} else {
|
||||
cache.iamUserPolicyMap[name] = newPolicyMapping
|
||||
}
|
||||
} else {
|
||||
cache.iamGroupPolicyMap[name] = newPolicyMapping
|
||||
}
|
||||
}
|
||||
|
||||
cache.updatedAt = UTCNow()
|
||||
return cache.updatedAt, addedOrRemoved, newPolicies, nil
|
||||
}
|
||||
|
||||
+2
-1
@@ -45,6 +45,7 @@ import (
|
||||
"github.com/minio/minio/internal/config/policy/opa"
|
||||
polplugin "github.com/minio/minio/internal/config/policy/plugin"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/jwt"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/policy"
|
||||
@@ -207,7 +208,7 @@ func (sys *IAMSys) Load(ctx context.Context, firstTime bool) error {
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
default:
|
||||
close(sys.configLoaded)
|
||||
xioutil.SafeClose(sys.configLoaded)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func pickRelevantGoroutines() (gs []string) {
|
||||
buf := debug.Stack()
|
||||
// runtime stack of go routines will be listed with 2 blank spaces between each of them, so split on "\n\n" .
|
||||
for _, g := range strings.Split(string(buf), "\n\n") {
|
||||
// Again split on a new line, the first line of the second half contaisn the info about the go routine.
|
||||
// Again split on a new line, the first line of the second half contains the info about the go routine.
|
||||
sl := strings.SplitN(g, "\n", 2)
|
||||
if len(sl) != 2 {
|
||||
continue
|
||||
|
||||
@@ -47,7 +47,7 @@ func initLicenseUpdateJob(ctx context.Context, objAPI ObjectLayer) {
|
||||
// sleep for some time and try again.
|
||||
duration := time.Duration(r.Float64() * float64(time.Hour))
|
||||
if duration < time.Second {
|
||||
// Make sure to sleep atleast a second to avoid high CPU ticks.
|
||||
// Make sure to sleep at least a second to avoid high CPU ticks.
|
||||
duration = time.Second
|
||||
}
|
||||
time.Sleep(duration)
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ type lockRequesterInfo struct {
|
||||
UID string // UID to uniquely identify request of client.
|
||||
Timestamp time.Time // Timestamp set at the time of initialization.
|
||||
TimeLastRefresh time.Time // Timestamp for last lock refresh.
|
||||
Source string // Contains line, function and filename reqesting the lock.
|
||||
Source string // Contains line, function and filename requesting the lock.
|
||||
Group bool // indicates if it was a group lock.
|
||||
// Owner represents the UUID of the owner who originally requested the lock
|
||||
// useful in expiry.
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
)
|
||||
@@ -659,7 +660,7 @@ func (m *metaCacheEntriesSorted) forwardPast(s string) {
|
||||
// If the context is canceled the function will return the error,
|
||||
// otherwise the function will return nil.
|
||||
func mergeEntryChannels(ctx context.Context, in []chan metaCacheEntry, out chan<- metaCacheEntry, readQuorum int) error {
|
||||
defer close(out)
|
||||
defer xioutil.SafeClose(out)
|
||||
top := make([]*metaCacheEntry, len(in))
|
||||
nDone := 0
|
||||
ctxDone := ctx.Done()
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
@@ -339,7 +340,7 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
|
||||
// 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 close(out)
|
||||
defer xioutil.SafeClose(out)
|
||||
|
||||
for {
|
||||
var obj metaCacheEntry
|
||||
@@ -472,16 +473,16 @@ func (z *erasureServerPools) listAndSave(ctx context.Context, o *listPathOptions
|
||||
funcReturnedMu.Unlock()
|
||||
outCh <- entry
|
||||
if returned {
|
||||
close(outCh)
|
||||
xioutil.SafeClose(outCh)
|
||||
}
|
||||
}
|
||||
entry.reusable = returned
|
||||
saveCh <- entry
|
||||
}
|
||||
if !returned {
|
||||
close(outCh)
|
||||
xioutil.SafeClose(outCh)
|
||||
}
|
||||
close(saveCh)
|
||||
xioutil.SafeClose(saveCh)
|
||||
}()
|
||||
|
||||
return filteredResults()
|
||||
|
||||
@@ -37,6 +37,7 @@ import (
|
||||
"github.com/minio/minio/internal/bucket/versioning"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
)
|
||||
@@ -679,7 +680,7 @@ func getQuorumDisks(disks []StorageAPI, infos []DiskInfo, readQuorum int) (newDi
|
||||
|
||||
// Will return io.EOF if continuing would not yield more results.
|
||||
func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions, results chan<- metaCacheEntry) (err error) {
|
||||
defer close(results)
|
||||
defer xioutil.SafeClose(results)
|
||||
o.debugf(color.Green("listPath:")+" with options: %#v", o)
|
||||
|
||||
// get prioritized non-healing disks for listing
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/klauspost/compress/s2"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
"github.com/valyala/bytebufferpool"
|
||||
@@ -550,7 +551,7 @@ func (r *metacacheReader) readAll(ctx context.Context, dst chan<- metaCacheEntry
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
defer close(dst)
|
||||
defer xioutil.SafeClose(dst)
|
||||
if r.current.name != "" {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
@@ -94,7 +94,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer close(out)
|
||||
defer xioutil.SafeClose(out)
|
||||
var objsReturned int
|
||||
|
||||
objReturned := func(metadata []byte) {
|
||||
@@ -407,12 +407,12 @@ func (client *storageRESTClient) WalkDir(ctx context.Context, opts WalkDirOption
|
||||
opts.DiskID = client.diskID
|
||||
b, err := opts.MarshalMsg(grid.GetByteBuffer()[:0])
|
||||
if err != nil {
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
st, err := client.gridConn.NewStream(ctx, grid.HandlerWalkDir, b)
|
||||
if err != nil {
|
||||
return err
|
||||
return toStorageErr(err)
|
||||
}
|
||||
return toStorageErr(st.Results(func(in []byte) error {
|
||||
_, err := wr.Write(in)
|
||||
|
||||
@@ -257,7 +257,7 @@ func collectDriveMetrics(m madmin.RealtimeMetrics) {
|
||||
|
||||
for _, d := range localDrives {
|
||||
labels := map[string]string{"drive": d.Endpoint().RawPath}
|
||||
di, err := d.DiskInfo(GlobalContext, false)
|
||||
di, err := d.DiskInfo(GlobalContext, DiskInfoOptions{})
|
||||
if err == nil {
|
||||
updateResourceMetrics(driveSubsystem, usedBytes, float64(di.Used), labels, false)
|
||||
updateResourceMetrics(driveSubsystem, totalBytes, float64(di.Total), labels, false)
|
||||
|
||||
+111
-22
@@ -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
|
||||
//
|
||||
@@ -20,6 +20,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -31,6 +32,7 @@ import (
|
||||
"github.com/minio/kes-go"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/mcontext"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
@@ -58,13 +60,13 @@ func init() {
|
||||
getClusterHealthMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true}),
|
||||
getIAMNodeMetrics(MetricsGroupOpts{dependGlobalAuthNPlugin: true, dependGlobalIAMSys: true}),
|
||||
getReplicationSiteMetrics(MetricsGroupOpts{dependGlobalSiteReplicationSys: true}),
|
||||
getBatchJobsMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true}),
|
||||
}
|
||||
|
||||
peerMetricsGroups = []*MetricsGroup{
|
||||
getGoMetrics(),
|
||||
getHTTPMetrics(MetricsGroupOpts{}),
|
||||
getNotificationMetrics(MetricsGroupOpts{dependGlobalLambdaTargetList: true}),
|
||||
getLocalStorageMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true}),
|
||||
getMinioProcMetrics(),
|
||||
getMinioVersionMetrics(),
|
||||
getNetworkMetrics(),
|
||||
@@ -75,7 +77,8 @@ func init() {
|
||||
getKMSNodeMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true, dependGlobalKMS: true}),
|
||||
getMinioHealingMetrics(MetricsGroupOpts{dependGlobalBackgroundHealState: true}),
|
||||
getWebhookMetrics(),
|
||||
getReplicationClusterMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true, dependBucketTargetSys: true}),
|
||||
getTierMetrics(),
|
||||
getReplicationNodeMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true, dependBucketTargetSys: true}),
|
||||
}
|
||||
|
||||
allMetricsGroups := func() (allMetrics []*MetricsGroup) {
|
||||
@@ -95,13 +98,13 @@ func init() {
|
||||
getDistLockMetrics(MetricsGroupOpts{dependGlobalIsDistErasure: true, dependGlobalLockServer: true}),
|
||||
getIAMNodeMetrics(MetricsGroupOpts{dependGlobalAuthNPlugin: true, dependGlobalIAMSys: true}),
|
||||
getLocalStorageMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true}),
|
||||
getReplicationNodeMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true, dependBucketTargetSys: true}),
|
||||
}
|
||||
|
||||
bucketMetricsGroups := []*MetricsGroup{
|
||||
getBucketUsageMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true}),
|
||||
getHTTPMetrics(MetricsGroupOpts{bucketOnly: true}),
|
||||
getBucketTTFBMetric(),
|
||||
getBatchJobsMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true}),
|
||||
}
|
||||
|
||||
bucketPeerMetricsGroups = []*MetricsGroup{
|
||||
@@ -255,6 +258,7 @@ const (
|
||||
startTime = "starttime_seconds"
|
||||
upTime = "uptime_seconds"
|
||||
memory = "resident_memory_bytes"
|
||||
vmemory = "virtual_memory_bytes"
|
||||
cpu = "cpu_total_seconds"
|
||||
|
||||
expiryPendingTasks MetricName = "expiry_pending_tasks"
|
||||
@@ -518,7 +522,7 @@ func getNodeDriveTimeoutErrorsMD() MetricDescription {
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: driveSubsystem,
|
||||
Name: "errors_timeout",
|
||||
Help: "Total number of timeout errors since server start",
|
||||
Help: "Total number of drive timeout errors since server start",
|
||||
Type: counterMetric,
|
||||
}
|
||||
}
|
||||
@@ -528,7 +532,27 @@ func getNodeDriveAvailablityErrorsMD() MetricDescription {
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: driveSubsystem,
|
||||
Name: "errors_availability",
|
||||
Help: "Total number of I/O errors, permission denied and timeouts since server start",
|
||||
Help: "Total number of drive I/O errors, permission denied and timeouts since server start",
|
||||
Type: counterMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func getNodeDriveWaitingIOMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: driveSubsystem,
|
||||
Name: "io_waiting",
|
||||
Help: "Total number I/O operations waiting on drive",
|
||||
Type: counterMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func getNodeDriveTokensIOMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: driveSubsystem,
|
||||
Name: "io_tokens",
|
||||
Help: "Total number concurrent I/O operations configured on drive",
|
||||
Type: counterMetric,
|
||||
}
|
||||
}
|
||||
@@ -1531,6 +1555,16 @@ func getMinIOProcessResidentMemory() MetricDescription {
|
||||
}
|
||||
}
|
||||
|
||||
func getMinIOProcessVirtualMemory() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: processSubsystem,
|
||||
Name: memory,
|
||||
Help: "Virtual memory size in bytes",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func getMinIOProcessCPUTime() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
@@ -1653,6 +1687,14 @@ func getMinioProcMetrics() *MetricsGroup {
|
||||
})
|
||||
}
|
||||
|
||||
if stat.VirtualMemory() > 0 {
|
||||
metrics = append(metrics,
|
||||
Metric{
|
||||
Description: getMinIOProcessVirtualMemory(),
|
||||
Value: float64(stat.VirtualMemory()),
|
||||
})
|
||||
}
|
||||
|
||||
if stat.CPUTime() > 0 {
|
||||
metrics = append(metrics,
|
||||
Metric{
|
||||
@@ -1684,7 +1726,7 @@ func getGoMetrics() *MetricsGroup {
|
||||
func getHistogramMetrics(hist *prometheus.HistogramVec, desc MetricDescription) []Metric {
|
||||
ch := make(chan prometheus.Metric)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
defer xioutil.SafeClose(ch)
|
||||
// Collects prometheus metrics from hist and sends it over ch
|
||||
hist.Collect(ch)
|
||||
}()
|
||||
@@ -1715,6 +1757,17 @@ func getHistogramMetrics(hist *prometheus.HistogramVec, desc MetricDescription)
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
// add metrics with +Inf label
|
||||
labels1 := make(map[string]string)
|
||||
for _, lp := range dtoMetric.GetLabel() {
|
||||
labels1[*lp.Name] = *lp.Value
|
||||
}
|
||||
labels1["le"] = fmt.Sprintf("%.3f", math.Inf(+1))
|
||||
metrics = append(metrics, Metric{
|
||||
Description: desc,
|
||||
VariableLabels: labels1,
|
||||
Value: dtoMetric.Counter.GetValue(),
|
||||
})
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
@@ -2085,7 +2138,7 @@ func getIAMNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
}
|
||||
|
||||
// replication metrics for each node - published to the cluster endpoint with nodename as label
|
||||
func getReplicationClusterMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
mg := &MetricsGroup{
|
||||
cacheInterval: 1 * time.Minute,
|
||||
metricsGroupOpts: opts,
|
||||
@@ -2888,6 +2941,9 @@ func getClusterUsageMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
}
|
||||
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
|
||||
objLayer := newObjectLayerFn()
|
||||
if objLayer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
metrics = make([]Metric, 0, 50)
|
||||
dataUsageInfo, err := loadDataUsageFromBackend(ctx, objLayer)
|
||||
@@ -3120,20 +3176,20 @@ func getBucketUsageMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
}
|
||||
}
|
||||
}
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getBucketObjectDistributionMD(),
|
||||
Histogram: usage.ObjectSizesHistogram,
|
||||
HistogramBucketLabel: "range",
|
||||
VariableLabels: map[string]string{"bucket": bucket},
|
||||
})
|
||||
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getBucketObjectVersionsMD(),
|
||||
Histogram: usage.ObjectVersionsHistogram,
|
||||
HistogramBucketLabel: "range",
|
||||
VariableLabels: map[string]string{"bucket": bucket},
|
||||
})
|
||||
}
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getBucketObjectDistributionMD(),
|
||||
Histogram: usage.ObjectSizesHistogram,
|
||||
HistogramBucketLabel: "range",
|
||||
VariableLabels: map[string]string{"bucket": bucket},
|
||||
})
|
||||
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getBucketObjectVersionsMD(),
|
||||
Histogram: usage.ObjectVersionsHistogram,
|
||||
HistogramBucketLabel: "range",
|
||||
VariableLabels: map[string]string{"bucket": bucket},
|
||||
})
|
||||
}
|
||||
return
|
||||
})
|
||||
@@ -3248,6 +3304,18 @@ func getLocalStorageMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
VariableLabels: map[string]string{"drive": disk.DrivePath},
|
||||
})
|
||||
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getNodeDriveWaitingIOMD(),
|
||||
Value: float64(disk.Metrics.TotalWaiting),
|
||||
VariableLabels: map[string]string{"drive": disk.DrivePath},
|
||||
})
|
||||
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getNodeDriveTokensIOMD(),
|
||||
Value: float64(disk.Metrics.TotalTokens),
|
||||
VariableLabels: map[string]string{"drive": disk.DrivePath},
|
||||
})
|
||||
|
||||
for apiName, latency := range disk.Metrics.LastMinute {
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getNodeDriveAPILatencyMD(),
|
||||
@@ -3308,6 +3376,16 @@ func getClusterHealthStatusMD() MetricDescription {
|
||||
}
|
||||
}
|
||||
|
||||
func getClusterErasureSetHealthStatusMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: clusterMetricNamespace,
|
||||
Subsystem: "health",
|
||||
Name: "erasure_set_status",
|
||||
Help: "Get current health status for this erasure set",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func getClusterErasureSetReadQuorumMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: clusterMetricNamespace,
|
||||
@@ -3401,6 +3479,17 @@ func getClusterHealthMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
VariableLabels: labels,
|
||||
Value: float64(h.HealingDrives),
|
||||
})
|
||||
|
||||
health := 1
|
||||
if !h.Healthy {
|
||||
health = 0
|
||||
}
|
||||
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getClusterErasureSetHealthStatusMD(),
|
||||
VariableLabels: labels,
|
||||
Value: float64(health),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
@@ -3815,7 +3904,7 @@ func (c *minioClusterCollector) Collect(out chan<- prometheus.Metric) {
|
||||
func ReportMetrics(ctx context.Context, metricsGroups []*MetricsGroup) <-chan Metric {
|
||||
ch := make(chan Metric)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
defer xioutil.SafeClose(ch)
|
||||
populateAndPublish(metricsGroups, func(m Metric) bool {
|
||||
if m.VariableLabels == nil {
|
||||
m.VariableLabels = make(map[string]string)
|
||||
|
||||
@@ -81,7 +81,8 @@ func TestGetHistogramMetrics(t *testing.T) {
|
||||
}
|
||||
|
||||
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD())
|
||||
if expPoints := len(labels) * len(histBuckets); expPoints != len(metrics) {
|
||||
// additional labels for +Inf for all histogram metrics
|
||||
if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) {
|
||||
t.Fatalf("Expected %v data points but got %v", expPoints, len(metrics))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
//
|
||||
@@ -108,6 +108,10 @@ func (d *naughtyDisk) GetDiskID() (string, error) {
|
||||
return d.disk.GetDiskID()
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) SetFormatData(b []byte) {
|
||||
d.disk.SetFormatData(b)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) SetDiskID(id string) {
|
||||
d.disk.SetDiskID(id)
|
||||
}
|
||||
@@ -119,11 +123,11 @@ func (d *naughtyDisk) NSScanner(ctx context.Context, cache dataUsageCache, updat
|
||||
return d.disk.NSScanner(ctx, cache, updates, scanMode, weSleep)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) DiskInfo(ctx context.Context, metrics bool) (info DiskInfo, err error) {
|
||||
func (d *naughtyDisk) DiskInfo(ctx context.Context, opts DiskInfoOptions) (info DiskInfo, err error) {
|
||||
if err := d.calcError(); err != nil {
|
||||
return info, err
|
||||
}
|
||||
return d.disk.DiskInfo(ctx, metrics)
|
||||
return d.disk.DiskInfo(ctx, opts)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) MakeVolBulk(ctx context.Context, volumes ...string) (err error) {
|
||||
|
||||
+2
-2
@@ -111,9 +111,9 @@ func getHostIP(host string) (ipList set.StringSet, err error) {
|
||||
return ipList, err
|
||||
}
|
||||
|
||||
// sortIPs - sort ips based on higher octects.
|
||||
// sortIPs - sort ips based on higher octets.
|
||||
// The logic to sort by last octet is implemented to
|
||||
// prefer CIDRs with higher octects, this in-turn skips the
|
||||
// prefer CIDRs with higher octets, this in-turn skips the
|
||||
// localhost/loopback address to be not preferred as the
|
||||
// first ip on the list. Subsequently this list helps us print
|
||||
// a user friendly message with appropriate values.
|
||||
|
||||
+70
-15
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -25,14 +26,17 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
xnet "github.com/minio/pkg/v2/net"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
"github.com/minio/pkg/v2/workers"
|
||||
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -58,7 +62,7 @@ type NotificationPeerErr struct {
|
||||
//
|
||||
// A zero NotificationGroup is valid and does not cancel on error.
|
||||
type NotificationGroup struct {
|
||||
wg sync.WaitGroup
|
||||
workers *workers.Workers
|
||||
errs []NotificationPeerErr
|
||||
retryCount int
|
||||
}
|
||||
@@ -66,7 +70,22 @@ type NotificationGroup struct {
|
||||
// WithNPeers returns a new NotificationGroup with length of errs slice upto nerrs,
|
||||
// upon Wait() errors are returned collected from all tasks.
|
||||
func WithNPeers(nerrs int) *NotificationGroup {
|
||||
return &NotificationGroup{errs: make([]NotificationPeerErr, nerrs), retryCount: 3}
|
||||
if nerrs <= 0 {
|
||||
nerrs = 1
|
||||
}
|
||||
wk, _ := workers.New(nerrs)
|
||||
return &NotificationGroup{errs: make([]NotificationPeerErr, nerrs), workers: wk, retryCount: 3}
|
||||
}
|
||||
|
||||
// WithNPeersThrottled returns a new NotificationGroup with length of errs slice upto nerrs,
|
||||
// upon Wait() errors are returned collected from all tasks, optionally allows for X workers
|
||||
// only "per" parallel task.
|
||||
func WithNPeersThrottled(nerrs, wks int) *NotificationGroup {
|
||||
if nerrs <= 0 {
|
||||
nerrs = 1
|
||||
}
|
||||
wk, _ := workers.New(wks)
|
||||
return &NotificationGroup{errs: make([]NotificationPeerErr, nerrs), workers: wk, retryCount: 3}
|
||||
}
|
||||
|
||||
// WithRetries sets the retry count for all function calls from the Go method.
|
||||
@@ -80,7 +99,7 @@ func (g *NotificationGroup) WithRetries(retryCount int) *NotificationGroup {
|
||||
// Wait blocks until all function calls from the Go method have returned, then
|
||||
// returns the slice of errors from all function calls.
|
||||
func (g *NotificationGroup) Wait() []NotificationPeerErr {
|
||||
g.wg.Wait()
|
||||
g.workers.Wait()
|
||||
return g.errs
|
||||
}
|
||||
|
||||
@@ -91,10 +110,11 @@ func (g *NotificationGroup) Wait() []NotificationPeerErr {
|
||||
func (g *NotificationGroup) Go(ctx context.Context, f func() error, index int, addr xnet.Host) {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
g.wg.Add(1)
|
||||
g.workers.Take()
|
||||
|
||||
go func() {
|
||||
defer g.wg.Done()
|
||||
defer g.workers.Give()
|
||||
|
||||
g.errs[index] = NotificationPeerErr{
|
||||
Host: addr,
|
||||
}
|
||||
@@ -106,7 +126,7 @@ func (g *NotificationGroup) Go(ctx context.Context, f func() error, index int, a
|
||||
if i == g.retryCount-1 {
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("peerAddress", addr.String())
|
||||
ctx := logger.SetReqInfo(ctx, reqInfo)
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, addr.String())
|
||||
}
|
||||
// Wait for a minimum of 100ms and dynamically increase this based on number of attempts.
|
||||
if i < g.retryCount-1 {
|
||||
@@ -338,15 +358,34 @@ func (sys *NotificationSys) DownloadProfilingData(ctx context.Context, writer io
|
||||
}
|
||||
|
||||
// VerifyBinary - asks remote peers to verify the checksum
|
||||
func (sys *NotificationSys) VerifyBinary(ctx context.Context, u *url.URL, sha256Sum []byte, releaseInfo string, reader []byte) []NotificationPeerErr {
|
||||
ng := WithNPeers(len(sys.peerClients))
|
||||
func (sys *NotificationSys) VerifyBinary(ctx context.Context, u *url.URL, sha256Sum []byte, releaseInfo string, bin []byte) []NotificationPeerErr {
|
||||
// FIXME: network calls made in this manner such as one goroutine per node,
|
||||
// can easily eat into the internode bandwidth. This function would be mostly
|
||||
// TX saturating, however there are situations where a RX might also saturate.
|
||||
// To avoid these problems we must split the work at scale. With 1000 node
|
||||
// setup becoming a reality we must try to shard the work properly such as
|
||||
// pick 10 nodes that precisely can send those 100 requests the first node
|
||||
// in the 10 node shard would coordinate between other 9 shards to get the
|
||||
// rest of the `99*9` requests.
|
||||
//
|
||||
// This essentially splits the workload properly and also allows for network
|
||||
// utilization to be optimal, instead of blindly throttling the way we are
|
||||
// doing below. However the changes that are needed here are a bit involved,
|
||||
// further discussion advised. Remove this comment and remove the worker model
|
||||
// for this function in future.
|
||||
maxWorkers := runtime.GOMAXPROCS(0) / 2
|
||||
if maxWorkers > len(sys.peerClients) {
|
||||
maxWorkers = len(sys.peerClients)
|
||||
}
|
||||
|
||||
ng := WithNPeersThrottled(len(sys.peerClients), maxWorkers)
|
||||
for idx, client := range sys.peerClients {
|
||||
if client == nil {
|
||||
continue
|
||||
}
|
||||
client := client
|
||||
ng.Go(ctx, func() error {
|
||||
return client.VerifyBinary(ctx, u, sha256Sum, releaseInfo, reader)
|
||||
return client.VerifyBinary(ctx, u, sha256Sum, releaseInfo, bytes.NewReader(bin))
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
@@ -376,7 +415,7 @@ func (sys *NotificationSys) SignalConfigReload(subSys string) []NotificationPeer
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(serviceReloadDynamic, subSys)
|
||||
return client.SignalService(serviceReloadDynamic, subSys, false)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
@@ -391,7 +430,23 @@ func (sys *NotificationSys) SignalService(sig serviceSignal) []NotificationPeerE
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(sig, "")
|
||||
// force == true preserves the current behavior
|
||||
return client.SignalService(sig, "", false)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
}
|
||||
|
||||
// SignalServiceV2 - calls signal service RPC call on all peers with v2 API
|
||||
func (sys *NotificationSys) SignalServiceV2(sig serviceSignal, dryRun bool) []NotificationPeerErr {
|
||||
ng := WithNPeers(len(sys.peerClients))
|
||||
for idx, client := range sys.peerClients {
|
||||
if client == nil {
|
||||
continue
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(sig, "", dryRun)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
return ng.Wait()
|
||||
@@ -1209,7 +1264,7 @@ func (sys *NotificationSys) collectPeerMetrics(ctx context.Context, peerChannels
|
||||
}
|
||||
go func(wg *sync.WaitGroup, ch chan Metric) {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
xioutil.SafeClose(ch)
|
||||
}(&wg, ch)
|
||||
return ch
|
||||
}
|
||||
@@ -1275,7 +1330,7 @@ func (sys *NotificationSys) ServiceFreeze(ctx context.Context, freeze bool) []No
|
||||
}
|
||||
client := client
|
||||
ng.Go(GlobalContext, func() error {
|
||||
return client.SignalService(serviceSig, "")
|
||||
return client.SignalService(serviceSig, "", false)
|
||||
}, idx, *client.host)
|
||||
}
|
||||
nerrs := ng.Wait()
|
||||
@@ -1434,7 +1489,7 @@ func (sys *NotificationSys) DriveSpeedTest(ctx context.Context, opts madmin.Driv
|
||||
|
||||
go func(wg *sync.WaitGroup, ch chan madmin.DriveSpeedTestResult) {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
xioutil.SafeClose(ch)
|
||||
}(&wg, ch)
|
||||
|
||||
return ch
|
||||
@@ -1562,7 +1617,7 @@ func (sys *NotificationSys) GetReplicationMRF(ctx context.Context, bucket, node
|
||||
}(mrfCh)
|
||||
go func(wg *sync.WaitGroup) {
|
||||
wg.Wait()
|
||||
close(mrfCh)
|
||||
xioutil.SafeClose(mrfCh)
|
||||
}(&wg)
|
||||
return mrfCh, nil
|
||||
}
|
||||
|
||||
@@ -428,7 +428,7 @@ func (lm ListMultipartsInfo) Lookup(uploadID string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ListMultipartsInfo - represnets bucket resources for incomplete multipart uploads.
|
||||
// ListMultipartsInfo - represents bucket resources for incomplete multipart uploads.
|
||||
type ListMultipartsInfo struct {
|
||||
// Together with upload-id-marker, this parameter specifies the multipart upload
|
||||
// after which listing should begin.
|
||||
|
||||
@@ -539,7 +539,7 @@ func (e ObjectNameTooLong) Error() string {
|
||||
|
||||
// Error returns string an error formatted as the given text.
|
||||
func (e ObjectNamePrefixAsSlash) Error() string {
|
||||
return "Object name contains forward slash as pefix: " + e.Bucket + "/" + e.Object
|
||||
return "Object name contains forward slash as prefix: " + e.Bucket + "/" + e.Object
|
||||
}
|
||||
|
||||
// AllAccessDisabled All access to this object has been disabled
|
||||
|
||||
@@ -825,7 +825,7 @@ func _testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler, v
|
||||
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Using an existing file for bucket name, but its not a directory (5).
|
||||
{"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false},
|
||||
// Valid bucket names, but they donot exist (6-8).
|
||||
// Valid bucket names, but they do not exist (6-8).
|
||||
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
@@ -1570,7 +1570,7 @@ func testListObjectVersions(obj ObjectLayer, instanceType string, t1 TestErrHand
|
||||
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Using an existing file for bucket name, but its not a directory (5).
|
||||
{"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false},
|
||||
// Valid bucket names, but they donot exist (6-8).
|
||||
// Valid bucket names, but they do not exist (6-8).
|
||||
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
|
||||
@@ -52,16 +52,16 @@ func testObjectNewMultipartUpload(obj ObjectLayer, instanceType string, t TestEr
|
||||
}
|
||||
|
||||
errMsg := "Bucket not found: minio-bucket"
|
||||
// opearation expected to fail since the bucket on which NewMultipartUpload is being initiated doesn't exist.
|
||||
// operation expected to fail since the bucket on which NewMultipartUpload is being initiated doesn't exist.
|
||||
_, err = obj.NewMultipartUpload(context.Background(), bucket, object, opts)
|
||||
if err == nil {
|
||||
t.Fatalf("%s: Expected to fail since the NewMultipartUpload is intialized on a non-existent bucket.", instanceType)
|
||||
t.Fatalf("%s: Expected to fail since the NewMultipartUpload is initialized on a non-existent bucket.", instanceType)
|
||||
}
|
||||
if errMsg != err.Error() {
|
||||
t.Errorf("%s, Expected to fail with Error \"%s\", but instead found \"%s\".", instanceType, errMsg, err.Error())
|
||||
}
|
||||
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err = obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// failed to create newbucket, abort.
|
||||
@@ -94,7 +94,7 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test
|
||||
bucket := "minio-bucket"
|
||||
object := "minio-object"
|
||||
opts := ObjectOptions{}
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// failed to create newbucket, abort.
|
||||
@@ -150,7 +150,7 @@ func testObjectAPIIsUploadIDExists(obj ObjectLayer, instanceType string, t TestE
|
||||
bucket := "minio-bucket"
|
||||
object := "minio-object"
|
||||
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
@@ -182,7 +182,7 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
|
||||
bucket := "minio-bucket"
|
||||
object := "minio-object"
|
||||
opts := ObjectOptions{}
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err := obj.MakeBucket(context.Background(), bucket, MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
@@ -348,7 +348,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
|
||||
// bucketnames[1].
|
||||
// objectNames[0].
|
||||
// uploadIds [1-3].
|
||||
// Bucket to test for mutiple upload Id's for a given object.
|
||||
// Bucket to test for multiple upload Id's for a given object.
|
||||
err = obj.MakeBucket(context.Background(), bucketNames[1], MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
@@ -366,7 +366,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
|
||||
uploadIDs = append(uploadIDs, res.UploadID)
|
||||
}
|
||||
|
||||
// Bucket to test for mutiple objects, each with unique UUID.
|
||||
// Bucket to test for multiple objects, each with unique UUID.
|
||||
// bucketnames[2].
|
||||
// objectNames[0-2].
|
||||
// uploadIds [4-9].
|
||||
@@ -1045,7 +1045,7 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
|
||||
{"Test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Valid bucket names, but they donot exist (Test number 5-7).
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
@@ -1210,7 +1210,7 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks
|
||||
// bucketnames[0].
|
||||
// objectNames[0].
|
||||
// uploadIds [0].
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
@@ -1362,7 +1362,7 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks
|
||||
{"---", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Test cases for listing uploadID with single part.
|
||||
// Valid bucket names, but they donot exist (Test number 5-7).
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
@@ -1461,7 +1461,7 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler)
|
||||
// bucketnames[0].
|
||||
// objectNames[0].
|
||||
// uploadIds [0].
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err := obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
@@ -1599,7 +1599,7 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler)
|
||||
{"---", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Test cases for listing uploadID with single part.
|
||||
// Valid bucket names, but they donot exist (Test number 5-7).
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
@@ -1702,7 +1702,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T
|
||||
// bucketnames[0].
|
||||
// objectNames[0].
|
||||
// uploadIds [0].
|
||||
// Create bucket before intiating NewMultipartUpload.
|
||||
// Create bucket before initiating NewMultipartUpload.
|
||||
err = obj.MakeBucket(context.Background(), bucketNames[0], MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
@@ -1814,7 +1814,7 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T
|
||||
{"---", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "ad"}, false},
|
||||
// Test cases for listing uploadID with single part.
|
||||
// Valid bucket names, but they donot exist (Test number 5-7).
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"github.com/minio/minio/internal/hash"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/ioutil"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/trie"
|
||||
"github.com/minio/pkg/v2/wildcard"
|
||||
@@ -1081,7 +1082,7 @@ func newS2CompressReader(r io.Reader, on int64, encrypted bool) (rc io.ReadClose
|
||||
comp := s2.NewWriter(pw, opts...)
|
||||
indexCh := make(chan []byte, 1)
|
||||
go func() {
|
||||
defer close(indexCh)
|
||||
defer xioutil.SafeClose(indexCh)
|
||||
cn, err := io.Copy(comp, r)
|
||||
if err != nil {
|
||||
comp.Close()
|
||||
@@ -1157,11 +1158,12 @@ func compressSelfTest() {
|
||||
// If a disk is nil or an error is returned the result will be nil as well.
|
||||
func getDiskInfos(ctx context.Context, disks ...StorageAPI) []*DiskInfo {
|
||||
res := make([]*DiskInfo, len(disks))
|
||||
opts := DiskInfoOptions{}
|
||||
for i, disk := range disks {
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
if di, err := disk.DiskInfo(ctx, false); err == nil {
|
||||
if di, err := disk.DiskInfo(ctx, opts); err == nil {
|
||||
res[i] = &di
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func testPathTraversalExploit(obj ObjectLayer, instanceType, bucketName string,
|
||||
t.Fatalf("failed to create HTTP request for Put Object: <ERROR> %v", err)
|
||||
}
|
||||
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
|
||||
|
||||
@@ -1914,7 +1914,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
},
|
||||
|
||||
// Test case - 6.
|
||||
// Test case with ivalid byte range for exceeding source size boundaries.
|
||||
// Test case with invalid byte range for exceeding source size boundaries.
|
||||
{
|
||||
bucketName: bucketName,
|
||||
uploadID: uploadID,
|
||||
@@ -2030,7 +2030,7 @@ func testAPICopyObjectPartHandler(obj ObjectLayer, instanceType, bucketName stri
|
||||
secretKey: credentials.SecretKey,
|
||||
expectedRespStatus: http.StatusNotFound,
|
||||
},
|
||||
// Test case - 16, Test case with ivalid byte range empty value.
|
||||
// Test case - 16, Test case with invalid byte range empty value.
|
||||
{
|
||||
bucketName: bucketName,
|
||||
uploadID: uploadID,
|
||||
@@ -3021,7 +3021,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
},
|
||||
// Test case - 7.
|
||||
// Test case with proper parts.
|
||||
// Should successed and the content in the response body is asserted.
|
||||
// Should succeeded and the content in the response body is asserted.
|
||||
{
|
||||
bucket: bucketName,
|
||||
object: objectName,
|
||||
@@ -3037,7 +3037,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
},
|
||||
// Test case - 8.
|
||||
// Test case with proper parts.
|
||||
// Should successed and the content in the response body is asserted.
|
||||
// Should succeeded and the content in the response body is asserted.
|
||||
{
|
||||
bucket: bucketName,
|
||||
object: objectName,
|
||||
@@ -3088,7 +3088,7 @@ func testAPICompleteMultipartHandler(obj ObjectLayer, instanceType, bucketName s
|
||||
if rec.Code == http.StatusOK {
|
||||
// Verify whether the bucket obtained object is same as the one created.
|
||||
if !bytes.Equal(testCase.expectedContent, actualContent) {
|
||||
t.Errorf("Test %d : MinIO %s: CompleteMultipart response content differs from expected value. got %s, expecte %s", i+1, instanceType,
|
||||
t.Errorf("Test %d : MinIO %s: CompleteMultipart response content differs from expected value. got %s, expected %s", i+1, instanceType,
|
||||
string(actualContent), string(testCase.expectedContent))
|
||||
}
|
||||
continue
|
||||
@@ -3366,7 +3366,7 @@ func testAPIDeleteObjectHandler(obj ObjectLayer, instanceType, bucketName string
|
||||
}{
|
||||
// Test case - 1.
|
||||
// Deleting an existing object.
|
||||
// Expected to return HTTP resposne status code 204.
|
||||
// Expected to return HTTP response status code 204.
|
||||
{
|
||||
bucketName: bucketName,
|
||||
objectName: objectName,
|
||||
@@ -3490,7 +3490,7 @@ func testAPIPutObjectPartHandlerStreaming(obj ObjectLayer, instanceType, bucketN
|
||||
}
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
|
||||
// Get uploadID of the mulitpart upload initiated.
|
||||
// Get uploadID of the multipart upload initiated.
|
||||
var mpartResp InitiateMultipartUploadResponse
|
||||
mpartRespBytes, err := io.ReadAll(rec.Result().Body)
|
||||
if err != nil {
|
||||
@@ -3897,7 +3897,7 @@ func testAPIListObjectPartsHandlerPreSign(obj ObjectLayer, instanceType, bucketN
|
||||
}
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
|
||||
// Get uploadID of the mulitpart upload initiated.
|
||||
// Get uploadID of the multipart upload initiated.
|
||||
var mpartResp InitiateMultipartUploadResponse
|
||||
mpartRespBytes, err := io.ReadAll(rec.Result().Body)
|
||||
if err != nil {
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ func osMkdirAll(dirPath string, perm os.FileMode, baseDir string) error {
|
||||
// refer https://github.com/golang/go/issues/24015
|
||||
const blockSize = 8 << 10 // 8192
|
||||
|
||||
// By default atleast 128 entries in single getdents call (1MiB buffer)
|
||||
// By default at least 128 entries in single getdents call (1MiB buffer)
|
||||
var (
|
||||
direntPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
|
||||
+17
-26
@@ -21,6 +21,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -33,6 +34,7 @@ import (
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/event"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
"github.com/minio/pkg/v2/logger/message/log"
|
||||
@@ -252,7 +254,7 @@ func (client *peerRESTClient) GetResourceMetrics(ctx context.Context) (<-chan Me
|
||||
go func(ch chan<- Metric) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
close(ch)
|
||||
xioutil.SafeClose(ch)
|
||||
}()
|
||||
for {
|
||||
var metric Metric
|
||||
@@ -475,26 +477,14 @@ func (client *peerRESTClient) LoadGroup(group string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type binaryInfo struct {
|
||||
URL *url.URL
|
||||
Sha256Sum []byte
|
||||
ReleaseInfo string
|
||||
BinaryFile []byte
|
||||
}
|
||||
|
||||
// VerifyBinary - sends verify binary message to remote peers.
|
||||
func (client *peerRESTClient) VerifyBinary(ctx context.Context, u *url.URL, sha256Sum []byte, releaseInfo string, readerInput []byte) error {
|
||||
func (client *peerRESTClient) VerifyBinary(ctx context.Context, u *url.URL, sha256Sum []byte, releaseInfo string, reader io.Reader) error {
|
||||
values := make(url.Values)
|
||||
var reader bytes.Buffer
|
||||
if err := gob.NewEncoder(&reader).Encode(binaryInfo{
|
||||
URL: u,
|
||||
Sha256Sum: sha256Sum,
|
||||
ReleaseInfo: releaseInfo,
|
||||
BinaryFile: readerInput,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodDownloadBinary, values, &reader, -1)
|
||||
values.Set(peerRESTURL, u.String())
|
||||
values.Set(peerRESTSha256Sum, hex.EncodeToString(sha256Sum))
|
||||
values.Set(peerRESTReleaseInfo, releaseInfo)
|
||||
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodVerifyBinary, values, reader, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -513,9 +503,10 @@ func (client *peerRESTClient) CommitBinary(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// SignalService - sends signal to peer nodes.
|
||||
func (client *peerRESTClient) SignalService(sig serviceSignal, subSys string) error {
|
||||
func (client *peerRESTClient) SignalService(sig serviceSignal, subSys string, dryRun bool) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTSignal, strconv.Itoa(int(sig)))
|
||||
values.Set(peerRESTDryRun, strconv.FormatBool(dryRun))
|
||||
values.Set(peerRESTSubSys, subSys)
|
||||
respBody, err := client.call(peerRESTMethodSignalService, values, nil, -1)
|
||||
if err != nil {
|
||||
@@ -635,7 +626,7 @@ func (client *peerRESTClient) doTrace(traceCh chan<- madmin.TraceInfo, doneCh <-
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
|
||||
cancelCh := make(chan struct{})
|
||||
defer close(cancelCh)
|
||||
defer xioutil.SafeClose(cancelCh)
|
||||
go func() {
|
||||
select {
|
||||
case <-doneCh:
|
||||
@@ -673,7 +664,7 @@ func (client *peerRESTClient) doListen(listenCh chan<- event.Event, doneCh <-cha
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
|
||||
cancelCh := make(chan struct{})
|
||||
defer close(cancelCh)
|
||||
defer xioutil.SafeClose(cancelCh)
|
||||
go func() {
|
||||
select {
|
||||
case <-doneCh:
|
||||
@@ -743,7 +734,7 @@ func (client *peerRESTClient) doConsoleLog(logCh chan log.Info, doneCh <-chan st
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
|
||||
cancelCh := make(chan struct{})
|
||||
defer close(cancelCh)
|
||||
defer xioutil.SafeClose(cancelCh)
|
||||
go func() {
|
||||
select {
|
||||
case <-doneCh:
|
||||
@@ -870,7 +861,7 @@ func (client *peerRESTClient) GetPeerMetrics(ctx context.Context) (<-chan Metric
|
||||
go func(ch chan<- Metric) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
close(ch)
|
||||
xioutil.SafeClose(ch)
|
||||
}()
|
||||
for {
|
||||
var metric Metric
|
||||
@@ -897,7 +888,7 @@ func (client *peerRESTClient) GetPeerBucketMetrics(ctx context.Context) (<-chan
|
||||
go func(ch chan<- Metric) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
close(ch)
|
||||
xioutil.SafeClose(ch)
|
||||
}()
|
||||
for {
|
||||
var metric Metric
|
||||
@@ -1035,7 +1026,7 @@ func (client *peerRESTClient) GetReplicationMRF(ctx context.Context, bucket stri
|
||||
go func(ch chan madmin.ReplicationMRF) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
close(ch)
|
||||
xioutil.SafeClose(ch)
|
||||
}()
|
||||
for {
|
||||
var entry madmin.ReplicationMRF
|
||||
|
||||
@@ -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,8 +18,7 @@
|
||||
package cmd
|
||||
|
||||
const (
|
||||
peerRESTVersion = "v34" // Add metrics flag to LocalStorageInfo call
|
||||
|
||||
peerRESTVersion = "v36" // Rewrite VerifyBinaryHandler()
|
||||
peerRESTVersionPrefix = SlashSeparator + peerRESTVersion
|
||||
peerRESTPrefix = minioReservedBucketPath + "/peer"
|
||||
peerRESTPath = peerRESTPrefix + peerRESTVersionPrefix
|
||||
@@ -42,7 +41,7 @@ const (
|
||||
peerRESTMethodLoadBucketMetadata = "/loadbucketmetadata"
|
||||
peerRESTMethodGetBucketStats = "/getbucketstats"
|
||||
peerRESTMethodGetAllBucketStats = "/getallbucketstats"
|
||||
peerRESTMethodDownloadBinary = "/downloadbinary"
|
||||
peerRESTMethodVerifyBinary = "/verifybinary"
|
||||
peerRESTMethodCommitBinary = "/commitbinary"
|
||||
peerRESTMethodSignalService = "/signalservice"
|
||||
peerRESTMethodBackgroundHealStatus = "/backgroundhealstatus"
|
||||
@@ -108,6 +107,11 @@ const (
|
||||
peerRESTDepID = "depID"
|
||||
peerRESTStartRebalance = "start-rebalance"
|
||||
peerRESTMetrics = "metrics"
|
||||
peerRESTDryRun = "dry-run"
|
||||
|
||||
peerRESTURL = "url"
|
||||
peerRESTSha256Sum = "sha256sum"
|
||||
peerRESTReleaseInfo = "releaseinfo"
|
||||
|
||||
peerRESTListenBucket = "bucket"
|
||||
peerRESTListenPrefix = "prefix"
|
||||
|
||||
+69
-12
@@ -20,16 +20,20 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
b "github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/event"
|
||||
@@ -823,8 +827,8 @@ func (s *peerRESTServer) GetLocalDiskIDs(w http.ResponseWriter, r *http.Request)
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(ids))
|
||||
}
|
||||
|
||||
// DownloadBinary - updates the current server.
|
||||
func (s *peerRESTServer) DownloadBinaryHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// VerifyBinary - verifies the downloaded binary is in-tact
|
||||
func (s *peerRESTServer) VerifyBinaryHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
@@ -835,14 +839,38 @@ func (s *peerRESTServer) DownloadBinaryHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
var info binaryInfo
|
||||
err := gob.NewDecoder(r.Body).Decode(&info)
|
||||
u, err := url.Parse(r.Form.Get(peerRESTURL))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = verifyBinary(info.URL, info.Sha256Sum, info.ReleaseInfo, getMinioMode(), info.BinaryFile); err != nil {
|
||||
sha256Sum, err := hex.DecodeString(r.Form.Get(peerRESTSha256Sum))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
releaseInfo := r.Form.Get(peerRESTReleaseInfo)
|
||||
|
||||
lrTime, err := releaseInfoToReleaseTime(releaseInfo)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if lrTime.Sub(currentReleaseTime) <= 0 {
|
||||
s.writeErrorResponse(w, fmt.Errorf("server is already running the latest version: %s", Version))
|
||||
return
|
||||
}
|
||||
|
||||
zr, err := zstd.NewReader(r.Body)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
if err = verifyBinary(u, sha256Sum, releaseInfo, getMinioMode(), zr); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
@@ -863,6 +891,24 @@ func (s *peerRESTServer) CommitBinaryHandler(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
var errUnsupportedSignal = fmt.Errorf("unsupported signal")
|
||||
|
||||
func waitingDrivesNode() map[string]madmin.DiskMetrics {
|
||||
errs := make([]error, len(globalLocalDrives))
|
||||
infos := make([]DiskInfo, len(globalLocalDrives))
|
||||
for i, drive := range globalLocalDrives {
|
||||
infos[i], errs[i] = drive.DiskInfo(GlobalContext, DiskInfoOptions{})
|
||||
}
|
||||
infoMaps := make(map[string]madmin.DiskMetrics)
|
||||
for i := range infos {
|
||||
if infos[i].Metrics.TotalWaiting >= 1 && errors.Is(errs[i], errFaultyDisk) {
|
||||
infoMaps[infos[i].Endpoint] = madmin.DiskMetrics{
|
||||
TotalTokens: infos[i].Metrics.TotalTokens,
|
||||
TotalWaiting: infos[i].Metrics.TotalWaiting,
|
||||
}
|
||||
}
|
||||
}
|
||||
return infoMaps
|
||||
}
|
||||
|
||||
// SignalServiceHandler - signal service handler.
|
||||
func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
@@ -883,10 +929,21 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
signal := serviceSignal(si)
|
||||
switch signal {
|
||||
case serviceRestart:
|
||||
globalServiceSignalCh <- signal
|
||||
case serviceStop:
|
||||
globalServiceSignalCh <- signal
|
||||
case serviceRestart, serviceStop:
|
||||
dryRun := r.Form.Get("dry-run") == "true" // This is only supported for `restart/stop`
|
||||
|
||||
waitingDisks := waitingDrivesNode()
|
||||
if len(waitingDisks) > 0 {
|
||||
buf, err := json.Marshal(waitingDisks)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
s.writeErrorResponse(w, errors.New(string(buf)))
|
||||
}
|
||||
if !dryRun {
|
||||
globalServiceSignalCh <- signal
|
||||
}
|
||||
case serviceFreeze:
|
||||
freezeServices()
|
||||
case serviceUnFreeze:
|
||||
@@ -1180,7 +1237,7 @@ func (s *peerRESTServer) ConsoleLogHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
defer xioutil.SafeClose(doneCh)
|
||||
|
||||
ch := make(chan log.Info, 100000)
|
||||
err := globalConsoleSys.Subscribe(ch, doneCh, "", 0, madmin.LogMaskAll, nil)
|
||||
@@ -1241,7 +1298,7 @@ func (s *peerRESTServer) GetBandwidth(w http.ResponseWriter, r *http.Request) {
|
||||
bucketsString := r.Form.Get("buckets")
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
defer close(doneCh)
|
||||
defer xioutil.SafeClose(doneCh)
|
||||
|
||||
selectBuckets := b.SelectBuckets(strings.Split(bucketsString, ",")...)
|
||||
report := globalBucketMonitor.GetReport(selectBuckets)
|
||||
@@ -1492,7 +1549,7 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadBucketMetadata).HandlerFunc(h(server.LoadBucketMetadataHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetBucketStats).HandlerFunc(h(server.GetBucketStatsHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSignalService).HandlerFunc(h(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDownloadBinary).HandlerFunc(h(server.DownloadBinaryHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodVerifyBinary).HandlerFunc(h(server.VerifyBinaryHandler)).Queries(restQueries(peerRESTURL, peerRESTSha256Sum, peerRESTReleaseInfo)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodCommitBinary).HandlerFunc(h(server.CommitBinaryHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeletePolicy).HandlerFunc(h(server.DeletePolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicy).HandlerFunc(h(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
|
||||
|
||||
+31
-1
@@ -135,6 +135,36 @@ func NewS3PeerSys(endpoints EndpointServerPools) *S3PeerSys {
|
||||
func (sys *S3PeerSys) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||
g := errgroup.WithNErrs(len(sys.peerClients))
|
||||
|
||||
for idx, client := range sys.peerClients {
|
||||
idx := idx
|
||||
client := client
|
||||
g.Go(func() error {
|
||||
if client == nil {
|
||||
return errPeerOffline
|
||||
}
|
||||
_, err := client.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||
return err
|
||||
}, idx)
|
||||
}
|
||||
|
||||
errs := g.Wait()
|
||||
|
||||
var poolErrs []error
|
||||
for poolIdx := 0; poolIdx < sys.poolsCount; poolIdx++ {
|
||||
perPoolErrs := make([]error, 0, len(sys.peerClients))
|
||||
for i, client := range sys.peerClients {
|
||||
if slices.Contains(client.GetPools(), poolIdx) {
|
||||
perPoolErrs = append(perPoolErrs, errs[i])
|
||||
}
|
||||
}
|
||||
quorum := len(perPoolErrs) / 2
|
||||
poolErrs = append(poolErrs, reduceWriteQuorumErrs(ctx, perPoolErrs, bucketOpIgnoredErrs, quorum))
|
||||
}
|
||||
|
||||
opts.Remove = isAllBucketsNotFound(poolErrs)
|
||||
opts.Recreate = !opts.Remove
|
||||
|
||||
g = errgroup.WithNErrs(len(sys.peerClients))
|
||||
healBucketResults := make([]madmin.HealResultItem, len(sys.peerClients))
|
||||
for idx, client := range sys.peerClients {
|
||||
idx := idx
|
||||
@@ -152,7 +182,7 @@ func (sys *S3PeerSys) HealBucket(ctx context.Context, bucket string, opts madmin
|
||||
}, idx)
|
||||
}
|
||||
|
||||
errs := g.Wait()
|
||||
errs = g.Wait()
|
||||
|
||||
for poolIdx := 0; poolIdx < sys.poolsCount; poolIdx++ {
|
||||
perPoolErrs := make([]error, 0, len(sys.peerClients))
|
||||
|
||||
+19
-24
@@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
@@ -83,21 +82,21 @@ func (s *peerS3Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (res madmin.HealResultItem, err error) {
|
||||
globalLocalDrivesMu.RLock()
|
||||
globalLocalDrives := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
// Initialize sync waitgroup.
|
||||
g := errgroup.WithNErrs(len(globalLocalDrives))
|
||||
g := errgroup.WithNErrs(len(localDrives))
|
||||
|
||||
// Disk states slices
|
||||
beforeState := make([]string, len(globalLocalDrives))
|
||||
afterState := make([]string, len(globalLocalDrives))
|
||||
beforeState := make([]string, len(localDrives))
|
||||
afterState := make([]string, len(localDrives))
|
||||
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range globalLocalDrives {
|
||||
for index := range localDrives {
|
||||
index := index
|
||||
g.Go(func() (serr error) {
|
||||
if globalLocalDrives[index] == nil {
|
||||
if localDrives[index] == nil {
|
||||
beforeState[index] = madmin.DriveStateOffline
|
||||
afterState[index] = madmin.DriveStateOffline
|
||||
return errDiskNotFound
|
||||
@@ -110,7 +109,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||
return nil
|
||||
}
|
||||
|
||||
_, serr = globalLocalDrives[index].StatVol(ctx, bucket)
|
||||
_, serr = localDrives[index].StatVol(ctx, bucket)
|
||||
if serr != nil {
|
||||
if serr == errDiskNotFound {
|
||||
beforeState[index] = madmin.DriveStateOffline
|
||||
@@ -138,7 +137,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||
res = madmin.HealResultItem{
|
||||
Type: madmin.HealItemBucket,
|
||||
Bucket: bucket,
|
||||
DiskCount: len(globalLocalDrives),
|
||||
DiskCount: len(localDrives),
|
||||
SetCount: -1, // explicitly set an invalid value -1, for bucket heal scenario
|
||||
}
|
||||
|
||||
@@ -150,25 +149,21 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||
for i := range beforeState {
|
||||
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
|
||||
UUID: "",
|
||||
Endpoint: globalLocalDrives[i].String(),
|
||||
Endpoint: localDrives[i].String(),
|
||||
State: beforeState[i],
|
||||
})
|
||||
}
|
||||
|
||||
// check dangling and delete bucket only if its not a meta bucket
|
||||
if !isMinioMetaBucketName(bucket) && !isAllBucketsNotFound(errs) && opts.Remove {
|
||||
g := errgroup.WithNErrs(len(globalLocalDrives))
|
||||
for index := range globalLocalDrives {
|
||||
g := errgroup.WithNErrs(len(localDrives))
|
||||
for index := range localDrives {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if globalLocalDrives[index] == nil {
|
||||
if localDrives[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
err := globalLocalDrives[index].DeleteVol(ctx, bucket, false)
|
||||
if errors.Is(err, errVolumeNotEmpty) {
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("While deleting dangling Bucket (%s), Drive %s:%s returned an error (%w)",
|
||||
bucket, globalLocalDrives[index].Hostname(), globalLocalDrives[index], err), "delete-dangling-bucket-"+bucket)
|
||||
}
|
||||
localDrives[index].DeleteVol(ctx, bucket, false)
|
||||
return nil
|
||||
}, index)
|
||||
}
|
||||
@@ -179,18 +174,18 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||
// Create the quorum lost volume only if its nor makred for delete
|
||||
if !opts.Remove {
|
||||
// Initialize sync waitgroup.
|
||||
g = errgroup.WithNErrs(len(globalLocalDrives))
|
||||
g = errgroup.WithNErrs(len(localDrives))
|
||||
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range globalLocalDrives {
|
||||
for index := range localDrives {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if beforeState[index] == madmin.DriveStateMissing {
|
||||
makeErr := globalLocalDrives[index].MakeVol(ctx, bucket)
|
||||
if makeErr == nil {
|
||||
err := localDrives[index].MakeVol(ctx, bucket)
|
||||
if err == nil {
|
||||
afterState[index] = madmin.DriveStateOk
|
||||
}
|
||||
return makeErr
|
||||
return err
|
||||
}
|
||||
return errs[index]
|
||||
}, index)
|
||||
@@ -202,7 +197,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
|
||||
for i := range afterState {
|
||||
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
|
||||
UUID: "",
|
||||
Endpoint: globalLocalDrives[i].String(),
|
||||
Endpoint: localDrives[i].String(),
|
||||
State: afterState[i],
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -318,7 +318,7 @@ func netperf(ctx context.Context, duration time.Duration) madmin.NetperfNodeResu
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(r.eof)
|
||||
xioutil.SafeClose(r.eof)
|
||||
wg.Wait()
|
||||
for {
|
||||
if globalNetPerfRX.ActiveConnections() == 0 {
|
||||
@@ -376,7 +376,7 @@ func siteNetperf(ctx context.Context, duration time.Duration) madmin.SiteNetPerf
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
close(r.eof)
|
||||
xioutil.SafeClose(r.eof)
|
||||
wg.Wait()
|
||||
for {
|
||||
if globalSiteNetPerfRX.ActiveConnections() == 0 || contextCanceled(ctx) {
|
||||
|
||||
@@ -40,7 +40,7 @@ type fanOutOptions struct {
|
||||
}
|
||||
|
||||
// fanOutPutObject takes an input source reader and fans out multiple PUT operations
|
||||
// based on the incoming fan-out request, a context cancelation by the caller
|
||||
// based on the incoming fan-out request, a context cancellation by the caller
|
||||
// would ensure all fan-out operations are canceled.
|
||||
func fanOutPutObject(ctx context.Context, bucket string, objectAPI ObjectLayer, fanOutEntries []minio.PutObjectFanOutEntry, fanOutBuf []byte, opts fanOutOptions) ([]ObjectInfo, []error) {
|
||||
errs := make([]error, len(fanOutEntries))
|
||||
|
||||
@@ -152,7 +152,7 @@ func testPostPolicyReservedBucketExploit(obj ObjectLayer, instanceType string, d
|
||||
req.Header.Set("Content-Type", contentTypeHdr)
|
||||
req.Header.Set("User-Agent", "Mozilla")
|
||||
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
|
||||
@@ -225,7 +225,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
if perr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != test.expectedStatus {
|
||||
@@ -284,7 +284,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
// Change the request body.
|
||||
req.Body = io.NopCloser(bytes.NewReader([]byte("Hello,")))
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
@@ -415,7 +415,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
if perr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
@@ -487,7 +487,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
|
||||
if perr != nil {
|
||||
t.Fatalf("Test %d: %s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", i+1, instanceType, perr)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
if rec.Code != testCase.expectedRespStatus {
|
||||
@@ -556,7 +556,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
|
||||
if perr != nil {
|
||||
t.Fatalf("%s: Failed to create HTTP request for PostPolicyHandler: <ERROR> %v", instanceType, perr)
|
||||
}
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic ofthe handler.
|
||||
// Since `apiRouter` satisfies `http.Handler` it has a ServeHTTP to execute the logic of the handler.
|
||||
// Call the ServeHTTP to execute the handler.
|
||||
apiRouter.ServeHTTP(rec, req)
|
||||
|
||||
|
||||
@@ -260,7 +260,7 @@ func parsePostPolicyForm(r io.Reader) (PostPolicyForm, error) {
|
||||
return parsedPolicy, nil
|
||||
}
|
||||
|
||||
// checkPolicyCond returns a boolean to indicate if a condition is satisified according
|
||||
// checkPolicyCond returns a boolean to indicate if a condition is satisfied according
|
||||
// to the passed operator
|
||||
func checkPolicyCond(op string, input1, input2 string) bool {
|
||||
switch op {
|
||||
|
||||
+11
-25
@@ -188,6 +188,7 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, endpoints Endpo
|
||||
|
||||
// Attempt to load all `format.json` from all disks.
|
||||
formatConfigs, sErrs := loadFormatErasureAll(storageDisks, false)
|
||||
|
||||
// Check if we have
|
||||
for i, sErr := range sErrs {
|
||||
// print the error, nonetheless, which is perhaps unhandled
|
||||
@@ -231,24 +232,6 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, endpoints Endpo
|
||||
return storageDisks, format, nil
|
||||
}
|
||||
|
||||
// Mark all root disks down
|
||||
markRootDisksAsDown(storageDisks, sErrs)
|
||||
|
||||
// Following function is added to fix a regressions which was introduced
|
||||
// in release RELEASE.2018-03-16T22-52-12Z after migrating v1 to v2 to v3.
|
||||
// This migration failed to capture '.This' field properly which indicates
|
||||
// the disk UUID association. Below function is called to handle and fix
|
||||
// this regression, for more info refer https://github.com/minio/minio/issues/5667
|
||||
if err = fixFormatErasureV3(storageDisks, endpoints, formatConfigs); err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// If any of the .This field is still empty, we return error.
|
||||
if formatErasureV3ThisEmpty(formatConfigs) {
|
||||
return nil, nil, errErasureV3ThisEmpty
|
||||
}
|
||||
|
||||
format, err = getFormatErasureInQuorum(formatConfigs)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
@@ -260,7 +243,7 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, endpoints Endpo
|
||||
if !firstDisk {
|
||||
return nil, nil, errNotFirstDisk
|
||||
}
|
||||
if err = formatErasureFixDeploymentID(endpoints, storageDisks, format); err != nil {
|
||||
if err = formatErasureFixDeploymentID(endpoints, storageDisks, format, formatConfigs); err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -289,9 +272,12 @@ func waitForFormatErasure(firstDisk bool, endpoints Endpoints, poolCount, setCou
|
||||
return time.Now().Round(time.Second).Sub(formatStartTime).String()
|
||||
}
|
||||
|
||||
var tries int
|
||||
var verboseLogging bool
|
||||
storageDisks, format, err := connectLoadInitFormats(verboseLogging, firstDisk, endpoints, poolCount, setCount, setDriveCount, deploymentID, distributionAlgo)
|
||||
var (
|
||||
tries int
|
||||
verbose bool
|
||||
)
|
||||
|
||||
storageDisks, format, err := connectLoadInitFormats(verbose, firstDisk, endpoints, poolCount, setCount, setDriveCount, deploymentID, distributionAlgo)
|
||||
if err == nil {
|
||||
return storageDisks, format, nil
|
||||
}
|
||||
@@ -304,12 +290,12 @@ func waitForFormatErasure(firstDisk bool, endpoints Endpoints, poolCount, setCou
|
||||
|
||||
for {
|
||||
// Only log once every 10 iterations, then reset the tries count.
|
||||
verboseLogging = tries >= 10
|
||||
if verboseLogging {
|
||||
verbose = tries >= 10
|
||||
if verbose {
|
||||
tries = 1
|
||||
}
|
||||
|
||||
storageDisks, format, err := connectLoadInitFormats(verboseLogging, firstDisk, endpoints, poolCount, setCount, setDriveCount, deploymentID, distributionAlgo)
|
||||
storageDisks, format, err := connectLoadInitFormats(verbose, firstDisk, endpoints, poolCount, setCount, setDriveCount, deploymentID, distributionAlgo)
|
||||
if err == nil {
|
||||
return storageDisks, format, nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func registerDistErasureRouters(router *mux.Router, endpointServerPools Endpoint
|
||||
registerPeerS3Handlers(router)
|
||||
|
||||
// Register bootstrap REST router for distributed setups.
|
||||
registerBootstrapRESTHandlers(router)
|
||||
registerBootstrapRESTHandlers(globalGrid.Load())
|
||||
|
||||
// Register distributed namespace lock routers.
|
||||
registerLockRESTHandlers()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user