Compare commits

..

26 Commits

Author SHA1 Message Date
Anis Elleuch 1d2ff46a89 Ensure lock/versioning permissions when creating a bucket (#15432)
Currently, the code doesn't check if the user creating a bucket with
locking feature has bucket locking and versioning permissions enabled,
adding it in accordance with S3 spec.

https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html

Object Lock - If ObjectLockEnabledForBucket is set to true in your CreateBucket request,
s3:PutBucketObjectLockConfiguration and s3:PutBucketVersioning permissions are required.
2022-08-05 16:27:09 -07:00
Harshavardhana 8f7c739328 feat: add SpeedTest ResponseTimes and TTFB (#15479)
Capture average, p50, p99, p999 response times
and ttfb values. These are needed for latency
measurements and overall understanding of our
speedtest results.
2022-08-05 09:40:03 -07:00
Poorna 1beea3daba fix: import bucket metadata import to return a summary (#15462) 2022-08-05 01:52:50 -07:00
Minio Trusted 1ffd063939 update CREDITS for latest deps 2022-08-04 23:30:31 -07:00
Aditya Manthramurthy 3d94c38ec4 Add env variables to configuration APIs output (#15465)
Config export and config get APIs now include environment 
variables set on the server
2022-08-04 22:21:52 -07:00
Harshavardhana f4af2d3cdc fix: decodeDirObject() in single drive DeleteObjects() call (#15477)
Thanks to @bh4t for reproducing this issue.
2022-08-04 18:57:43 -07:00
ebozduman b57e7321e7 Replaces 'disk'=>'drive' visible to end user (#15464) 2022-08-04 16:10:08 -07:00
Anis Elleuch e93867488b actively cancel listIAMConfigItems to avoid goroutine leak (#15471)
listConfigItems creates a goroutine but sometimes callers will
exit without properly asking listAllIAMConfigItems() to stop sending
results, hence a goroutine leak.

Create a new context and cancel it for each listAllIAMConfigItems
call.
2022-08-04 13:20:43 -07:00
Minio Trusted c08790edd2 upgrade helm v4.0.10 2022-08-04 09:09:22 -07:00
Kourosh Tafreshi a46baddbc4 Add OIDC to the HelmChart (#15469) 2022-08-04 09:07:51 -07:00
Harshavardhana 3bd9615d0e fix: log if there is readDir() failure with ListBuckets (#15461)
This is actionable and must be logged.

Bonus: also honor umask by using 0o666 for all Open() syscalls.
2022-08-04 07:23:05 -07:00
Minio Trusted 2871cb5775 upgrade helm v4.0.9 2022-08-02 23:10:44 -07:00
Harshavardhana a6e0ec4e6f Add support converting non-inlined to inlined (#15444)
This is a feature to allow for inode compaction on
large clusters that use a lot of small files spread
across a large heirarchy.
2022-08-02 23:10:22 -07:00
Minio Trusted e956369c4e Update yaml files to latest version RELEASE.2022-08-02T23-59-16Z 2022-08-03 01:45:42 +00:00
Minio Trusted 76f950c663 upgrade to minio-go/v7 v7.0.34 2022-08-02 16:59:16 -07:00
Andreas Auernhammer d774a3309b kes: automatically reload KES client certificate (#15450)
This commit adds support for automatically reloading
the MinIO client certificate for authentication to KES.

The client certificate will now be reloaded:
 - when the private key / certificate file changes
 - when a SIGHUP signal is received
 - every 15 minutes

Fixes #14869

Signed-off-by: Andreas Auernhammer <hi@aead.dev>
2022-08-02 16:58:09 -07:00
Anis Elleuch b3edb25377 bloom: healObject to mark a path dirty only for dangling objects (#15458)
The path is marked dirty automatically when healObject() is called, which is
wrong. HealObject() is called during self-healing and this will lead to
an increase in the false positive result of the bloom filter.

Also move NSUpdated() from renameData() and call it directly in
CompleteMultipart and PutObject, this is not a functional change but
it will make it less prone to errors in the future.
2022-08-02 16:57:39 -07:00
Harshavardhana 026b87e39b do not crash for unwrapErrs return nil (#15456)
fixes #15454
2022-08-02 15:10:11 -07:00
Harshavardhana 53a816b17a fix: readdir fallback on root of the drive (#15457)
fixes #15452
2022-08-02 14:57:36 -07:00
Harshavardhana 043aaa792d fix: intrument os.OpenFile differently for Reads and Writes (#15449)
allows us to trace latency for READs or WRITEs
2022-08-01 13:22:43 -07:00
dorman aad9cb208a helm: modify user secret volumes mount path name (#15443) 2022-08-01 12:28:04 -07:00
Minio Trusted edf081c6a2 update minio-go v7.0.33 2022-08-01 10:44:35 -07:00
Harshavardhana fd349103e8 fix: allow P-384/P-512 constant time implementation (#15445)
since go1.18.x P-384/P-512 are now constant time
implementations, enable them.
2022-08-01 09:27:16 -07:00
Anis Elleuch 10b49eb4fb Fix resetting a config with a non default target name (#15448)
mc admin config reset <alias> notify_webhook:something was not working
properly.

The reason is that GetSubSys() was not calculating the target
name properly because it is quitting early when the number of config
inputs ('notify_webhook:something' in this case) is equal to 1.

This commit will make the code calculates always calculate the target
name if found.
2022-08-01 07:52:23 -07:00
Anis Elleuch 3856d078d2 fix: set 20000 as maximum parallel event calls (#15435)
This is needed to avoid consuming a lot of goroutines when a target is
very slow or there is a bug in a target library.
2022-07-30 12:12:33 -07:00
Minio Trusted 6b4cb35f4f Update yaml files to latest version RELEASE.2022-07-30T05-21-40Z 2022-07-30 05:50:06 +00:00
99 changed files with 2527 additions and 603 deletions
+1
View File
@@ -49,3 +49,4 @@ jobs:
make verify-healing
make verify-healing-inconsistent-versions
make verify-healing-with-root-disks
make verify-healing-with-rewrite
+1250 -11
View File
File diff suppressed because it is too large Load Diff
+8 -3
View File
@@ -15,7 +15,7 @@ checks: ## check dependencies
@(env bash $(PWD)/buildscripts/checkdeps.sh)
help: ## print this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-40s\033[0m %s\n", $$1, $$2}'
getdeps: ## fetch necessary dependencies
@mkdir -p ${GOPATH}/bin
@@ -90,11 +90,16 @@ verify-healing: ## verify healing and replacing disks with minio binary
@(env bash $(PWD)/buildscripts/verify-healing.sh)
@(env bash $(PWD)/buildscripts/unaligned-healing.sh)
verify-healing-with-root-disks:
@echo "Verify healing with root disks"
verify-healing-with-root-disks: ## verify healing root disks
@echo "Verify healing with root drives"
@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-with-root-disks.sh)
verify-healing-with-rewrite: ## verify healing to rewrite old xl.meta -> new xl.meta
@echo "Verify healing with rewrite"
@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/rewrite-old-new.sh)
verify-healing-inconsistent-versions: ## verify resolving inconsistent versions
@echo "Verify resolving inconsistent versions build with race"
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
+87
View File
@@ -0,0 +1,87 @@
//go:build ignore
// +build ignore
//
// MinIO Object Storage (c) 2022 MinIO, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"time"
"github.com/minio/madmin-go"
)
func main() {
// Note: YOUR-ACCESSKEYID, YOUR-SECRETACCESSKEY are
// dummy values, please replace them with original values.
// API requests are secure (HTTPS) if secure=true and insecure (HTTP) otherwise.
// New returns an MinIO Admin client object.
madmClnt, err := madmin.New(os.Args[1], os.Args[2], os.Args[3], false)
if err != nil {
log.Fatalln(err)
}
opts := madmin.HealOpts{
Recursive: true, // recursively heal all objects at 'prefix'
Remove: true, // remove content that has lost quorum and not recoverable
Recreate: true, // rewrite all old non-inlined xl.meta to new xl.meta
ScanMode: madmin.HealNormalScan, // by default do not do 'deep' scanning
}
start, _, err := madmClnt.Heal(context.Background(), "healing-rewrite-bucket", "", opts, "", false, false)
if err != nil {
log.Fatalln(err)
}
fmt.Println("Healstart sequence ===")
enc := json.NewEncoder(os.Stdout)
if err = enc.Encode(&start); err != nil {
log.Fatalln(err)
}
fmt.Println()
for {
_, status, err := madmClnt.Heal(context.Background(), "healing-rewrite-bucket", "", opts, start.ClientToken, false, false)
if status.Summary == "finished" {
fmt.Println("Healstatus on items ===")
for _, item := range status.Items {
if err = enc.Encode(&item); err != nil {
log.Fatalln(err)
}
}
break
}
if status.Summary == "stopped" {
fmt.Println("Healstatus on items ===")
fmt.Println("Heal failed with", status.FailureDetail)
break
}
for _, item := range status.Items {
if err = enc.Encode(&item); err != nil {
log.Fatalln(err)
}
}
time.Sleep(time.Second)
}
}
+151
View File
@@ -0,0 +1,151 @@
#!/bin/bash -e
set -E
set -o pipefail
set -x
WORK_DIR="$PWD/.verify-$RANDOM"
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO_OLD=( "$PWD/minio.RELEASE.2020-10-28T08-16-50Z" --config-dir "$MINIO_CONFIG_DIR" server )
MINIO=( "$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server )
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
function download_old_release() {
if [ ! -f minio.RELEASE.2020-10-28T08-16-50Z ]; then
curl --silent -O https://dl.minio.io/server/minio/release/linux-amd64/archive/minio.RELEASE.2020-10-28T08-16-50Z
chmod a+x minio.RELEASE.2020-10-28T08-16-50Z
fi
}
function verify_rewrite() {
start_port=$1
export MINIO_ACCESS_KEY=minio
export MINIO_SECRET_KEY=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_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 10
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
"${WORK_DIR}/mc" mb minio/healing-rewrite-bucket --quiet --with-lock
"${WORK_DIR}/mc" cp \
buildscripts/verify-build.sh \
minio/healing-rewrite-bucket/ \
--disable-multipart --quiet
"${WORK_DIR}/mc" cp \
buildscripts/verify-build.sh \
minio/healing-rewrite-bucket/ \
--disable-multipart --quiet
"${WORK_DIR}/mc" cp \
buildscripts/verify-build.sh \
minio/healing-rewrite-bucket/ \
--disable-multipart --quiet
kill ${pid}
sleep 3
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" > "${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
sleep 10
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
go build ./docs/debugging/s3-check-md5/
if ! ./s3-check-md5 \
-debug \
-versions \
-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" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
"${WORK_DIR}/mc" mb play/inspects
"${WORK_DIR}/mc" mirror inspects play/inspects
purge "$WORK_DIR"
exit 1
fi
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
sleep 1
if ! ./s3-check-md5 \
-debug \
-versions \
-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" admin inspect minio/healing-rewrite-bucket/verify-build.sh/**)
"${WORK_DIR}/mc" mb play/inspects
"${WORK_DIR}/mc" mirror inspects play/inspects
purge "$WORK_DIR"
exit 1
fi
kill ${pid}
}
function main() {
download_old_release
start_port=$(shuf -i 10000-65000 -n 1)
verify_rewrite ${start_port}
}
function purge()
{
rm -rf "$1"
}
( main "$@" )
rv=$?
purge "$WORK_DIR"
exit "$rv"
+166 -145
View File
@@ -374,7 +374,7 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
bucket := pathClean(r.Form.Get("bucket"))
if !globalIsErasure {
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
@@ -619,6 +619,39 @@ func (a adminAPIHandlers) ExportBucketMetadataHandler(w http.ResponseWriter, r *
}
}
type importMetaReport struct {
madmin.BucketMetaImportErrs
}
func (i *importMetaReport) SetStatus(bucket, fname string, err error) {
st := i.Buckets[bucket]
var errMsg string
if err != nil {
errMsg = err.Error()
}
switch fname {
case bucketPolicyConfig:
st.Policy = madmin.MetaStatus{IsSet: true, Err: errMsg}
case bucketNotificationConfig:
st.Notification = madmin.MetaStatus{IsSet: true, Err: errMsg}
case bucketLifecycleConfig:
st.Lifecycle = madmin.MetaStatus{IsSet: true, Err: errMsg}
case bucketSSEConfig:
st.SSEConfig = madmin.MetaStatus{IsSet: true, Err: errMsg}
case bucketTaggingConfig:
st.Tagging = madmin.MetaStatus{IsSet: true, Err: errMsg}
case bucketQuotaConfigFile:
st.Quota = madmin.MetaStatus{IsSet: true, Err: errMsg}
case objectLockConfig:
st.ObjectLock = madmin.MetaStatus{IsSet: true, Err: errMsg}
case bucketVersioningConfig:
st.Versioning = madmin.MetaStatus{IsSet: true, Err: errMsg}
default:
st.Err = errMsg
}
i.Buckets[bucket] = st
}
// ImportBucketMetadataHandler - imports all bucket metadata from a zipped file and overwrite bucket metadata config
// There are some caveats regarding the following:
// 1. object lock config - object lock should have been specified at time of bucket creation. Only default retention settings are imported here.
@@ -628,9 +661,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
ctx := newContext(r, w, "ImportBucketMetadata")
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
bucket := pathClean(r.Form.Get("bucket"))
if !globalIsErasure {
if globalIsGateway {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
return
}
@@ -651,37 +683,36 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
return
}
bucketMap := make(map[string]struct{}, 1)
rpt := importMetaReport{
madmin.BucketMetaImportErrs{
Buckets: make(map[string]madmin.BucketStatus, len(zr.File)),
},
}
// import object lock config if any - order of import matters here.
for _, file := range zr.File {
slc := strings.Split(file.Name, slashSeparator)
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
b, fileName := slc[0], slc[1]
if bucket == "" { // use bucket requested in query parameters if specified. Otherwise default bucket name to directory name within zip
bucket = b
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
continue
}
bucket, fileName := slc[0], slc[1]
switch fileName {
case objectLockConfig:
reader, err := file.Open()
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
config, err := objectlock.ParseObjectLockConfig(reader)
if err != nil {
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
apiErr.Description = err.Error()
writeErrorResponse(ctx, w, apiErr, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
continue
}
configData, err := xml.Marshal(config)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
if _, ok := bucketMap[bucket]; !ok {
opts := MakeBucketOptions{
@@ -690,8 +721,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
err = objectAPI.MakeBucketWithLocation(ctx, bucket, opts)
if err != nil {
if _, ok := err.(BucketExists); !ok {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
}
bucketMap[bucket] = struct{}{}
@@ -699,15 +730,16 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
// Deny object locking configuration settings on existing buckets without object lock enabled.
if _, _, err = globalBucketMetadataSys.GetObjectLockConfig(bucket); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, configData)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
// Call site replication hook.
//
@@ -720,8 +752,8 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
ObjectLockConfig: &cfgStr,
UpdatedAt: updatedAt,
}); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
}
}
@@ -730,162 +762,145 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
for _, file := range zr.File {
slc := strings.Split(file.Name, slashSeparator)
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
b, fileName := slc[0], slc[1]
if bucket == "" { // use bucket requested in query parameters if specified. Otherwise default bucket name to directory name within zip
bucket = b
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
continue
}
bucket, fileName := slc[0], slc[1]
switch fileName {
case bucketVersioningConfig:
reader, err := file.Open()
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
v, err := versioning.ParseConfig(io.LimitReader(reader, maxBucketVersioningConfigSize))
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
if _, ok := bucketMap[bucket]; !ok {
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
if err != nil {
if err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{}); err != nil {
if _, ok := err.(BucketExists); !ok {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
}
bucketMap[bucket] = struct{}{}
}
if globalSiteReplicationSys.isEnabled() && v.Suspended() {
writeErrorResponse(ctx, w, APIError{
Code: "InvalidBucketState",
Description: "Cluster replication is enabled for this site, so the versioning state cannot be changed.",
HTTPStatusCode: http.StatusConflict,
}, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("Cluster replication is enabled for this site, so the versioning state cannot be suspended."))
continue
}
if rcfg, _ := globalBucketObjectLockSys.Get(bucket); rcfg.LockEnabled && v.Suspended() {
writeErrorResponse(ctx, w, APIError{
Code: "InvalidBucketState",
Description: "An Object Lock configuration is present on this bucket, so the versioning state cannot be changed.",
HTTPStatusCode: http.StatusConflict,
}, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("An Object Lock configuration is present on this bucket, so the versioning state cannot be suspended."))
continue
}
if _, err := getReplicationConfig(ctx, bucket); err == nil && v.Suspended() {
writeErrorResponse(ctx, w, APIError{
Code: "InvalidBucketState",
Description: "A replication configuration is present on this bucket, so the versioning state cannot be changed.",
HTTPStatusCode: http.StatusConflict,
}, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("A replication configuration is present on this bucket, so the versioning state cannot be suspended."))
continue
}
configData, err := xml.Marshal(v)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
continue
}
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, configData); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
}
}
for _, file := range zr.File {
reader, err := file.Open()
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, ""), r.URL)
return
rpt.SetStatus(file.Name, "", err)
continue
}
sz := file.FileInfo().Size()
slc := strings.Split(file.Name, slashSeparator)
if len(slc) != 2 { // expecting bucket/configfile in the zipfile
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
b, fileName := slc[0], slc[1]
if bucket == "" { // use bucket requested in query parameters if specified. Otherwise default bucket name to directory name within zip
bucket = b
rpt.SetStatus(file.Name, "", fmt.Errorf("malformed zip - expecting format bucket/<config.json>"))
continue
}
bucket, fileName := slc[0], slc[1]
// create bucket if it does not exist yet.
if _, ok := bucketMap[bucket]; !ok {
err = objectAPI.MakeBucketWithLocation(ctx, bucket, MakeBucketOptions{})
if err != nil {
if _, ok := err.(BucketExists); !ok {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, "", err)
continue
}
}
bucketMap[bucket] = struct{}{}
}
if _, ok := bucketMap[bucket]; !ok {
continue
}
switch fileName {
case bucketNotificationConfig:
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region, globalNotificationSys.targetList)
if err != nil {
apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
if event.IsEventError(err) {
apiErr = importError(ctx, err, file.Name, bucket)
}
writeErrorResponse(ctx, w, apiErr, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
continue
}
configData, err := xml.Marshal(config)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketNotificationConfig, configData); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rulesMap := config.ToRulesMap()
globalNotificationSys.AddRulesMap(bucket, rulesMap)
rpt.SetStatus(bucket, fileName, nil)
case bucketPolicyConfig:
// Error out if Content-Length is beyond allowed size.
if sz > maxBucketPolicySize {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrPolicyTooLarge), r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrPolicyTooLarge.String()))
continue
}
bucketPolicyBytes, err := ioutil.ReadAll(io.LimitReader(reader, sz))
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
bucketPolicy, err := policy.ParseConfig(bytes.NewReader(bucketPolicyBytes), bucket)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
// Version in policy must not be empty
if bucketPolicy.Version == "" {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPolicy), r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf(ErrMalformedPolicy.String()))
continue
}
configData, err := json.Marshal(bucketPolicy)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, configData)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
// Call site replication hook.
if err = globalSiteReplicationSys.BucketMetaHook(ctx, madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypePolicy,
@@ -893,56 +908,51 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
Policy: bucketPolicyBytes,
UpdatedAt: updatedAt,
}); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
case bucketLifecycleConfig:
bucketLifecycle, err := lifecycle.ParseLifecycleConfig(io.LimitReader(reader, sz))
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
// Validate the received bucket policy document
if err = bucketLifecycle.Validate(); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
// Validate the transition storage ARNs
if err = validateTransitionTier(bucketLifecycle); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
configData, err := xml.Marshal(bucketLifecycle)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
if _, err = globalBucketMetadataSys.Update(ctx, bucket, bucketLifecycleConfig, configData); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
case bucketSSEConfig:
// Parse bucket encryption xml
encConfig, err := validateBucketSSEConfig(io.LimitReader(reader, maxBucketSSEConfigSize))
if err != nil {
apiErr := APIError{
Code: "MalformedXML",
Description: fmt.Sprintf("%s (%s)", errorCodes[ErrMalformedXML].Description, err),
HTTPStatusCode: errorCodes[ErrMalformedXML].HTTPStatusCode,
}
writeErrorResponse(ctx, w, apiErr, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
continue
}
// Return error if KMS is not initialized
if GlobalKMS == nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrKMSNotConfigured), r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s", errorCodes[ErrKMSNotConfigured].Description))
continue
}
kmsKey := encConfig.KeyID()
if kmsKey != "" {
@@ -950,26 +960,27 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
_, err := GlobalKMS.GenerateKey(ctx, kmsKey, kmsContext)
if err != nil {
if errors.Is(err, kes.ErrKeyNotFound) {
writeErrorResponse(ctx, w, importError(ctx, errKMSKeyNotFound, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, errKMSKeyNotFound)
continue
}
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
}
configData, err := xml.Marshal(encConfig)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
// Store the bucket encryption configuration in the object layer
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, configData)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
// Call site replication hook.
//
@@ -982,29 +993,30 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
SSEConfig: &cfgStr,
UpdatedAt: updatedAt,
}); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
case bucketTaggingConfig:
tags, err := tags.ParseBucketXML(io.LimitReader(reader, sz))
if err != nil {
apiErr := errorCodes.ToAPIErrWithErr(ErrMalformedXML, fmt.Errorf("error importing %s with %w", file.Name, err))
writeErrorResponse(ctx, w, apiErr, r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
continue
}
configData, err := xml.Marshal(tags)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, configData)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
// Call site replication hook.
//
// We encode the xml bytes as base64 to ensure there are no encoding
@@ -1016,32 +1028,33 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
Tags: &cfgStr,
UpdatedAt: updatedAt,
}); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
case bucketQuotaConfigFile:
data, err := ioutil.ReadAll(reader)
if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
quotaConfig, err := parseBucketQuota(bucket, data)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
if quotaConfig.Type == "fifo" {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
rpt.SetStatus(bucket, fileName, fmt.Errorf("Detected older 'fifo' quota config, 'fifo' feature is removed and not supported anymore"))
continue
}
updatedAt, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, data)
if err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
rpt.SetStatus(bucket, fileName, nil)
bucketMeta := madmin.SRBucketMeta{
Type: madmin.SRBucketMetaTypeQuotaConfig,
@@ -1055,11 +1068,19 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
// Call site replication hook.
if err = globalSiteReplicationSys.BucketMetaHook(ctx, bucketMeta); err != nil {
writeErrorResponse(ctx, w, importError(ctx, err, file.Name, bucket), r.URL)
return
rpt.SetStatus(bucket, fileName, err)
continue
}
}
}
rptData, err := json.Marshal(rpt.BucketMetaImportErrs)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, rptData)
}
// ReplicationDiffHandler - POST returns info on unreplicated versions for a remote target ARN
+22 -29
View File
@@ -189,15 +189,20 @@ func (a adminAPIHandlers) GetConfigKVHandler(w http.ResponseWriter, r *http.Requ
cfg := globalServerConfig.Clone()
vars := mux.Vars(r)
buf := &bytes.Buffer{}
cw := config.NewConfigWriteTo(cfg, vars["key"])
if _, err := cw.WriteTo(buf); err != nil {
subSys := vars["key"]
subSysConfigs, err := cfg.GetSubsysInfo(subSys)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
var s strings.Builder
for _, subSysConfig := range subSysConfigs {
subSysConfig.AddString(&s, false)
}
password := cred.SecretKey
econfigData, err := madmin.EncryptData(password, buf.Bytes())
econfigData, err := madmin.EncryptData(password, []byte(s.String()))
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
@@ -427,42 +432,30 @@ func (a adminAPIHandlers) GetConfigHandler(w http.ResponseWriter, r *http.Reques
count += len(cfg[hkv.Key])
}
for _, hkv := range hkvs {
v := cfg[hkv.Key]
for target, kv := range v {
off := kv.Get(config.Enable) == config.EnableOff
// We ignore the error below, as we cannot get one.
cfgSubsysItems, _ := cfg.GetSubsysInfo(hkv.Key)
for _, item := range cfgSubsysItems {
off := item.Params.Get(config.Enable) == config.EnableOff
switch hkv.Key {
case config.EtcdSubSys:
off = !etcd.Enabled(kv)
off = !etcd.Enabled(item.Params)
case config.CacheSubSys:
off = !cache.Enabled(kv)
off = !cache.Enabled(item.Params)
case config.StorageClassSubSys:
off = !storageclass.Enabled(kv)
off = !storageclass.Enabled(item.Params)
case config.PolicyPluginSubSys:
off = !polplugin.Enabled(kv)
off = !polplugin.Enabled(item.Params)
case config.IdentityOpenIDSubSys:
off = !openid.Enabled(kv)
off = !openid.Enabled(item.Params)
case config.IdentityLDAPSubSys:
off = !xldap.Enabled(kv)
off = !xldap.Enabled(item.Params)
case config.IdentityTLSSubSys:
off = !globalSTSTLSConfig.Enabled
case config.IdentityPluginSubSys:
off = !idplugin.Enabled(kv)
}
if off {
s.WriteString(config.KvComment)
s.WriteString(config.KvSpaceSeparator)
}
s.WriteString(hkv.Key)
if target != config.Default {
s.WriteString(config.SubSystemSeparator)
s.WriteString(target)
}
s.WriteString(config.KvSpaceSeparator)
s.WriteString(kv.String())
count--
if count > 0 {
s.WriteString(config.KvNewline)
off = !idplugin.Enabled(item.Params)
}
item.AddString(&s, off)
}
}
+1 -1
View File
@@ -1158,7 +1158,7 @@ var errorCodes = errorCodeMap{
// MinIO extensions.
ErrStorageFull: {
Code: "XMinioStorageFull",
Description: "Storage backend has reached its minimum free disk threshold. Please delete a few objects to proceed.",
Description: "Storage backend has reached its minimum free drive threshold. Please delete a few objects to proceed.",
HTTPStatusCode: http.StatusInsufficientStorage,
},
ErrRequestBodyParse: {
+8 -8
View File
@@ -87,7 +87,7 @@ type healingTracker struct {
// The disk ID will be validated against the loaded one.
func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker, error) {
if disk == nil {
return nil, errors.New("loadHealingTracker: nil disk given")
return nil, errors.New("loadHealingTracker: nil drive given")
}
diskID, err := disk.GetDiskID()
if err != nil {
@@ -104,7 +104,7 @@ func loadHealingTracker(ctx context.Context, disk StorageAPI) (*healingTracker,
return nil, err
}
if h.ID != diskID && h.ID != "" {
return nil, fmt.Errorf("loadHealingTracker: disk id mismatch expected %s, got %s", h.ID, diskID)
return nil, fmt.Errorf("loadHealingTracker: drive id mismatch expected %s, got %s", h.ID, diskID)
}
h.disk = disk
h.ID = diskID
@@ -129,7 +129,7 @@ func newHealingTracker(disk StorageAPI) *healingTracker {
// If the tracker has been deleted an error is returned.
func (h *healingTracker) update(ctx context.Context) error {
if h.disk.Healing() == nil {
return fmt.Errorf("healingTracker: disk %q is not marked as healing", h.ID)
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
}
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
h.ID, _ = h.disk.GetDiskID()
@@ -310,7 +310,7 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
}
// Prevent parallel erasure set healing
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-disk-healing/%s/%d/%d", endpoint, poolIdx, setIdx))
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%s/%d/%d", endpoint, poolIdx, setIdx))
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
if err != nil {
return err
@@ -337,14 +337,14 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
})
if serverDebugLog {
logger.Info("Healing disk '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
logger.Info("Healing drive '%v' on %s pool", disk, humanize.Ordinal(poolIdx+1))
}
// Load healing tracker in this disk
tracker, err := loadHealingTracker(ctx, disk)
if err != nil {
// So someone changed the drives underneath, healing tracker missing.
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', disk was swapped again on %s pool: %w",
logger.LogIf(ctx, fmt.Errorf("Healing tracker missing on '%s', drive was swapped again on %s pool: %w",
disk, humanize.Ordinal(poolIdx+1), err))
tracker = newHealingTracker(disk)
}
@@ -369,9 +369,9 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
}
if tracker.ItemsFailed > 0 {
logger.Info("Healing disk '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
logger.Info("Healing drive '%s' failed (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
} else {
logger.Info("Healing disk '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
logger.Info("Healing drive '%s' complete (healed: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsFailed)
}
if serverDebugLog {
+1 -1
View File
@@ -180,7 +180,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
b.h.Write(buf)
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s - content hash does not match - expected %s, got %s",
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s - content hash does not match - expected %s, got %s",
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
return 0, errFileCorrupt
}
+4 -4
View File
@@ -38,12 +38,12 @@ type wholeBitrotWriter struct {
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
if err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s returned %w", b.disk, err))
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
return 0, err
}
_, err = b.Hash.Write(p)
if err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s returned %w", b.disk, err))
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
return 0, err
}
return len(p), nil
@@ -72,12 +72,12 @@ func (b *wholeBitrotReader) ReadAt(buf []byte, offset int64) (n int, err error)
if b.buf == nil {
b.buf = make([]byte, b.tillOffset-offset)
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
return 0, err
}
}
if len(b.buf) < len(buf) {
logger.LogIf(GlobalContext, fmt.Errorf("Disk: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
return 0, errLessData
}
n = copy(buf, b.buf)
+20 -1
View File
@@ -727,11 +727,30 @@ func (api objectAPIHandlers) PutBucketHandler(w http.ResponseWriter, r *http.Req
}
}
if s3Error := checkRequestAuthType(ctx, r, policy.CreateBucketAction, bucket, ""); s3Error != ErrNone {
cred, owner, s3Error := checkRequestAuthTypeCredential(ctx, r, policy.CreateBucketAction, bucket, "")
if s3Error != ErrNone {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL)
return
}
if objectLockEnabled {
// Creating a bucket with locking requires the user having more permissions
for _, action := range []iampolicy.Action{iampolicy.PutBucketObjectLockConfigurationAction, iampolicy.PutBucketVersioningAction} {
if !globalIAMSys.IsAllowed(iampolicy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: action,
ConditionValues: getConditionValues(r, "", cred.AccessKey, cred.Claims),
BucketName: bucket,
IsOwner: owner,
Claims: cred.Claims,
}) {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
return
}
}
}
// Parse incoming location constraint.
location, s3Error := parseLocationConstraint(r)
if s3Error != ErrNone {
+1 -1
View File
@@ -1992,7 +1992,7 @@ func (p *ReplicationPool) updateResyncStatus(ctx context.Context, objectAPI Obje
if updt {
brs.LastUpdate = now
if err := saveResyncStatus(ctx, bucket, brs, objectAPI); err != nil {
logger.LogIf(ctx, fmt.Errorf("Could not save resync metadata to disk for %s - %w", bucket, err))
logger.LogIf(ctx, fmt.Errorf("Could not save resync metadata to drive for %s - %w", bucket, err))
continue
}
}
+47 -35
View File
@@ -508,8 +508,7 @@ func parsEnvEntry(envEntry string) (envKV, error) {
Skip: true,
}, nil
}
const envSeparator = "="
envTokens := strings.SplitN(strings.TrimSpace(strings.TrimPrefix(envEntry, "export")), envSeparator, 2)
envTokens := strings.SplitN(strings.TrimSpace(strings.TrimPrefix(envEntry, "export")), config.EnvSeparator, 2)
if len(envTokens) != 2 {
return envKV{}, fmt.Errorf("envEntry malformed; %s, expected to be of form 'KEY=value'", envEntry)
}
@@ -535,7 +534,7 @@ func parsEnvEntry(envEntry string) (envKV, error) {
// the environment values from a file, in the form "key, value".
// in a structured form.
func minioEnvironFromFile(envConfigFile string) ([]envKV, error) {
f, err := os.Open(envConfigFile)
f, err := Open(envConfigFile)
if err != nil {
return nil, err
}
@@ -830,43 +829,56 @@ func handleKMSConfig() {
endpoints = append(endpoints, strings.Join(lbls, ""))
}
}
// Manually load the certificate and private key into memory.
// We need to check whether the private key is encrypted, and
// if so, decrypt it using the user-provided password.
certBytes, err := os.ReadFile(env.Get(config.EnvKESClientCert, ""))
if err != nil {
logger.Fatal(err, "Unable to load KES client certificate as specified by the shell environment")
}
keyBytes, err := os.ReadFile(env.Get(config.EnvKESClientKey, ""))
if err != nil {
logger.Fatal(err, "Unable to load KES client private key as specified by the shell environment")
}
privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes))
if len(rest) != 0 {
logger.Fatal(errors.New("private key contains additional data"), "Unable to load KES client private key as specified by the shell environment")
}
if x509.IsEncryptedPEMBlock(privateKeyPEM) {
keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(config.EnvKESClientPassword, "")))
if err != nil {
logger.Fatal(err, "Unable to decrypt KES client private key as specified by the shell environment")
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes})
}
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
logger.Fatal(err, "Unable to load KES client certificate as specified by the shell environment")
}
rootCAs, err := certs.GetRootCAs(env.Get(config.EnvKESServerCA, globalCertsCADir.Get()))
if err != nil {
logger.Fatal(err, fmt.Sprintf("Unable to load X.509 root CAs for KES from %q", env.Get(config.EnvKESServerCA, globalCertsCADir.Get())))
}
loadX509KeyPair := func(certFile, keyFile string) (tls.Certificate, error) {
// Manually load the certificate and private key into memory.
// We need to check whether the private key is encrypted, and
// if so, decrypt it using the user-provided password.
certBytes, err := os.ReadFile(certFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
}
keyBytes, err := os.ReadFile(keyFile)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client private key as specified by the shell environment: %v", err)
}
privateKeyPEM, rest := pem.Decode(bytes.TrimSpace(keyBytes))
if len(rest) != 0 {
return tls.Certificate{}, errors.New("Unable to load KES client private key as specified by the shell environment: private key contains additional data")
}
if x509.IsEncryptedPEMBlock(privateKeyPEM) {
keyBytes, err = x509.DecryptPEMBlock(privateKeyPEM, []byte(env.Get(config.EnvKESClientPassword, "")))
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to decrypt KES client private key as specified by the shell environment: %v", err)
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: privateKeyPEM.Type, Bytes: keyBytes})
}
certificate, err := tls.X509KeyPair(certBytes, keyBytes)
if err != nil {
return tls.Certificate{}, fmt.Errorf("Unable to load KES client certificate as specified by the shell environment: %v", err)
}
return certificate, nil
}
reloadCertEvents := make(chan tls.Certificate, 1)
certificate, err := certs.NewCertificate(env.Get(config.EnvKESClientCert, ""), env.Get(config.EnvKESClientKey, ""), loadX509KeyPair)
if err != nil {
logger.Fatal(err, "Failed to load KES client certificate")
}
certificate.Watch(context.Background(), 15*time.Minute, syscall.SIGHUP)
certificate.Notify(reloadCertEvents)
defaultKeyID := env.Get(config.EnvKESKeyName, "")
KMS, err := kms.NewWithConfig(kms.Config{
Endpoints: endpoints,
DefaultKeyID: defaultKeyID,
Certificate: certificate,
RootCAs: rootCAs,
Endpoints: endpoints,
DefaultKeyID: defaultKeyID,
Certificate: certificate,
ReloadCertEvents: reloadCertEvents,
RootCAs: rootCAs,
})
if err != nil {
logger.Fatal(err, "Unable to initialize a connection to KES as specified by the shell environment")
@@ -916,7 +928,7 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
// Therefore, we read all filenames in the cert directory and check
// for each directory whether it contains a public.crt and private.key.
// If so, we try to add it to certificate manager.
root, err := os.Open(globalCertsDir.Get())
root, err := Open(globalCertsDir.Get())
if err != nil {
return nil, nil, false, err
}
@@ -935,7 +947,7 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
continue
}
if file.Mode()&os.ModeSymlink == os.ModeSymlink {
file, err = os.Stat(filepath.Join(root.Name(), file.Name()))
file, err = Stat(filepath.Join(root.Name(), file.Name()))
if err != nil {
// not accessible ignore
continue
+1 -1
View File
@@ -227,7 +227,7 @@ func (d *dataUpdateTracker) load(ctx context.Context, drives ...string) {
for _, drive := range drives {
cacheFormatPath := pathJoin(drive, dataUpdateTrackerFilename)
f, err := os.Open(cacheFormatPath)
f, err := OpenFile(cacheFormatPath, readMode, 0o666)
if err != nil {
if osIsNotExist(err) {
continue
+10 -10
View File
@@ -624,7 +624,7 @@ func (c *diskCache) saveMetadata(ctx context.Context, bucket, object string, met
if err := os.MkdirAll(cachedPath, 0o777); err != nil {
return err
}
f, err := os.OpenFile(metaPath, os.O_RDWR|os.O_CREATE, 0o666)
f, err := OpenFile(metaPath, os.O_RDWR|os.O_CREATE|writeMode, 0o666)
if err != nil {
return err
}
@@ -687,7 +687,7 @@ func (c *diskCache) updateMetadata(ctx context.Context, bucket, object, etag str
if err := os.MkdirAll(cachedPath, 0o777); err != nil {
return err
}
f, err := os.OpenFile(metaPath, os.O_RDWR, 0o666)
f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666)
if err != nil {
return err
}
@@ -1275,12 +1275,12 @@ func (c *diskCache) NewMultipartUpload(ctx context.Context, bucket, object, uID
cachePath := getMultipartCacheSHADir(c.dir, bucket, object)
uploadIDDir := path.Join(cachePath, uploadID)
if err := os.MkdirAll(uploadIDDir, 0o777); err != nil {
if err := mkdirAll(uploadIDDir, 0o777); err != nil {
return uploadID, err
}
metaPath := pathJoin(uploadIDDir, cacheMetaJSONFile)
f, err := os.OpenFile(metaPath, os.O_RDWR|os.O_CREATE, 0o666)
f, err := OpenFile(metaPath, os.O_RDWR|os.O_CREATE|writeMode, 0o666)
if err != nil {
return uploadID, err
}
@@ -1386,7 +1386,7 @@ func (c *diskCache) SavePartMetadata(ctx context.Context, bucket, object, upload
defer uploadLock.Unlock(ulkctx.Cancel)
metaPath := pathJoin(uploadDir, cacheMetaJSONFile)
f, err := os.OpenFile(metaPath, os.O_RDWR, 0o666)
f, err := OpenFile(metaPath, os.O_RDWR|writeMode, 0o666)
if err != nil {
return err
}
@@ -1465,7 +1465,7 @@ func newCachePartEncryptReader(ctx context.Context, bucket, object string, partI
func (c *diskCache) uploadIDExists(bucket, object, uploadID string) (err error) {
mpartCachePath := getMultipartCacheSHADir(c.dir, bucket, object)
uploadIDDir := path.Join(mpartCachePath, uploadID)
if _, err := os.Stat(uploadIDDir); err != nil {
if _, err := Stat(uploadIDDir); err != nil {
return err
}
return nil
@@ -1564,7 +1564,7 @@ func (c *diskCache) CompleteMultipartUpload(ctx context.Context, bucket, object,
uploadMeta.Hits++
metaPath := pathJoin(uploadIDDir, cacheMetaJSONFile)
f, err := os.OpenFile(metaPath, os.O_RDWR|os.O_CREATE, 0o666)
f, err := OpenFile(metaPath, os.O_RDWR|os.O_CREATE|writeMode, 0o666)
if err != nil {
return oi, err
}
@@ -1634,7 +1634,7 @@ func (c *diskCache) cleanupStaleUploads(ctx context.Context) {
readDirFn(pathJoin(c.dir, minioMetaBucket, cacheMultipartDir), func(shaDir string, typ os.FileMode) error {
return readDirFn(pathJoin(c.dir, minioMetaBucket, cacheMultipartDir, shaDir), func(uploadIDDir string, typ os.FileMode) error {
uploadIDPath := pathJoin(c.dir, minioMetaBucket, cacheMultipartDir, shaDir, uploadIDDir)
fi, err := os.Stat(uploadIDPath)
fi, err := Stat(uploadIDPath)
if err != nil {
return nil
}
@@ -1649,8 +1649,8 @@ func (c *diskCache) cleanupStaleUploads(ctx context.Context) {
readDirFn(pathJoin(c.dir, minioMetaBucket, cacheWritebackDir), func(shaDir string, typ os.FileMode) error {
wbdir := pathJoin(c.dir, minioMetaBucket, cacheWritebackDir, shaDir)
cachedir := pathJoin(c.dir, shaDir)
if _, err := os.Stat(cachedir); os.IsNotExist(err) {
fi, err := os.Stat(wbdir)
if _, err := Stat(cachedir); os.IsNotExist(err) {
fi, err := Stat(wbdir)
if err != nil {
return nil
}
+3 -3
View File
@@ -607,12 +607,12 @@ func newCache(config cache.Config) ([]*diskCache, bool, error) {
warningMsg = fmt.Sprintf("Invalid cache dir %s err : %s", dir, err.Error())
}
if rootDsk {
warningMsg = fmt.Sprintf("cache dir cannot be part of root disk: %s", dir)
warningMsg = fmt.Sprintf("cache dir cannot be part of root drive: %s", dir)
}
}
if err := checkAtimeSupport(dir); err != nil {
return nil, false, fmt.Errorf("Atime support required for disk caching, atime check failed with %w", err)
return nil, false, fmt.Errorf("Atime support required for drive caching, atime check failed with %w", err)
}
cache, err := newDiskCache(ctx, dir, config)
@@ -622,7 +622,7 @@ func newCache(config cache.Config) ([]*diskCache, bool, error) {
caches = append(caches, cache)
}
if warningMsg != "" {
logger.Info(color.Yellow(fmt.Sprintf("WARNING: Usage of root disk for disk caching is deprecated: %s", warningMsg)))
logger.Info(color.Yellow(fmt.Sprintf("WARNING: Usage of root drive for drive caching is deprecated: %s", warningMsg)))
}
return caches, migrating, nil
}
+3 -3
View File
@@ -156,7 +156,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
setCounts := possibleSetCounts(commonSize)
if len(setCounts) == 0 {
msg := fmt.Sprintf("Incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
msg := fmt.Sprintf("Incorrect number of endpoints provided %s, number of drives %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
}
@@ -183,7 +183,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
setCounts = possibleSetCountsWithSymmetry(setCounts, argPatterns)
if len(setCounts) == 0 {
msg := fmt.Sprintf("No symmetric distribution detected with input endpoints provided %s, disks %d cannot be spread symmetrically by any supported erasure set sizes %d", args, commonSize, setSizes)
msg := fmt.Sprintf("No symmetric distribution detected with input endpoints provided %s, drives %d cannot be spread symmetrically by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
}
@@ -193,7 +193,7 @@ func getSetIndexes(args []string, totalSizes []uint64, customSetDriveCount uint6
// Check whether setSize is with the supported range.
if !isValidSetSize(setSize) {
msg := fmt.Sprintf("Incorrect number of endpoints provided %s, number of disks %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
msg := fmt.Sprintf("Incorrect number of endpoints provided %s, number of drives %d is not divisible by any supported erasure set sizes %d", args, commonSize, setSizes)
return nil, config.ErrInvalidNumberOfErasureEndpoints(nil).Msg(msg)
}
+2 -2
View File
@@ -20,10 +20,10 @@ package cmd
import "errors"
// errErasureReadQuorum - did not meet read quorum.
var errErasureReadQuorum = errors.New("Read failed. Insufficient number of disks online")
var errErasureReadQuorum = errors.New("Read failed. Insufficient number of drives online")
// errErasureWriteQuorum - did not meet write quorum.
var errErasureWriteQuorum = errors.New("Write failed. Insufficient number of disks online")
var errErasureWriteQuorum = errors.New("Write failed. Insufficient number of drives online")
// errNoHealRequired - returned when healing is attempted on a previously healed disks.
var errNoHealRequired = errors.New("No healing is required")
+1 -1
View File
@@ -66,7 +66,7 @@ func TestErasureHeal(t *testing.T) {
for i, test := range erasureHealTests {
if test.offDisks < test.badStaleDisks {
// test case sanity check
t.Fatalf("Test %d: Bad test case - number of stale disks cannot be less than number of badstale disks", i)
t.Fatalf("Test %d: Bad test case - number of stale drives cannot be less than number of badstale drives", i)
}
// create some test data
+15 -15
View File
@@ -260,7 +260,7 @@ func TestListOnlineDisks(t *testing.T) {
if test._tamperBackend != noTamper {
if tamperedIndex != -1 && availableDisks[tamperedIndex] != nil {
t.Fatalf("disk (%v) with part.1 missing is not a disk with available data",
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
erasureDisks[tamperedIndex])
}
}
@@ -446,7 +446,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
if test._tamperBackend != noTamper {
if tamperedIndex != -1 && availableDisks[tamperedIndex] != nil {
t.Fatalf("disk (%v) with part.1 missing is not a disk with available data",
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
erasureDisks[tamperedIndex])
}
}
@@ -506,7 +506,7 @@ func TestDisksWithAllParts(t *testing.T) {
errs, fi, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) {
t.Errorf("Unexpected number of disks: %d", len(filteredDisks))
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
@@ -515,7 +515,7 @@ func TestDisksWithAllParts(t *testing.T) {
}
if disk == nil {
t.Errorf("Disk erroneously filtered, diskIndex: %d", diskIndex)
t.Errorf("Drive erroneously filtered, driveIndex: %d", diskIndex)
}
}
@@ -528,14 +528,14 @@ func TestDisksWithAllParts(t *testing.T) {
errs, fi, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) {
t.Errorf("Unexpected number of disks: %d", len(filteredDisks))
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if diskIndex == 0 && disk != nil {
t.Errorf("Disk not filtered as expected, disk: %d", diskIndex)
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
}
if diskIndex != 0 && disk == nil {
t.Errorf("Disk erroneously filtered, diskIndex: %d", diskIndex)
t.Errorf("Drive erroneously filtered, driveIndex: %d", diskIndex)
}
}
partsMetadata[0] = partsMetadataBackup // Revert before going to the next test
@@ -549,14 +549,14 @@ func TestDisksWithAllParts(t *testing.T) {
errs, fi, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) {
t.Errorf("Unexpected number of disks: %d", len(filteredDisks))
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if diskIndex == 1 && disk != nil {
t.Errorf("Disk not filtered as expected, disk: %d", diskIndex)
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
}
if diskIndex != 1 && disk == nil {
t.Errorf("Disk erroneously filtered, diskIndex: %d", diskIndex)
t.Errorf("Drive erroneously filtered, driveIndex: %d", diskIndex)
}
}
partsMetadata[1] = partsMetadataBackup // Revert before going to the next test
@@ -586,23 +586,23 @@ func TestDisksWithAllParts(t *testing.T) {
errs, fi, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) {
t.Errorf("Unexpected number of disks: %d", len(filteredDisks))
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if _, ok := diskFailures[diskIndex]; ok {
if disk != nil {
t.Errorf("Disk not filtered as expected, disk: %d", diskIndex)
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
}
if errs[diskIndex] == nil {
t.Errorf("Expected error not received, diskIndex: %d", diskIndex)
t.Errorf("Expected error not received, driveIndex: %d", diskIndex)
}
} else {
if disk == nil {
t.Errorf("Disk erroneously filtered, diskIndex: %d", diskIndex)
t.Errorf("Drive erroneously filtered, driveIndex: %d", diskIndex)
}
if errs[diskIndex] != nil {
t.Errorf("Unexpected error, %s, diskIndex: %d", errs[diskIndex], diskIndex)
t.Errorf("Unexpected error, %s, driveIndex: %d", errs[diskIndex], diskIndex)
}
}
+39 -22
View File
@@ -243,7 +243,7 @@ func listAllBuckets(ctx context.Context, storageDisks []StorageAPI, healBuckets
// Only heal on disks where we are sure that healing is needed. We can expand
// this list as and when we figure out more errors can be added to this list safely.
func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta FileInfo) bool {
func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta FileInfo, doinline bool) bool {
switch {
case errors.Is(erErr, errFileNotFound) || errors.Is(erErr, errFileVersionNotFound):
return true
@@ -256,6 +256,10 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta File
// always check first.
return true
}
if doinline {
// convert small files to 'inline'
return true
}
if !meta.Deleted && !meta.IsRemote() {
// If xl.meta was read fine but there may be problem with the part.N files.
if IsErr(dataErr, []error{
@@ -275,10 +279,6 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta File
// Heals an object by re-writing corrupt/missing erasure blocks.
func (er erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
if !opts.DryRun {
defer NSUpdated(bucket, object)
}
dryRun := opts.DryRun
scanMode := opts.ScanMode
@@ -346,6 +346,26 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
availableDisks, dataErrs, diskMTime := disksWithAllParts(ctx, onlineDisks, partsMetadata,
errs, latestMeta, bucket, object, scanMode)
var erasure Erasure
var recreate bool
if !latestMeta.Deleted && !latestMeta.IsRemote() {
// Initialize erasure coding
erasure, err = NewErasure(ctx, latestMeta.Erasure.DataBlocks,
latestMeta.Erasure.ParityBlocks, latestMeta.Erasure.BlockSize)
if err != nil {
return result, err
}
// Is only 'true' if the opts.Recreate is true and
// the object shardSize < smallFileThreshold do not
// set this to 'true' arbitrarily and must be only
// 'true' with caller ask.
recreate = (opts.Recreate &&
!latestMeta.InlineData() &&
len(latestMeta.Parts) == 1 &&
erasure.ShardFileSize(latestMeta.Parts[0].ActualSize) < smallFileThreshold)
}
// Loop to find number of disks with valid data, per-drive
// data state and a list of outdated disks on which data needs
// to be healed.
@@ -372,7 +392,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
driveState = madmin.DriveStateCorrupt
}
if shouldHealObjectOnDisk(errs[i], dataErrs[i], partsMetadata[i], latestMeta) {
if shouldHealObjectOnDisk(errs[i], dataErrs[i], partsMetadata[i], latestMeta, recreate) {
outDatedDisks[i] = storageDisks[i]
disksToHealCount++
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
@@ -426,9 +446,9 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
return result, nil
}
if !latestMeta.XLV1 && !latestMeta.Deleted && disksToHealCount > latestMeta.Erasure.ParityBlocks {
if !latestMeta.XLV1 && !latestMeta.Deleted && !recreate && disksToHealCount > latestMeta.Erasure.ParityBlocks {
// When disk to heal count is greater than parity blocks we should simply error out.
err := fmt.Errorf("more disks are expected to heal than parity, returned errors: %v (dataErrs %v) -> %s/%s(%s)", errs, dataErrs, bucket, object, versionID)
err := fmt.Errorf("more drives are expected to heal than parity, returned errors: %v (dataErrs %v) -> %s/%s(%s)", errs, dataErrs, bucket, object, versionID)
logger.LogIf(ctx, err)
return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs,
bucket, object, versionID), err
@@ -482,18 +502,9 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
}
var inlineBuffers []*bytes.Buffer
if latestMeta.InlineData() {
inlineBuffers = make([]*bytes.Buffer, len(outDatedDisks))
}
if !latestMeta.Deleted && !latestMeta.IsRemote() {
// Heal each part. erasureHealFile() will write the healed
// part to .minio/tmp/uuid/ which needs to be renamed later to
// the final location.
erasure, err := NewErasure(ctx, latestMeta.Erasure.DataBlocks,
latestMeta.Erasure.ParityBlocks, latestMeta.Erasure.BlockSize)
if err != nil {
return result, err
if latestMeta.InlineData() || recreate {
inlineBuffers = make([]*bytes.Buffer, len(outDatedDisks))
}
erasureInfo := latestMeta.Erasure
@@ -529,6 +540,10 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
}
}
// Heal each part. erasure.Heal() will write the healed
// part to .minio/tmp/uuid/ which needs to be renamed
// later to the final location.
err = erasure.Heal(ctx, writers, readers, partSize)
closeBitrotReaders(readers)
closeBitrotWriters(writers)
@@ -560,6 +575,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
})
if len(inlineBuffers) > 0 && inlineBuffers[i] != nil {
partsMetadata[i].Data = inlineBuffers[i].Bytes()
partsMetadata[i].SetInlineData()
} else {
partsMetadata[i].Data = nil
}
@@ -567,7 +583,7 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
// If all disks are having errors, we give up.
if disksToHealCount == 0 {
return result, fmt.Errorf("all disks had write errors, unable to heal %s/%s", bucket, object)
return result, fmt.Errorf("all drives had write errors, unable to heal %s/%s", bucket, object)
}
}
@@ -591,8 +607,9 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
return result, err
}
// Remove any remaining parts from outdated disks from before transition.
if partsMetadata[i].IsRemote() {
// - Remove any parts from healed disks after its been inlined.
// - 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)
}
+1 -1
View File
@@ -275,7 +275,7 @@ func shuffleDisks(disks []StorageAPI, distribution []int) (shuffledDisks []Stora
// the corresponding error in errs slice is not nil
func evalDisks(disks []StorageAPI, errs []error) []StorageAPI {
if len(errs) != len(disks) {
logger.LogIf(GlobalContext, errors.New("unexpected disks/errors slice length"))
logger.LogIf(GlobalContext, errors.New("unexpected drives/errors slice length"))
return nil
}
newDisks := make([]StorageAPI, len(disks))
+2
View File
@@ -1159,6 +1159,8 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
return oi, toObjectErr(err, bucket, object)
}
defer NSUpdated(bucket, object)
// Check if there is any offline disk and add it to the MRF list
for _, disk := range onlineDisks {
if disk != nil && disk.IsOnline() {
+2 -2
View File
@@ -686,8 +686,6 @@ func (er erasureObjects) getObjectInfoAndQuorum(ctx context.Context, bucket, obj
// Similar to rename but renames data from srcEntry to dstEntry at dataDir
func renameData(ctx context.Context, disks []StorageAPI, srcBucket, srcEntry string, metadata []FileInfo, dstBucket, dstEntry string, writeQuorum int) ([]StorageAPI, error) {
defer NSUpdated(dstBucket, dstEntry)
g := errgroup.WithNErrs(len(disks))
fvID := mustGetUUID()
@@ -1140,6 +1138,8 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
return ObjectInfo{}, toObjectErr(err, bucket, object)
}
defer NSUpdated(bucket, object)
for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
// Object info is the same in all disks, so we can pick
+2 -2
View File
@@ -457,7 +457,7 @@ func (p *poolMeta) updateAfter(ctx context.Context, idx int, pools []*erasureSet
now := UTCNow()
if now.Sub(p.Pools[idx].LastUpdate) >= duration {
if serverDebugLog {
console.Debugf("decommission: persisting poolMeta on disk: threshold:%s, poolMeta:%#v\n", now.Sub(p.Pools[idx].LastUpdate), p.Pools[idx])
console.Debugf("decommission: persisting poolMeta on drive: threshold:%s, poolMeta:%#v\n", now.Sub(p.Pools[idx].LastUpdate), p.Pools[idx])
}
p.Pools[idx].LastUpdate = now
if err := p.save(ctx, pools); err != nil {
@@ -677,7 +677,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
set := set
disks := set.getOnlineDisks()
if len(disks) == 0 {
logger.LogIf(GlobalContext, fmt.Errorf("no online disks found for set with endpoints %s",
logger.LogIf(GlobalContext, fmt.Errorf("no online drives found for set with endpoints %s",
set.getEndpoints()))
continue
}
+5 -3
View File
@@ -1769,7 +1769,9 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts
}
// Attempt heal on the bucket metadata, ignore any failures
defer z.HealObject(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, bucketMetadataFile), "", opts)
hopts := opts
hopts.Recreate = false
defer z.HealObject(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, bucket, bucketMetadataFile), "", hopts)
for _, pool := range z.serverPools {
result, err := pool.HealBucket(ctx, bucket, opts)
@@ -1905,7 +1907,7 @@ func listAndHeal(ctx context.Context, bucket, prefix string, set *erasureObjects
disks, _ := set.getOnlineDisksWithHealing()
if len(disks) == 0 {
return errors.New("listAndHeal: No non-healing disks found")
return errors.New("listAndHeal: No non-healing drives found")
}
// How to resolve partial results.
@@ -2096,7 +2098,7 @@ func (z *erasureServerPools) getPoolAndSet(id string) (poolIdx, setIdx, diskIdx
}
}
}
return -1, -1, -1, fmt.Errorf("DiskID(%s) %w", id, errDiskNotFound)
return -1, -1, -1, fmt.Errorf("DriveID(%s) %w", id, errDiskNotFound)
}
// HealthOptions takes input options to return sepcific information
+11 -11
View File
@@ -129,10 +129,10 @@ func connectEndpoint(endpoint Endpoint) (StorageAPI, *formatErasureV3, error) {
if errors.Is(err, errUnformattedDisk) {
info, derr := disk.DiskInfo(context.TODO())
if derr != nil && info.RootDisk {
return nil, nil, fmt.Errorf("Disk: %s is a root disk", disk)
return nil, nil, fmt.Errorf("Drive: %s is a root drive", disk)
}
}
return nil, nil, fmt.Errorf("Disk: %s returned %w", disk, err) // make sure to '%w' to wrap the error
return nil, nil, fmt.Errorf("Drive: %s returned %w", disk, err) // make sure to '%w' to wrap the error
}
return disk, format, nil
@@ -147,7 +147,7 @@ func findDiskIndexByDiskID(refFormat *formatErasureV3, diskID string) (int, int,
return -1, -1, errDiskNotFound
}
if diskID == offlineDiskUUID {
return -1, -1, fmt.Errorf("diskID: %s is offline", diskID)
return -1, -1, fmt.Errorf("DriveID: %s is offline", diskID)
}
for i := 0; i < len(refFormat.Erasure.Sets); i++ {
for j := 0; j < len(refFormat.Erasure.Sets[0]); j++ {
@@ -157,7 +157,7 @@ func findDiskIndexByDiskID(refFormat *formatErasureV3, diskID string) (int, int,
}
}
return -1, -1, fmt.Errorf("diskID: %s not found", diskID)
return -1, -1, fmt.Errorf("DriveID: %s not found", diskID)
}
// findDiskIndex - returns the i,j'th position of the input `format` against the reference
@@ -170,7 +170,7 @@ func findDiskIndex(refFormat, format *formatErasureV3) (int, int, error) {
}
if format.Erasure.This == offlineDiskUUID {
return -1, -1, fmt.Errorf("diskID: %s is offline", format.Erasure.This)
return -1, -1, fmt.Errorf("DriveID: %s is offline", format.Erasure.This)
}
for i := 0; i < len(refFormat.Erasure.Sets); i++ {
@@ -181,7 +181,7 @@ func findDiskIndex(refFormat, format *formatErasureV3) (int, int, error) {
}
}
return -1, -1, fmt.Errorf("diskID: %s not found", format.Erasure.This)
return -1, -1, fmt.Errorf("DriveID: %s not found", format.Erasure.This)
}
// connectDisks - attempt to connect all the endpoints, loads format
@@ -239,7 +239,7 @@ func (s *erasureSets) connectDisks() {
s.erasureDisksMu.Lock()
if currentDisk := s.erasureDisks[setIndex][diskIndex]; currentDisk != nil {
if !reflect.DeepEqual(currentDisk.Endpoint(), disk.Endpoint()) {
err = fmt.Errorf("Detected unexpected disk ordering refusing to use the disk: expecting %s, found %s, refusing to use the disk",
err = fmt.Errorf("Detected unexpected drive ordering refusing to use the drive: expecting %s, found %s, refusing to use the drive",
currentDisk.Endpoint(), disk.Endpoint())
printEndpointError(endpoint, err, false)
disk.Close()
@@ -300,7 +300,7 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
return
case <-monitor.C:
if serverDebugLog {
console.Debugln("running disk monitoring")
console.Debugln("running drive monitoring")
}
s.connectDisks()
@@ -446,7 +446,7 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
return
}
if m != i || n != j {
logger.LogIf(ctx, fmt.Errorf("Detected unexpected disk ordering refusing to use the disk - poolID: %s, found disk mounted at (set=%s, disk=%s) expected mount at (set=%s, disk=%s): %s(%s)", humanize.Ordinal(poolIdx+1), humanize.Ordinal(m+1), humanize.Ordinal(n+1), humanize.Ordinal(i+1), humanize.Ordinal(j+1), disk, diskID))
logger.LogIf(ctx, fmt.Errorf("Detected unexpected drive ordering refusing to use the drive - poolID: %s, found drive mounted at (set=%s, drive=%s) expected mount at (set=%s, drive=%s): %s(%s)", humanize.Ordinal(poolIdx+1), humanize.Ordinal(m+1), humanize.Ordinal(n+1), humanize.Ordinal(i+1), humanize.Ordinal(j+1), disk, diskID))
s.erasureDisks[i][j] = &unrecognizedDisk{storage: disk}
return
}
@@ -1240,7 +1240,7 @@ func markRootDisksAsDown(storageDisks []StorageAPI, errs []error) {
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("Disk `%s` is part of root disk, will not be used", storageDisks[i]))
logger.LogIf(GlobalContext, fmt.Errorf("Drive `%s` is part of root drive, will not be used", storageDisks[i]))
storageDisks[i] = nil
}
}
@@ -1314,7 +1314,7 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
continue
}
if err := saveFormatErasure(storageDisks[index], format, true); err != nil {
logger.LogIf(ctx, fmt.Errorf("Disk %s failed to write updated 'format.json': %v", storageDisks[index], err))
logger.LogIf(ctx, fmt.Errorf("Drive %s failed to write updated 'format.json': %v", storageDisks[index], err))
tmpNewFormats[index] = nil // this disk failed to write new format
}
}
+1 -1
View File
@@ -187,7 +187,7 @@ func TestNewErasureSets(t *testing.T) {
// Initializes all erasure disks
storageDisks, format, err := waitForFormatErasure(true, endpoints, 1, 1, 16, "", "")
if err != nil {
t.Fatalf("Unable to format disks for erasure, %s", err)
t.Fatalf("Unable to format drives for erasure, %s", err)
}
ep := PoolEndpoints{Endpoints: endpoints}
+6 -2
View File
@@ -1203,6 +1203,8 @@ func (es *erasureSingle) putObject(ctx context.Context, bucket string, object st
return ObjectInfo{}, toObjectErr(err, bucket, object)
}
defer NSUpdated(bucket, object)
for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
// Object info is the same in all disks, so we can pick
@@ -1308,12 +1310,12 @@ func (es *erasureSingle) DeleteObjects(ctx context.Context, bucket string, objec
DeleteMarker: vr.Deleted,
DeleteMarkerVersionID: vr.VersionID,
DeleteMarkerMTime: DeleteMarkerMTime{vr.ModTime},
ObjectName: vr.Name,
ObjectName: decodeDirObject(vr.Name),
ReplicationState: vr.ReplicationState,
}
} else {
dobjects[i] = DeletedObject{
ObjectName: vr.Name,
ObjectName: decodeDirObject(vr.Name),
VersionID: vr.VersionID,
ReplicationState: vr.ReplicationState,
}
@@ -2804,6 +2806,8 @@ func (es *erasureSingle) CompleteMultipartUpload(ctx context.Context, bucket str
return oi, toObjectErr(err, bucket, object)
}
defer NSUpdated(bucket, object)
for i := 0; i < len(onlineDisks); i++ {
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
// Object info is the same in all disks, so we can pick
+2 -2
View File
@@ -288,7 +288,7 @@ func (er erasureObjects) getOnlineDisksWithHealing() (newDisks []StorageAPI, hea
disk := disks[i-1]
if disk == nil {
infos[i-1].Error = "nil disk"
infos[i-1].Error = "nil drive"
return
}
@@ -354,7 +354,7 @@ func (er erasureObjects) nsScanner(ctx context.Context, buckets []BucketInfo, bf
// Collect disks we can use.
disks, healing := er.getOnlineDisksWithHealing()
if len(disks) == 0 {
logger.LogIf(ctx, errors.New("data-scanner: all disks are offline or being healed, skipping scanner cycle"))
logger.LogIf(ctx, errors.New("data-scanner: all drives are offline or being healed, skipping scanner cycle"))
return nil
}
+3 -3
View File
@@ -110,7 +110,7 @@ func formatCacheGetVersion(r io.ReadSeeker) (string, error) {
// Creates a new cache format.json if unformatted.
func createFormatCache(fsFormatPath string, format *formatCacheV1) error {
// open file using READ & WRITE permission
file, err := os.OpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0o600)
file, err := os.OpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return err
}
@@ -155,7 +155,7 @@ func loadFormatCache(ctx context.Context, drives []string) ([]*formatCacheV2, bo
migrating := false
for i, drive := range drives {
cacheFormatPath := pathJoin(drive, minioMetaBucket, formatConfigFile)
f, err := os.OpenFile(cacheFormatPath, os.O_RDWR, 0)
f, err := os.OpenFile(cacheFormatPath, os.O_RDWR, 0o666)
if err != nil {
if osIsNotExist(err) {
continue
@@ -478,7 +478,7 @@ func migrateOldCache(ctx context.Context, c *diskCache) error {
func migrateCacheFormatJSON(cacheFormatPath string) error {
// now migrate format.json
f, err := os.OpenFile(cacheFormatPath, os.O_RDWR, 0)
f, err := os.OpenFile(cacheFormatPath, os.O_RDWR, 0o666)
if err != nil {
return err
}
+7 -7
View File
@@ -79,7 +79,7 @@ type formatErasureV1 struct {
formatMetaV1
Erasure struct {
Version string `json:"version"` // Version of 'xl' format.
Disk string `json:"disk"` // Disk field carries assigned disk uuid.
Disk string `json:"drive"` // Disk field carries assigned disk uuid.
// JBOD field carries the input disk order generated the first
// time when fresh disks were supplied.
JBOD []string `json:"jbod"`
@@ -199,7 +199,7 @@ func formatErasureMigrate(export string) ([]byte, fs.FileInfo, error) {
version, err := formatGetBackendErasureVersion(formatData)
if err != nil {
return nil, nil, fmt.Errorf("Disk %s: %w", export, err)
return nil, nil, fmt.Errorf("Drive %s: %w", export, err)
}
migrate := func(formatPath string, formatData []byte) ([]byte, fs.FileInfo, error) {
@@ -217,7 +217,7 @@ func formatErasureMigrate(export string) ([]byte, fs.FileInfo, error) {
case formatErasureVersionV1:
formatData, err = formatErasureMigrateV1ToV2(formatData, version)
if err != nil {
return nil, nil, fmt.Errorf("Disk %s: %w", export, err)
return nil, nil, fmt.Errorf("Drive %s: %w", export, err)
}
// Migrate successful v1 => v2, proceed to v2 => v3
version = formatErasureVersionV2
@@ -225,7 +225,7 @@ func formatErasureMigrate(export string) ([]byte, fs.FileInfo, error) {
case formatErasureVersionV2:
formatData, err = formatErasureMigrateV2ToV3(formatData, export, version)
if err != nil {
return nil, nil, fmt.Errorf("Disk %s: %w", export, err)
return nil, nil, fmt.Errorf("Drive %s: %w", export, err)
}
// Migrate successful v2 => v3, v3 is latest
// version = formatXLVersionV3
@@ -438,14 +438,14 @@ func checkFormatErasureValues(formats []*formatErasureV3, disks []StorageAPI, se
return err
}
if len(formats) != len(formatErasure.Erasure.Sets)*len(formatErasure.Erasure.Sets[0]) {
return fmt.Errorf("%s disk is already being used in another erasure deployment. (Number of disks specified: %d but the number of disks found in the %s disk's format.json: %d)",
return fmt.Errorf("%s drive is already being used in another erasure deployment. (Number of drives specified: %d but the number of drives found in the %s drive's format.json: %d)",
disks[i], len(formats), humanize.Ordinal(i+1), len(formatErasure.Erasure.Sets)*len(formatErasure.Erasure.Sets[0]))
}
// Only if custom erasure drive count is set, verify if the
// set_drive_count was manually set - we need to honor what is
// present on the drives.
if globalCustomErasureDriveCount && len(formatErasure.Erasure.Sets[0]) != setDriveCount {
return fmt.Errorf("%s disk is already formatted with %d drives per erasure set. This cannot be changed to %d, please revert your MINIO_ERASURE_SET_DRIVE_COUNT setting", disks[i], len(formatErasure.Erasure.Sets[0]), setDriveCount)
return fmt.Errorf("%s drive is already formatted with %d drives per erasure set. This cannot be changed to %d, please revert your MINIO_ERASURE_SET_DRIVE_COUNT setting", disks[i], len(formatErasure.Erasure.Sets[0]), setDriveCount)
}
}
return nil
@@ -628,7 +628,7 @@ func formatErasureV3Check(reference *formatErasureV3, format *formatErasureV3) e
}
}
}
return fmt.Errorf("Disk ID %s not found in any disk sets %s", this, format.Erasure.Sets)
return fmt.Errorf("DriveID %s not found in any drive sets %s", this, format.Erasure.Sets)
}
// saveFormatErasureAll - populates `format.json` on disks in its order.
+2 -2
View File
@@ -149,13 +149,13 @@ func TestFormatErasureMigrate(t *testing.T) {
t.Fatal(err)
}
if formatV3.Erasure.This != m.Erasure.Disk {
t.Fatalf("expected disk uuid: %s, got: %s", m.Erasure.Disk, formatV3.Erasure.This)
t.Fatalf("expected drive uuid: %s, got: %s", m.Erasure.Disk, formatV3.Erasure.This)
}
if len(formatV3.Erasure.Sets) != 1 {
t.Fatalf("expected single set after migrating from v1 to v3, but found %d", len(formatV3.Erasure.Sets))
}
if !reflect.DeepEqual(formatV3.Erasure.Sets[0], m.Erasure.JBOD) {
t.Fatalf("expected disk uuid: %v, got: %v", m.Erasure.JBOD, formatV3.Erasure.Sets[0])
t.Fatalf("expected drive uuid: %v, got: %v", m.Erasure.JBOD, formatV3.Erasure.Sets[0])
}
m = &formatErasureV1{}
+3 -3
View File
@@ -165,7 +165,7 @@ func formatFSMigrate(ctx context.Context, wlk *lock.LockedFile, fsPath string) e
func createFormatFS(fsFormatPath string) error {
// Attempt a write lock on formatConfigFile `format.json`
// file stored in minioMetaBucket(.minio.sys) directory.
lk, err := lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0o600)
lk, err := lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR|os.O_CREATE, 0o666)
if err != nil {
return err
}
@@ -256,7 +256,7 @@ func initFormatFS(ctx context.Context, fsPath string) (rlk *lock.RLockedFile, er
// Hold write lock during migration so that we do not disturb any
// minio processes running in parallel.
var wlk *lock.LockedFile
wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0)
wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0o666)
if err == lock.ErrAlreadyLocked {
// Lock already present, sleep and attempt again.
time.Sleep(100 * time.Millisecond)
@@ -357,7 +357,7 @@ func formatFSFixDeploymentID(ctx context.Context, fsFormatPath string) error {
case <-ctx.Done():
return fmt.Errorf("Initializing FS format stopped gracefully")
default:
wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0)
wlk, err = lock.TryLockedOpenFile(fsFormatPath, os.O_RDWR, 0o666)
if err == lock.ErrAlreadyLocked {
// Lock already present, sleep and attempt again
logger.Info("Another minio process(es) might be holding a lock to the file %s. Please kill that minio process(es) (elapsed %s)\n", fsFormatPath, getElapsedTime())
+1 -1
View File
@@ -517,7 +517,7 @@ func (fs *FSObjects) ListObjectParts(ctx context.Context, bucket, object, upload
}
}
rc, _, err := fsOpenFile(ctx, pathJoin(uploadIDDir, fs.metaJSONFile), 0)
rc, _, err := fsOpenFile(ctx, pathJoin(uploadIDDir, fs.metaJSONFile), 0o666)
if err != nil {
if err == errFileNotFound || err == errFileAccessDenied {
return result, InvalidUploadID{Bucket: bucket, Object: object, UploadID: uploadID}
+1 -1
View File
@@ -867,7 +867,7 @@ func (fs *FSObjects) getObjectInfoNoFSLock(ctx context.Context, bucket, object s
// Read `fs.json` to perhaps contend with
// parallel Put() operations.
rc, _, err := fsOpenFile(ctx, fsMetaPath, 0)
rc, _, err := fsOpenFile(ctx, fsMetaPath, 0o666)
if err == nil {
fsMetaBuf, rerr := ioutil.ReadAll(rc)
rc.Close()
+1 -1
View File
@@ -41,7 +41,7 @@ func TestNewFS(t *testing.T) {
}
_, err = NewFSObjectLayer(disk)
if err != nil {
errMsg := "Unable to recognize backend format, Disk is not in FS format."
errMsg := "Unable to recognize backend format, Drive is not in FS format."
if err.Error() == errMsg {
t.Errorf("Expecting %s, got %s", errMsg, err)
}
+1 -1
View File
@@ -323,7 +323,7 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
// initialize the new disk cache objects.
var cacheAPI CacheObjectLayer
cacheAPI, err = newServerCacheObjects(GlobalContext, globalCacheConfig)
logger.FatalIf(err, "Unable to initialize disk caching")
logger.FatalIf(err, "Unable to initialize drive caching")
globalObjLayerMutex.Lock()
globalCacheObjectAPI = cacheAPI
+1 -1
View File
@@ -209,7 +209,7 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
}
if serverDebugLog {
console.Debugf(color.Green("healDisk:")+" healing bucket %s content on %s erasure set\n",
console.Debugf(color.Green("healDrive:")+" healing bucket %s content on %s erasure set\n",
bucket, humanize.Ordinal(tracker.SetIndex+1))
}
+13 -1
View File
@@ -90,6 +90,9 @@ func (iamOS *IAMObjectStore) getUsersSysType() UsersSysType {
// 3. Migrate user identity json file to include version info.
func (iamOS *IAMObjectStore) migrateUsersConfigToV1(ctx context.Context) error {
basePrefix := iamConfigUsersPrefix
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
if item.Err != nil {
return item.Err
@@ -274,6 +277,8 @@ func (iamOS *IAMObjectStore) loadPolicyDoc(ctx context.Context, policy string, m
}
func (iamOS *IAMObjectStore) loadPolicyDocs(ctx context.Context, m map[string]PolicyDoc) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPoliciesPrefix) {
if item.Err != nil {
return item.Err
@@ -323,6 +328,8 @@ func (iamOS *IAMObjectStore) loadUsers(ctx context.Context, userType IAMUserType
basePrefix = iamConfigUsersPrefix
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePrefix) {
if item.Err != nil {
return item.Err
@@ -350,6 +357,8 @@ func (iamOS *IAMObjectStore) loadGroup(ctx context.Context, group string, m map[
}
func (iamOS *IAMObjectStore) loadGroups(ctx context.Context, m map[string]GroupInfo) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigGroupsPrefix) {
if item.Err != nil {
return item.Err
@@ -392,6 +401,8 @@ func (iamOS *IAMObjectStore) loadMappedPolicies(ctx context.Context, userType IA
basePath = iamConfigPolicyDBUsersPrefix
}
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, basePath) {
if item.Err != nil {
return item.Err
@@ -432,7 +443,8 @@ var (
func (iamOS *IAMObjectStore) listAllIAMConfigItems(ctx context.Context) (map[string][]string, error) {
res := make(map[string][]string)
ctx, cancel := context.WithCancel(ctx)
defer cancel()
for item := range listIAMConfigItems(ctx, iamOS.objAPI, iamConfigPrefix+SlashSeparator) {
if item.Err != nil {
return nil, item.Err
+1 -1
View File
@@ -1332,7 +1332,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
if err != nil {
if disks[i] != nil {
combinedErr = append(combinedErr,
fmt.Sprintf("disk %s returned: %s", disks[i], err))
fmt.Sprintf("drive %s returned: %s", disks[i], err))
} else {
combinedErr = append(combinedErr, err.Error())
}
+10 -10
View File
@@ -332,7 +332,7 @@ func getNodeDiskAPILatencyMD() MetricDescription {
Namespace: nodeMetricNamespace,
Subsystem: diskSubsystem,
Name: apiLatencyMicroSec,
Help: "Average last minute latency in µs for disk API storage operations",
Help: "Average last minute latency in µs for drive API storage operations",
Type: gaugeMetric,
}
}
@@ -342,7 +342,7 @@ func getNodeDiskUsedBytesMD() MetricDescription {
Namespace: nodeMetricNamespace,
Subsystem: diskSubsystem,
Name: usedBytes,
Help: "Total storage used on a disk",
Help: "Total storage used on a drive",
Type: gaugeMetric,
}
}
@@ -352,7 +352,7 @@ func getNodeDiskFreeBytesMD() MetricDescription {
Namespace: nodeMetricNamespace,
Subsystem: diskSubsystem,
Name: freeBytes,
Help: "Total storage available on a disk",
Help: "Total storage available on a drive",
Type: gaugeMetric,
}
}
@@ -362,7 +362,7 @@ func getClusterDisksOfflineTotalMD() MetricDescription {
Namespace: clusterMetricNamespace,
Subsystem: diskSubsystem,
Name: offlineTotal,
Help: "Total disks offline",
Help: "Total drives offline",
Type: gaugeMetric,
}
}
@@ -372,7 +372,7 @@ func getClusterDisksOnlineTotalMD() MetricDescription {
Namespace: clusterMetricNamespace,
Subsystem: diskSubsystem,
Name: onlineTotal,
Help: "Total disks online",
Help: "Total drives online",
Type: gaugeMetric,
}
}
@@ -382,7 +382,7 @@ func getClusterDisksTotalMD() MetricDescription {
Namespace: clusterMetricNamespace,
Subsystem: diskSubsystem,
Name: total,
Help: "Total disks",
Help: "Total drives",
Type: gaugeMetric,
}
}
@@ -402,7 +402,7 @@ func getNodeDiskTotalBytesMD() MetricDescription {
Namespace: nodeMetricNamespace,
Subsystem: diskSubsystem,
Name: totalBytes,
Help: "Total storage on a disk",
Help: "Total storage on a drive",
Type: gaugeMetric,
}
}
@@ -702,7 +702,7 @@ func getCacheHitsTotalMD() MetricDescription {
Namespace: minioNamespace,
Subsystem: cacheSubsystem,
Name: hitsTotal,
Help: "Total number of disk cache hits",
Help: "Total number of drive cache hits",
Type: counterMetric,
}
}
@@ -712,7 +712,7 @@ func getCacheHitsMissedTotalMD() MetricDescription {
Namespace: minioNamespace,
Subsystem: cacheSubsystem,
Name: missedTotal,
Help: "Total number of disk cache misses",
Help: "Total number of drive cache misses",
Type: counterMetric,
}
}
@@ -752,7 +752,7 @@ func getCacheTotalBytesMD() MetricDescription {
Namespace: minioNamespace,
Subsystem: cacheSubsystem,
Name: totalBytes,
Help: "Total size of cache disk in bytes",
Help: "Total size of cache drive in bytes",
Type: gaugeMetric,
}
}
+9 -9
View File
@@ -273,7 +273,7 @@ func cacheMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(cacheNamespace, "hits", "total"),
"Total number of disk cache hits in current MinIO instance",
"Total number of drive cache hits in current MinIO instance",
nil, nil),
prometheus.CounterValue,
float64(cacheObjLayer.CacheStats().getHits()),
@@ -281,7 +281,7 @@ func cacheMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(cacheNamespace, "misses", "total"),
"Total number of disk cache misses in current MinIO instance",
"Total number of drive cache misses in current MinIO instance",
nil, nil),
prometheus.CounterValue,
float64(cacheObjLayer.CacheStats().getMisses()),
@@ -328,7 +328,7 @@ func cacheMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName("cache", "total", "size"),
"Indicates total size of cache disk",
"Indicates total size of cache drive",
[]string{"disk"}, nil),
prometheus.GaugeValue,
float64(cdStats.TotalCapacity),
@@ -593,7 +593,7 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(minioNamespace, "disks", "offline"),
"Total number of offline disks in current MinIO server instance",
"Total number of offline drives in current MinIO server instance",
nil, nil),
prometheus.GaugeValue,
float64(offlineDisks.Sum()),
@@ -602,8 +602,8 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
// MinIO Total Disks per node
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(minioNamespace, "disks", "total"),
"Total number of disks for current MinIO server instance",
prometheus.BuildFQName(minioNamespace, "drives", "total"),
"Total number of drives for current MinIO server instance",
nil, nil),
prometheus.GaugeValue,
float64(totalDisks.Sum()),
@@ -614,7 +614,7 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(diskNamespace, "storage", "used"),
"Total disk storage used on the disk",
"Total disk storage used on the drive",
[]string{"disk"}, nil),
prometheus.GaugeValue,
float64(disk.UsedSpace),
@@ -625,7 +625,7 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(diskNamespace, "storage", "available"),
"Total available space left on the disk",
"Total available space left on the drive",
[]string{"disk"}, nil),
prometheus.GaugeValue,
float64(disk.AvailableSpace),
@@ -636,7 +636,7 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(diskNamespace, "storage", "total"),
"Total space on the disk",
"Total space on the drive",
[]string{"disk"}, nil),
prometheus.GaugeValue,
float64(disk.TotalSpace),
+1 -1
View File
@@ -449,7 +449,7 @@ func (sys *NotificationSys) updateBloomFilter(ctx context.Context, current uint6
serverBF, err := client.cycleServerBloomFilter(ctx, req)
if false && intDataUpdateTracker.debug {
b, _ := json.MarshalIndent(serverBF, "", " ")
logger.Info("Disk %v, Bloom filter: %v", client.host.Name, string(b))
logger.Info("Drive %v, Bloom filter: %v", client.host.Name, string(b))
}
// Keep lock while checking result.
mu.Lock()
+1 -1
View File
@@ -186,7 +186,7 @@ func (e SignatureDoesNotMatch) Error() string {
type StorageFull struct{}
func (e StorageFull) Error() string {
return "Storage reached its minimum free disk threshold."
return "Storage reached its minimum free drive threshold."
}
// SlowDown too many file descriptors open or backend busy .
+8 -2
View File
@@ -37,7 +37,8 @@ const (
osMetricMkdirAll
osMetricMkdir
osMetricRename
osMetricOpenFile
osMetricOpenFileW
osMetricOpenFileR
osMetricOpen
osMetricOpenFileDirectIO
osMetricLstat
@@ -135,7 +136,12 @@ func Rename(src, dst string) error {
// OpenFile captures time taken to call os.OpenFile
func OpenFile(name string, flag int, perm os.FileMode) (*os.File, error) {
defer updateOSMetrics(osMetricOpenFile, name)()
switch flag & writeMode {
case writeMode:
defer updateOSMetrics(osMetricOpenFileW, name)()
default:
defer updateOSMetrics(osMetricOpenFileR, name)()
}
return os.OpenFile(name, flag, perm)
}
+25 -4
View File
@@ -152,12 +152,24 @@ func parseDirEnt(buf []byte) (consumed int, name []byte, typ os.FileMode, err er
// the directory itself, if the dirPath doesn't exist this function doesn't return
// an error.
func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) error {
f, err := Open(dirPath)
f, err := OpenFile(dirPath, readMode, 0o666)
if err != nil {
if osErrToFileErr(err) == errFileNotFound {
return nil
}
return osErrToFileErr(err)
if !osIsPermission(err) {
return osErrToFileErr(err)
}
// There may be permission error when dirPath
// is at the root of the disk mount that may
// not have the permissions to avoid 'noatime'
f, err = Open(dirPath)
if err != nil {
if osErrToFileErr(err) == errFileNotFound {
return nil
}
return osErrToFileErr(err)
}
}
defer f.Close()
@@ -232,9 +244,18 @@ func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) erro
// Return count entries at the directory dirPath and all entries
// if count is set to -1
func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err error) {
f, err := OpenFile(dirPath, readMode, 0)
f, err := OpenFile(dirPath, readMode, 0o666)
if err != nil {
return nil, osErrToFileErr(err)
if !osIsPermission(err) {
return nil, osErrToFileErr(err)
}
// There may be permission error when dirPath
// is at the root of the disk mount that may
// not have the permissions to avoid 'noatime'
f, err = Open(dirPath)
if err != nil {
return nil, osErrToFileErr(err)
}
}
defer f.Close()
+15 -14
View File
@@ -12,23 +12,24 @@ func _() {
_ = x[osMetricMkdirAll-1]
_ = x[osMetricMkdir-2]
_ = x[osMetricRename-3]
_ = x[osMetricOpenFile-4]
_ = x[osMetricOpen-5]
_ = x[osMetricOpenFileDirectIO-6]
_ = x[osMetricLstat-7]
_ = x[osMetricRemove-8]
_ = x[osMetricStat-9]
_ = x[osMetricAccess-10]
_ = x[osMetricCreate-11]
_ = x[osMetricReadDirent-12]
_ = x[osMetricFdatasync-13]
_ = x[osMetricSync-14]
_ = x[osMetricLast-15]
_ = x[osMetricOpenFileW-4]
_ = x[osMetricOpenFileR-5]
_ = x[osMetricOpen-6]
_ = x[osMetricOpenFileDirectIO-7]
_ = x[osMetricLstat-8]
_ = x[osMetricRemove-9]
_ = x[osMetricStat-10]
_ = x[osMetricAccess-11]
_ = x[osMetricCreate-12]
_ = x[osMetricReadDirent-13]
_ = x[osMetricFdatasync-14]
_ = x[osMetricSync-15]
_ = x[osMetricLast-16]
}
const _osMetric_name = "RemoveAllMkdirAllMkdirRenameOpenFileOpenOpenFileDirectIOLstatRemoveStatAccessCreateReadDirentFdatasyncSyncLast"
const _osMetric_name = "RemoveAllMkdirAllMkdirRenameOpenFileWOpenFileROpenOpenFileDirectIOLstatRemoveStatAccessCreateReadDirentFdatasyncSyncLast"
var _osMetric_index = [...]uint8{0, 9, 17, 22, 28, 36, 40, 56, 61, 67, 71, 77, 83, 93, 102, 106, 110}
var _osMetric_index = [...]uint8{0, 9, 17, 22, 28, 37, 46, 50, 66, 71, 77, 81, 87, 93, 103, 112, 116, 120}
func (i osMetric) String() string {
if i >= osMetric(len(_osMetric_index)-1) {
+60 -8
View File
@@ -37,16 +37,37 @@ import (
// SpeedTestResult return value of the speedtest function
type SpeedTestResult struct {
Endpoint string
Uploads uint64
Downloads uint64
Error string
Endpoint string
Uploads uint64
Downloads uint64
UploadTimes madmin.TimeDurations
DownloadTimes madmin.TimeDurations
DownloadTTFB madmin.TimeDurations
Error string
}
func newRandomReader(size int) io.Reader {
return io.LimitReader(randreader.New(), int64(size))
}
type firstByteRecorder struct {
t *time.Time
r io.Reader
}
func (f *firstByteRecorder) Read(p []byte) (n int, err error) {
if f.t != nil || len(p) == 0 {
return f.r.Read(p)
}
// Read a single byte.
n, err = f.r.Read(p[:1])
if n > 0 {
t := time.Now()
f.t = &t
}
return n, err
}
// Runs the speedtest on local MinIO process.
func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, error) {
objAPI := newObjectLayerFn()
@@ -54,9 +75,9 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
return SpeedTestResult{}, errServerNotInitialized
}
var wg sync.WaitGroup
var errOnce sync.Once
var retError string
var wg sync.WaitGroup
var totalBytesWritten uint64
var totalBytesRead uint64
@@ -80,11 +101,14 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
DisableMultipart: true,
}
var mu sync.Mutex
var uploadTimes madmin.TimeDurations
wg.Add(opts.concurrency)
for i := 0; i < opts.concurrency; i++ {
go func(i int) {
defer wg.Done()
for {
t := time.Now()
reader := newRandomReader(opts.objectSize)
tmpObjName := pathJoin(objNamePrefix, fmt.Sprintf("%d/%d", i, objCountPerThread[i]))
info, err := globalMinioClient.PutObject(uploadsCtx, opts.bucketName, tmpObjName, reader, int64(opts.objectSize), popts)
@@ -97,8 +121,12 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
uploadsCancel()
return
}
response := time.Since(t)
atomic.AddUint64(&totalBytesWritten, uint64(info.Size))
objCountPerThread[i]++
mu.Lock()
uploadTimes = append(uploadTimes, response)
mu.Unlock()
}
}(i)
}
@@ -106,7 +134,12 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
// We already saw write failures, no need to proceed into read's
if retError != "" {
return SpeedTestResult{Uploads: totalBytesWritten, Downloads: totalBytesRead, Error: retError}, nil
return SpeedTestResult{
Uploads: totalBytesWritten,
Downloads: totalBytesRead,
UploadTimes: uploadTimes,
Error: retError,
}, nil
}
downloadsCtx, downloadsCancel := context.WithCancel(context.Background())
@@ -119,6 +152,8 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
gopts := minio.GetObjectOptions{}
gopts.Set(globalObjectPerfUserMetadata, "true") // Bypass S3 API freeze
var downloadTimes madmin.TimeDurations
var downloadTTFB madmin.TimeDurations
wg.Add(opts.concurrency)
for i := 0; i < opts.concurrency; i++ {
go func(i int) {
@@ -132,6 +167,7 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
j = 0
}
tmpObjName := pathJoin(objNamePrefix, fmt.Sprintf("%d/%d", i, j))
t := time.Now()
r, err := globalMinioClient.GetObject(downloadsCtx, opts.bucketName, tmpObjName, gopts)
if err != nil {
errResp, ok := err.(minio.ErrorResponse)
@@ -146,13 +182,22 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
downloadsCancel()
return
}
n, err := io.Copy(ioutil.Discard, r)
fbr := firstByteRecorder{
r: r,
}
n, err := io.Copy(ioutil.Discard, &fbr)
r.Close()
if err == nil {
response := time.Since(t)
ttfb := time.Since(*fbr.t)
// Only capture success criteria - do not
// have to capture failed reads, truncated
// reads etc.
atomic.AddUint64(&totalBytesRead, uint64(n))
mu.Lock()
downloadTimes = append(downloadTimes, response)
downloadTTFB = append(downloadTTFB, ttfb)
mu.Unlock()
}
if err != nil {
if !contextCanceled(downloadsCtx) && !errors.Is(err, context.Canceled) {
@@ -169,7 +214,14 @@ func selfSpeedTest(ctx context.Context, opts speedTestOpts) (SpeedTestResult, er
}
wg.Wait()
return SpeedTestResult{Uploads: totalBytesWritten, Downloads: totalBytesRead, Error: retError}, nil
return SpeedTestResult{
Uploads: totalBytesWritten,
Downloads: totalBytesRead,
UploadTimes: uploadTimes,
DownloadTimes: downloadTimes,
DownloadTTFB: downloadTTFB,
Error: retError,
}, nil
}
// To collect RX stats during "mc support perf net"
+4 -4
View File
@@ -305,20 +305,20 @@ func waitForFormatErasure(firstDisk bool, endpoints Endpoints, poolCount, setCou
switch err {
case errNotFirstDisk:
// Fresh setup, wait for first server to be up.
logger.Info("Waiting for the first server to format the disks (elapsed %s)\n", getElapsedTime())
logger.Info("Waiting for the first server to format the drives (elapsed %s)\n", getElapsedTime())
continue
case errFirstDiskWait:
// Fresh setup, wait for other servers to come up.
logger.Info("Waiting for all other servers to be online to format the disks (elapses %s)\n", getElapsedTime())
logger.Info("Waiting for all other servers to be online to format the drives (elapses %s)\n", getElapsedTime())
continue
case errErasureReadQuorum:
// no quorum available continue to wait for minimum number of servers.
logger.Info("Waiting for a minimum of %d disks to come online (elapsed %s)\n",
logger.Info("Waiting for a minimum of %d drives to come online (elapsed %s)\n",
len(endpoints)/2, getElapsedTime())
continue
case errErasureWriteQuorum:
// no quorum available continue to wait for minimum number of servers.
logger.Info("Waiting for a minimum of %d disks to come online (elapsed %s)\n",
logger.Info("Waiting for a minimum of %d drives to come online (elapsed %s)\n",
(len(endpoints)/2)+1, getElapsedTime())
continue
case errErasureV3ThisEmpty:
+2 -2
View File
@@ -631,10 +631,10 @@ func serverMain(ctx *cli.Context) {
// initialize the new disk cache objects.
if globalCacheConfig.Enabled {
logger.Info(color.Yellow("WARNING: Disk caching is deprecated for single/multi drive MinIO setups. Please migrate to using MinIO S3 gateway instead of disk caching"))
logger.Info(color.Yellow("WARNING: Drive caching is deprecated for single/multi drive MinIO setups. Please migrate to using MinIO S3 gateway instead of drive caching"))
var cacheAPI CacheObjectLayer
cacheAPI, err = newServerCacheObjects(GlobalContext, globalCacheConfig)
logger.FatalIf(err, "Unable to initialize disk caching")
logger.FatalIf(err, "Unable to initialize drive caching")
setCacheObjectLayer(cacheAPI)
}
+2 -2
View File
@@ -31,7 +31,7 @@ func TestNewObjectLayer(t *testing.T) {
nDisks := 1
disks, err := getRandomDisks(nDisks)
if err != nil {
t.Fatal("Failed to create disks for the backend")
t.Fatal("Failed to create drives for the backend")
}
defer removeRoots(disks)
@@ -50,7 +50,7 @@ func TestNewObjectLayer(t *testing.T) {
nDisks = 16
disks, err = getRandomDisks(nDisks)
if err != nil {
t.Fatal("Failed to create disks for the backend")
t.Fatal("Failed to create drives for the backend")
}
defer removeRoots(disks)
+1 -1
View File
@@ -206,7 +206,7 @@ func getStorageInfoMsg(storageInfo StorageInfo) string {
onlineDisks, offlineDisks := getOnlineOfflineDisksStats(storageInfo.Disks)
if storageInfo.Backend.Type == madmin.Erasure {
if offlineDisks.Sum() > 0 {
mcMessage = "Use `mc admin info` to look for latest server/disk info\n"
mcMessage = "Use `mc admin info` to look for latest server/drive info\n"
}
diskInfo := fmt.Sprintf(" %d Online, %d Offline. ", onlineDisks.Sum(), offlineDisks.Sum())
+3 -3
View File
@@ -542,7 +542,7 @@ func (c *SiteReplicationSys) PeerJoinReq(ctx context.Context, arg madmin.SRPeerJ
ServiceAccountAccessKey: arg.SvcAcctAccessKey,
}
if err = c.saveToDisk(ctx, state); err != nil {
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to disk on %s: %v", ourName, err))
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to drive on %s: %v", ourName, err))
}
return nil
}
@@ -2143,7 +2143,7 @@ func (c *SiteReplicationSys) InternalRemoveReq(ctx context.Context, objectAPI Ob
}
if err := c.saveToDisk(ctx, state); err != nil {
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to disk on %s: %v", ourName, err))
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to drive on %s: %v", ourName, err))
}
return nil
}
@@ -3492,7 +3492,7 @@ func (c *SiteReplicationSys) PeerEditReq(ctx context.Context, arg madmin.PeerInf
}
}
if err := c.saveToDisk(ctx, c.state); err != nil {
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to disk on %s: %v", ourName, err))
return errSRBackendIssue(fmt.Errorf("unable to save cluster-replication state to drive on %s: %v", ourName, err))
}
return nil
}
+14
View File
@@ -94,6 +94,9 @@ func objectSpeedTest(ctx context.Context, opts speedTestOpts) chan madmin.SpeedT
result.GETStats.ObjectsPerSec = throughputHighestGet / uint64(opts.objectSize) / uint64(durationSecs)
result.PUTStats.ThroughputPerSec = throughputHighestPut / uint64(durationSecs)
result.PUTStats.ObjectsPerSec = throughputHighestPut / uint64(opts.objectSize) / uint64(durationSecs)
var totalUploadTimes madmin.TimeDurations
var totalDownloadTimes madmin.TimeDurations
var totalDownloadTTFB madmin.TimeDurations
for i := 0; i < len(throughputHighestResults); i++ {
errStr := ""
if throughputHighestResults[i].Error != "" {
@@ -116,14 +119,23 @@ func objectSpeedTest(ctx context.Context, opts speedTestOpts) chan madmin.SpeedT
ObjectsPerSec: throughputHighestResults[i].Uploads / uint64(opts.objectSize) / uint64(durationSecs),
Err: errStr,
})
result.GETStats.Servers = append(result.GETStats.Servers, madmin.SpeedTestStatServer{
Endpoint: throughputHighestResults[i].Endpoint,
ThroughputPerSec: throughputHighestResults[i].Downloads / uint64(durationSecs),
ObjectsPerSec: throughputHighestResults[i].Downloads / uint64(opts.objectSize) / uint64(durationSecs),
Err: errStr,
})
totalUploadTimes = append(totalUploadTimes, throughputHighestResults[i].UploadTimes...)
totalDownloadTimes = append(totalDownloadTimes, throughputHighestResults[i].DownloadTimes...)
totalDownloadTTFB = append(totalDownloadTTFB, throughputHighestResults[i].DownloadTTFB...)
}
result.PUTStats.Response = totalUploadTimes.Measure()
result.GETStats.Response = totalDownloadTimes.Measure()
result.GETStats.TTFB = totalDownloadTTFB.Measure()
result.Size = opts.objectSize
result.Disks = globalEndpoints.NEndpoints()
result.Servers = len(globalNotificationSys.peerClients) + 1
@@ -185,6 +197,8 @@ func objectSpeedTest(ctx context.Context, opts speedTestOpts) chan madmin.SpeedT
break
}
// We break if we did not see 2.5% growth rate in total GET
// requests, we have reached our peak at this point.
doBreak := float64(totalGet-throughputHighestGet)/float64(totalGet) < 0.025
throughputHighestGet = totalGet
+8 -8
View File
@@ -56,8 +56,8 @@ func BenchmarkDecodeDiskInfoMsgp(b *testing.B) {
FSType: "xfs",
RootDisk: true,
Healing: true,
Endpoint: "http://localhost:9001/tmp/disk1",
MountPath: "/tmp/disk1",
Endpoint: "http://localhost:9001/tmp/drive1",
MountPath: "/tmp/drive1",
ID: "uuid",
Error: "",
}
@@ -85,8 +85,8 @@ func BenchmarkDecodeDiskInfoGOB(b *testing.B) {
FSType: "xfs",
RootDisk: true,
Healing: true,
Endpoint: "http://localhost:9001/tmp/disk1",
MountPath: "/tmp/disk1",
Endpoint: "http://localhost:9001/tmp/drive1",
MountPath: "/tmp/drive1",
ID: "uuid",
Error: "",
}
@@ -115,8 +115,8 @@ func BenchmarkEncodeDiskInfoMsgp(b *testing.B) {
FSType: "xfs",
RootDisk: true,
Healing: true,
Endpoint: "http://localhost:9001/tmp/disk1",
MountPath: "/tmp/disk1",
Endpoint: "http://localhost:9001/tmp/drive1",
MountPath: "/tmp/drive1",
ID: "uuid",
Error: "",
}
@@ -140,8 +140,8 @@ func BenchmarkEncodeDiskInfoGOB(b *testing.B) {
FSType: "xfs",
RootDisk: true,
Healing: true,
Endpoint: "http://localhost:9001/tmp/disk1",
MountPath: "/tmp/disk1",
Endpoint: "http://localhost:9001/tmp/drive1",
MountPath: "/tmp/drive1",
ID: "uuid",
Error: "",
}
+13 -13
View File
@@ -28,34 +28,34 @@ import (
var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/minio/minio/issues")
// errCorruptedFormat - corrupted backend format.
var errCorruptedFormat = StorageErr("corrupted backend format, specified disk mount has unexpected previous content")
var errCorruptedFormat = StorageErr("corrupted backend format, specified drive mount has unexpected previous content")
// errUnformattedDisk - unformatted disk found.
var errUnformattedDisk = StorageErr("unformatted disk found")
var errUnformattedDisk = StorageErr("unformatted drive found")
// errInconsistentDisk - inconsistent disk found.
var errInconsistentDisk = StorageErr("inconsistent disk found")
var errInconsistentDisk = StorageErr("inconsistent drive found")
// errUnsupporteDisk - when disk does not support O_DIRECT flag.
var errUnsupportedDisk = StorageErr("disk does not support O_DIRECT")
var errUnsupportedDisk = StorageErr("drive does not support O_DIRECT")
// errDiskFull - cannot create volume or files when disk is full.
var errDiskFull = StorageErr("disk path full")
var errDiskFull = StorageErr("drive path full")
// errDiskNotDir - cannot use storage disk if its not a directory
var errDiskNotDir = StorageErr("disk is not directory or mountpoint")
var errDiskNotDir = StorageErr("drive is not directory or mountpoint")
// errDiskNotFound - cannot find the underlying configured disk anymore.
var errDiskNotFound = StorageErr("disk not found")
var errDiskNotFound = StorageErr("drive not found")
// errFaultyRemoteDisk - remote disk is faulty.
var errFaultyRemoteDisk = StorageErr("remote disk is faulty")
var errFaultyRemoteDisk = StorageErr("remote drive is faulty")
// errFaultyDisk - disk is faulty.
var errFaultyDisk = StorageErr("disk is faulty")
var errFaultyDisk = StorageErr("drive is faulty")
// errDiskAccessDenied - we don't have write permissions on disk.
var errDiskAccessDenied = StorageErr("disk access denied")
var errDiskAccessDenied = StorageErr("drive access denied")
// errFileNotFound - cannot find the file.
var errFileNotFound = StorageErr("file not found")
@@ -101,7 +101,7 @@ var errBitrotHashAlgoInvalid = StorageErr("bit-rot hash algorithm is invalid")
var errCrossDeviceLink = StorageErr("Rename across devices not allowed, please fix your backend configuration")
// errMinDiskSize - cannot create volume or files when disk size is less than threshold.
var errMinDiskSize = StorageErr("The disk size is less than 900MiB threshold")
var errMinDiskSize = StorageErr("The drive size is less than 900MiB threshold")
// errLessData - returned when less data available than what was requested.
var errLessData = StorageErr("less data available than what was requested")
@@ -117,10 +117,10 @@ var errDoneForNow = errors.New("done for now")
var errSkipFile = errors.New("skip this file")
// Returned by FS drive mode when a fresh disk is specified.
var errFreshDisk = errors.New("FS backend requires existing disk")
var errFreshDisk = errors.New("FS backend requires existing drive")
// errXLBackend XL drive mode requires fresh deployment.
var errXLBackend = errors.New("XL backend requires fresh disk")
var errXLBackend = errors.New("XL backend requires fresh drive")
// StorageErr represents error generated by xlStorage call.
type StorageErr string
+9 -9
View File
@@ -49,7 +49,7 @@ import (
xnet "github.com/minio/pkg/net"
)
var errDiskStale = errors.New("disk stale")
var errDiskStale = errors.New("drive stale")
// To abstract a disk over network.
type storageRESTServer struct {
@@ -1171,17 +1171,17 @@ func logFatalErrs(err error, endpoint Endpoint, exit bool) {
case errors.Is(err, errUnsupportedDisk):
var hint string
if endpoint.URL != nil {
hint = fmt.Sprintf("Disk '%s' does not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support", endpoint.Path)
hint = fmt.Sprintf("Drive '%s' does not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support", endpoint.Path)
} else {
hint = "Disks do not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support"
hint = "Drives do not support O_DIRECT flags, MinIO erasure coding requires filesystems with O_DIRECT support"
}
logger.Fatal(config.ErrUnsupportedBackend(err).Hint(hint), "Unable to initialize backend")
case errors.Is(err, errDiskNotDir):
var hint string
if endpoint.URL != nil {
hint = fmt.Sprintf("Disk '%s' is not a directory, MinIO erasure coding needs a directory", endpoint.Path)
hint = fmt.Sprintf("Drive '%s' is not a directory, MinIO erasure coding needs a directory", endpoint.Path)
} else {
hint = "Disks are not directories, MinIO erasure coding needs directories"
hint = "Drives are not directories, MinIO erasure coding needs directories"
}
logger.Fatal(config.ErrUnableToWriteInBackend(err).Hint(hint), "Unable to initialize backend")
case errors.Is(err, errDiskAccessDenied):
@@ -1200,25 +1200,25 @@ func logFatalErrs(err error, endpoint Endpoint, exit bool) {
hint = fmt.Sprintf("Run the following command to add write permissions: `sudo chown -R %s. <path> && sudo chmod u+rxw <path>`", username)
}
if !exit {
logger.LogIf(GlobalContext, fmt.Errorf("disk is not writable %s, %s", endpoint, hint))
logger.LogIf(GlobalContext, fmt.Errorf("Drive is not writable %s, %s", endpoint, hint))
} else {
logger.Fatal(config.ErrUnableToWriteInBackend(err).Hint(hint), "Unable to initialize backend")
}
case errors.Is(err, errFaultyDisk):
if !exit {
logger.LogIf(GlobalContext, fmt.Errorf("disk is faulty at %s, please replace the drive - disk will be offline", endpoint))
logger.LogIf(GlobalContext, fmt.Errorf("Drive is faulty at %s, please replace the drive - drive will be offline", endpoint))
} else {
logger.Fatal(err, "Unable to initialize backend")
}
case errors.Is(err, errDiskFull):
if !exit {
logger.LogIf(GlobalContext, fmt.Errorf("disk is already full at %s, incoming I/O will fail - disk will be offline", endpoint))
logger.LogIf(GlobalContext, fmt.Errorf("Drive is already full at %s, incoming I/O will fail - drive will be offline", endpoint))
} else {
logger.Fatal(err, "Unable to initialize backend")
}
default:
if !exit {
logger.LogIf(GlobalContext, fmt.Errorf("disk returned an unexpected error at %s, please investigate - disk will be offline (%w)", endpoint, err))
logger.LogIf(GlobalContext, fmt.Errorf("Drive returned an unexpected error at %s, please investigate - drive will be offline (%w)", endpoint, err))
} else {
logger.Fatal(err, "Unable to initialize backend")
}
+1 -1
View File
@@ -1898,7 +1898,7 @@ func ExecObjectLayerStaleFilesTest(t *testing.T, objTest objTestStaleFilesType)
nDisks := 16
erasureDisks, err := getRandomDisks(nDisks)
if err != nil {
t.Fatalf("Initialization of disks for Erasure setup: %s", err)
t.Fatalf("Initialization of drives for Erasure setup: %s", err)
}
objLayer, _, err := initObjectLayer(ctx, mustGetPoolEndpoints(erasureDisks...))
if err != nil {
+3 -3
View File
@@ -80,7 +80,7 @@ func initTierDeletionJournal(ctx context.Context) (*tierJournal, error) {
return j, nil
}
return nil, errors.New("no local disk found")
return nil, errors.New("no local drive found")
}
// rotate rotates the journal. If a read-only journal already exists it does
@@ -237,7 +237,7 @@ func (jd *tierDiskJournal) Open() error {
}
var err error
jd.file, err = os.OpenFile(jd.JournalPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY|writeMode, 0o666)
jd.file, err = OpenFile(jd.JournalPath(), os.O_APPEND|os.O_CREATE|os.O_WRONLY|writeMode, 0o666)
if err != nil {
return err
}
@@ -259,7 +259,7 @@ func (jd *tierDiskJournal) Open() error {
}
func (jd *tierDiskJournal) OpenRO() (io.ReadCloser, error) {
file, err := os.Open(jd.ReadOnlyPath())
file, err := Open(jd.ReadOnlyPath())
if err != nil {
return nil, err
}
+2 -2
View File
@@ -57,10 +57,10 @@ var errInvalidRangeSource = errors.New("Range specified exceeds source object si
// error returned by disks which are to be initialized are waiting for the
// first server to initialize them in distributed set to initialize them.
var errNotFirstDisk = errors.New("Not first disk")
var errNotFirstDisk = errors.New("Not first drive")
// error returned by first disk waiting to initialize other servers.
var errFirstDiskWait = errors.New("Waiting on other disks")
var errFirstDiskWait = errors.New("Waiting on other drives")
// error returned for a negative actual size.
var errInvalidDecompressedSize = errors.New("Invalid Decompressed Size")
+1 -1
View File
@@ -181,7 +181,7 @@ func IsBOSH() bool {
// Check if this is Helm package installation and report helm chart version
func getHelmVersion(helmInfoFilePath string) string {
// Read the file exists.
helmInfoFile, err := os.Open(helmInfoFilePath)
helmInfoFile, err := Open(helmInfoFilePath)
if err != nil {
// Log errors and return "" as MinIO can be deployed
// without Helm charts as well.
+2 -2
View File
@@ -757,7 +757,7 @@ func (p *xlStorageDiskIDCheck) checkHealth(ctx context.Context) (err error) {
t = time.Since(time.Unix(0, atomic.LoadInt64(&p.health.lastSuccess)))
if t > maxTimeSinceLastSuccess {
if atomic.CompareAndSwapInt32(&p.health.status, diskHealthOK, diskHealthFaulty) {
logger.LogAlwaysIf(ctx, fmt.Errorf("taking disk %s offline, time since last response %v", p.storage.String(), t.Round(time.Millisecond)))
logger.LogAlwaysIf(ctx, fmt.Errorf("taking drive %s offline, time since last response %v", p.storage.String(), t.Round(time.Millisecond)))
go p.monitorDiskStatus()
}
return errFaultyDisk
@@ -789,7 +789,7 @@ func (p *xlStorageDiskIDCheck) monitorDiskStatus() {
Force: false,
})
if err == nil {
logger.Info("Able to read+write, bringing disk %s online.", p.storage.String())
logger.Info("Able to read+write, bringing drive %s online.", p.storage.String())
atomic.StoreInt32(&p.health.status, diskHealthOK)
return
}
+13 -7
View File
@@ -394,7 +394,7 @@ func (s *xlStorage) readMetadataWithDMTime(ctx context.Context, itemPath string)
return nil, time.Time{}, err
}
f, err := OpenFile(itemPath, readMode, 0)
f, err := OpenFile(itemPath, readMode, 0o666)
if err != nil {
return nil, time.Time{}, err
}
@@ -751,18 +751,24 @@ func (s *xlStorage) MakeVol(ctx context.Context, volume string) error {
}
// ListVols - list volumes.
func (s *xlStorage) ListVols(context.Context) (volsInfo []VolInfo, err error) {
return listVols(s.diskPath)
func (s *xlStorage) ListVols(ctx context.Context) (volsInfo []VolInfo, err error) {
return listVols(ctx, s.diskPath)
}
// List all the volumes from diskPath.
func listVols(dirPath string) ([]VolInfo, error) {
func listVols(ctx context.Context, dirPath string) ([]VolInfo, error) {
if err := checkPathLength(dirPath); err != nil {
return nil, err
}
entries, err := readDir(dirPath)
if err != nil {
return nil, errDiskNotFound
logger.LogIf(ctx, err)
if errors.Is(err, errFileAccessDenied) {
return nil, errDiskAccessDenied
} else if errors.Is(err, errFileNotFound) {
return nil, errDiskNotFound
}
return nil, err
}
volsInfo := make([]VolInfo, 0, len(entries))
for _, entry := range entries {
@@ -1547,7 +1553,7 @@ func (s *xlStorage) ReadFile(ctx context.Context, volume string, path string, of
}
// Open the file for reading.
file, err := Open(filePath)
file, err := OpenFile(filePath, readMode, 0o666)
if err != nil {
switch {
case osIsNotExist(err):
@@ -2515,7 +2521,7 @@ func (s *xlStorage) RenameFile(ctx context.Context, srcVolume, srcPath, dstVolum
func (s *xlStorage) bitrotVerify(ctx context.Context, partPath string, partSize int64, algo BitrotAlgorithm, sum []byte, shardSize int64) error {
// Open the file for reading.
file, err := Open(partPath)
file, err := OpenFile(partPath, readMode, 0o666)
if err != nil {
return osErrToFileErr(err)
}
+4 -4
View File
@@ -659,7 +659,7 @@ func TestXLStorageDeleteVol(t *testing.T) {
// should fail with disk not found.
err = xlStorageDeletedStorage.DeleteVol(context.Background(), "Del-Vol", false)
if err != errDiskNotFound {
t.Errorf("Expected: \"Disk not found\", got \"%s\"", err)
t.Errorf("Expected: \"Drive not found\", got \"%s\"", err)
}
}
@@ -723,7 +723,7 @@ func TestXLStorageStatVol(t *testing.T) {
// should fail with disk not found.
_, err = xlStorageDeletedStorage.StatVol(context.Background(), "Stat vol")
if err != errDiskNotFound {
t.Errorf("Expected: \"Disk not found\", got \"%s\"", err)
t.Errorf("Expected: \"Drive not found\", got \"%s\"", err)
}
}
@@ -902,7 +902,7 @@ func TestXLStorageListDir(t *testing.T) {
Force: false,
})
if err != errDiskNotFound {
t.Errorf("Expected: \"Disk not found\", got \"%s\"", err)
t.Errorf("Expected: \"Drive not found\", got \"%s\"", err)
}
}
@@ -1044,7 +1044,7 @@ func TestXLStorageDeleteFile(t *testing.T) {
Force: false,
})
if err != errDiskNotFound {
t.Errorf("Expected: \"Disk not found\", got \"%s\"", err)
t.Errorf("Expected: \"Drive not found\", got \"%s\"", err)
}
}
+1 -1
View File
@@ -128,7 +128,7 @@ NOTE:
"hosts": {
"poolId": 1,
"setId": 1,
"disks": [
"drives": [
"/mnt/data1",
"/mnt/data2",
"/mnt/data3",
@@ -838,7 +838,7 @@
"instant": true,
"interval": "",
"intervalFactor": 1,
"legendFormat": "Total online disks in MinIO Cluster",
"legendFormat": "Total online drives in MinIO Cluster",
"metric": "process_start_time_seconds",
"refId": "A",
"step": 60
@@ -846,7 +846,7 @@
],
"timeFrom": null,
"timeShift": null,
"title": "Total Online Disks",
"title": "Total Online Drives",
"type": "stat"
},
{
@@ -1276,7 +1276,7 @@
],
"timeFrom": null,
"timeShift": null,
"title": "Total Offline Disks",
"title": "Total Offline Drives",
"type": "stat"
},
{
@@ -2453,7 +2453,7 @@
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"description": "Number of online disks per MinIO Server",
"description": "Number of online drives per MinIO Server",
"fieldConfig": {
"defaults": {
"links": []
@@ -2,7 +2,7 @@ version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:RELEASE.2022-07-29T19-40-48Z
image: quay.io/minio/minio:RELEASE.2022-08-02T23-59-16Z
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
expose:
- "9000"
-2
View File
@@ -86,8 +86,6 @@ Alternatively, use the following command to generate a private ECDSA key protect
openssl ecparam -genkey -name prime256v1 | openssl ec -aes256 -out private.key -passout pass:PASSWORD
```
**Note:** NIST curves P-384 and P-521 are not currently supported.
#### 3.2.2 Generate a private key with RSA
Use the following command to generate a private key with RSA:
+11 -11
View File
@@ -35,8 +35,8 @@ require (
github.com/hashicorp/golang-lru v0.5.4
github.com/inconshreveable/mousetrap v1.0.0
github.com/json-iterator/go v1.1.12
github.com/klauspost/compress v1.15.8
github.com/klauspost/cpuid/v2 v2.0.14
github.com/klauspost/compress v1.15.9
github.com/klauspost/cpuid/v2 v2.1.0
github.com/klauspost/pgzip v1.2.5
github.com/klauspost/readahead v1.4.0
github.com/klauspost/reedsolomon v1.10.0
@@ -48,9 +48,9 @@ require (
github.com/minio/dperf v0.4.2
github.com/minio/highwayhash v1.0.2
github.com/minio/kes v0.20.0
github.com/minio/madmin-go v1.4.9
github.com/minio/minio-go/v7 v7.0.32
github.com/minio/pkg v1.1.26
github.com/minio/madmin-go v1.4.12
github.com/minio/minio-go/v7 v7.0.34
github.com/minio/pkg v1.3.0
github.com/minio/selfupdate v0.5.0
github.com/minio/sha256-simd v1.0.0
github.com/minio/simdjson-go v0.4.2
@@ -83,9 +83,9 @@ require (
go.etcd.io/etcd/client/v3 v3.5.4
go.uber.org/atomic v1.9.0
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
google.golang.org/api v0.78.0
gopkg.in/yaml.v2 v2.4.0
@@ -164,7 +164,7 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/minio/colorjson v1.0.2 // indirect
github.com/minio/filepath v1.0.0 // indirect
github.com/minio/mc v0.0.0-20220705180830-01b87ecc02ff // indirect
github.com/minio/mc v0.0.0-20220805080128-351d021b924b // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -192,7 +192,7 @@ require (
github.com/rjeczalik/notify v0.9.2 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/rs/xid v1.4.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/tidwall/gjson v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
@@ -207,7 +207,7 @@ require (
go.uber.org/goleak v1.1.12 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/mod v0.5.1 // indirect
golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
@@ -218,6 +218,6 @@ require (
google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/h2non/filetype.v1 v1.0.5 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
)
+24 -17
View File
@@ -511,15 +511,17 @@ github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA=
github.com/klauspost/compress v1.15.8/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.14 h1:QRqdp6bb9M9S5yyKeYteXKuoKE4p0tGlra81fKOpWH8=
github.com/klauspost/cpuid/v2 v2.0.14/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/klauspost/readahead v1.4.0 h1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=
@@ -620,19 +622,19 @@ github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLT
github.com/minio/kes v0.20.0 h1:1tyC51Rr8zTregTESuT/QN/iebNMX7B9t7d3xLNMEpE=
github.com/minio/kes v0.20.0/go.mod h1:3FW1BQkMGQW78yhy+69tUq5bdcf5rnXJizyeKB9a/tc=
github.com/minio/madmin-go v1.3.5/go.mod h1:vGKGboQgGIWx4DuDUaXixjlIEZOCIp6ivJkQoiVaACc=
github.com/minio/madmin-go v1.4.9 h1:kFJhzWlDomaK1l2RT6yMRVDTYBUTcfn7sT5c8PCM/3Q=
github.com/minio/madmin-go v1.4.9/go.mod h1:ez87VmMtsxP7DRxjKJKD4RDNW+nhO2QF9KSzwxBDQ98=
github.com/minio/mc v0.0.0-20220705180830-01b87ecc02ff h1:b5XHy2gDZ+B3xQFhegHdSsQQUp82y6pKowwBCgD7SBU=
github.com/minio/mc v0.0.0-20220705180830-01b87ecc02ff/go.mod h1:z/hyvWFsn5ZLbSaJjr9TlCocFghHmhYuNrtpEpEIn48=
github.com/minio/madmin-go v1.4.12 h1:2jr4QLoJOXkwxpPPSbriopZJ3ExyO4TwF/j57pE6dVQ=
github.com/minio/madmin-go v1.4.12/go.mod h1:ez87VmMtsxP7DRxjKJKD4RDNW+nhO2QF9KSzwxBDQ98=
github.com/minio/mc v0.0.0-20220805080128-351d021b924b h1:ikMXncKqNE/0acH6us6yy3v+gJBP7nGv/3Rc9F7vRio=
github.com/minio/mc v0.0.0-20220805080128-351d021b924b/go.mod h1:YUXIqqgGfFknByv0eeJSMBQl/WGuEN0XkpW68/ghBm0=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.23/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do=
github.com/minio/minio-go/v7 v7.0.32 h1:p9zXF2g+C1rm9ZMZXVLp4sv3WzON+NSb0IF6WdIWV0g=
github.com/minio/minio-go/v7 v7.0.32/go.mod h1:/sjRKkKIA75CKh1iu8E3qBy7ktBmCCDGII0zbXGwbUk=
github.com/minio/minio-go/v7 v7.0.34 h1:JMfS5fudx1mN6V2MMNyCJ7UMrjEzZzIvMgfkWc1Vnjk=
github.com/minio/minio-go/v7 v7.0.34/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/pkg v1.1.20/go.mod h1:Xo7LQshlxGa9shKwJ7NzQbgW4s8T/Wc1cOStR/eUiMY=
github.com/minio/pkg v1.1.26 h1:a8x4sHNBxCiHEkxZ/0EBTLqvV3nMtM2G/A6lXNfXN3U=
github.com/minio/pkg v1.1.26/go.mod h1:z9PfmEI804KFkF6eY4LoGe8IDVvTCsYGVuaf58Dr0WI=
github.com/minio/pkg v1.3.0 h1:myG72OiSi1dMN5kTrdfffzC1VHQaoRwC6jefyH3VGrU=
github.com/minio/pkg v1.3.0/go.mod h1:z9PfmEI804KFkF6eY4LoGe8IDVvTCsYGVuaf58Dr0WI=
github.com/minio/selfupdate v0.5.0 h1:0UH1HlL49+2XByhovKl5FpYTjKfvrQ2sgL1zEXK6mfI=
github.com/minio/selfupdate v0.5.0/go.mod h1:mcDkzMgq8PRcpCRJo/NlPY7U45O5dfYl2Y0Rg7IustY=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
@@ -814,8 +816,9 @@ github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
@@ -960,8 +963,8 @@ golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1050,8 +1053,9 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1176,8 +1180,11 @@ golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -1451,8 +1458,8 @@ gopkg.in/h2non/filetype.v1 v1.0.5 h1:CC1jjJjoEhNVbMhXYalmGBhOBK2V70Q1N850wt/98/Y
gopkg.in/h2non/filetype.v1 v1.0.5/go.mod h1:M0yem4rwSX5lLVrkEuRRp2/NinFMD5vgJ4DlAhZcfNo=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -1,8 +1,8 @@
apiVersion: v1
description: Multi-Cloud Object Storage
name: minio
version: 4.0.8
appVersion: RELEASE.2022-07-29T19-40-48Z
version: 4.0.10
appVersion: RELEASE.2022-08-02T23-59-16Z
keywords:
- minio
- storage
+18
View File
@@ -118,6 +118,24 @@ spec:
- name: MINIO_PROMETHEUS_AUTH_TYPE
value: "public"
{{- end}}
{{- if .Values.oidc.enabled }}
- name: MINIO_IDENTITY_OPENID_CONFIG_URL
value: {{ .Values.oidc.configUrl }}
- name: MINIO_IDENTITY_OPENID_CLIENT_ID
value: {{ .Values.oidc.clientId }}
- name: MINIO_IDENTITY_OPENID_CLIENTs_SECRET
value: {{ .Values.oidc.clientSecret }}
- name: MINIO_IDENTITY_OPENID_CLAIM_NAME
value: {{ .Values.oidc.claimName }}
- name: MINIO_IDENTITY_OPENID_CLAIM_PREFIX
value: {{ .Values.oidc.claimPrefix }}
- name: MINIO_IDENTITY_OPENID_SCOPES
value: {{ .Values.oidc.scopes }}
- name: MINIO_IDENTITY_OPENID_REDIRECT_URI
value: {{ .Values.oidc.redirectUri }}
- name: MINIO_IDENTITY_OPENID_COMMENT
value: {{ .Values.oidc.comment }}
{{- end}}
{{- if .Values.etcd.endpoints }}
- name: MINIO_ETCD_ENDPOINTS
value: {{ join "," .Values.etcd.endpoints | quote }}
@@ -64,7 +64,7 @@ spec:
name: {{ tpl .existingSecret $global }}
items:
- key: {{ .existingSecretKey }}
path: secrets/{{ tpl .accessKey $global }}
path: secrets/{{ tpl .existingSecretKey $global }}
{{- end }}
{{- end }}
{{- if .Values.tls.enabled }}
+18
View File
@@ -154,6 +154,24 @@ spec:
- name: MINIO_PROMETHEUS_AUTH_TYPE
value: "public"
{{- end}}
{{- if .Values.oidc.enabled }}
- name: MINIO_IDENTITY_OPENID_CONFIG_URL
value: {{ .Values.oidc.configUrl }}
- name: MINIO_IDENTITY_OPENID_CLIENT_ID
value: {{ .Values.oidc.clientId }}
- name: MINIO_IDENTITY_OPENID_CLIENT_SECRET
value: {{ .Values.oidc.clientSecret }}
- name: MINIO_IDENTITY_OPENID_CLAIM_NAME
value: {{ .Values.oidc.claimName }}
- name: MINIO_IDENTITY_OPENID_CLAIM_PREFIX
value: {{ .Values.oidc.claimPrefix }}
- name: MINIO_IDENTITY_OPENID_SCOPES
value: {{ .Values.oidc.scopes }}
- name: MINIO_IDENTITY_OPENID_REDIRECT_URI
value: {{ .Values.oidc.redirectUri }}
- name: MINIO_IDENTITY_OPENID_COMMENT
value: {{ .Values.oidc.comment }}
{{- end}}
{{- range $key, $val := .Values.environment }}
- name: {{ $key }}
value: {{ $val | quote }}
+16 -1
View File
@@ -14,7 +14,7 @@ clusterDomain: cluster.local
##
image:
repository: quay.io/minio/minio
tag: RELEASE.2022-07-29T19-40-48Z
tag: RELEASE.2022-08-02T23-59-16Z
pullPolicy: IfNotPresent
imagePullSecrets: []
@@ -421,6 +421,21 @@ environment:
##
# extraSecret: minio-extraenv
## OpenID Identity Management
## The following section documents environment variables for enabling external identity management using an OpenID Connect (OIDC)-compatible provider.
## See https://docs.min.io/minio/baremetal/security/openid-external-identity-management/external-authentication-with-openid-identity-provider.html#minio-external-identity-management-openid for a tutorial on using these variables.
oidc:
enabled: false
configUrl: "https://identity-provider-url/.well-known/openid-configuration"
clientId: "minio"
clientSecret: ""
claimName: "policy"
scopes: "openid,profile,email"
redirectUri: "https://console-endpoint-url/oauth_callback"
# Can leave empty
claimPrefix: ""
comment: ""
networkPolicy:
enabled: false
allowExternal: true
+107 -63
View File
@@ -1,9 +1,53 @@
apiVersion: v1
entries:
minio:
- apiVersion: v1
appVersion: RELEASE.2022-08-02T23-59-16Z
created: "2022-08-04T09:08:58.243832675-07:00"
description: Multi-Cloud Object Storage
digest: 841d87788fb094d6a7d8a91e91821fe1e847bc952e054c781fc93742d112e18a
home: https://min.io
icon: https://min.io/resources/img/logo/MINIO_wordmark.png
keywords:
- minio
- storage
- object-storage
- s3
- cluster
maintainers:
- email: dev@minio.io
name: MinIO, Inc
name: minio
sources:
- https://github.com/minio/minio
urls:
- https://charts.min.io/helm-releases/minio-4.0.10.tgz
version: 4.0.10
- apiVersion: v1
appVersion: RELEASE.2022-08-02T23-59-16Z
created: "2022-08-04T09:08:58.25388547-07:00"
description: Multi-Cloud Object Storage
digest: 6f1a78382df3215deac07495a5e7de7009a1153b4cf6cb565630652a69aec4cf
home: https://min.io
icon: https://min.io/resources/img/logo/MINIO_wordmark.png
keywords:
- minio
- storage
- object-storage
- s3
- cluster
maintainers:
- email: dev@minio.io
name: MinIO, Inc
name: minio
sources:
- https://github.com/minio/minio
urls:
- https://charts.min.io/helm-releases/minio-4.0.9.tgz
version: 4.0.9
- apiVersion: v1
appVersion: RELEASE.2022-07-29T19-40-48Z
created: "2022-07-29T16:39:37.342971174-07:00"
created: "2022-08-04T09:08:58.252543461-07:00"
description: Multi-Cloud Object Storage
digest: d11db37963636922cb778b6bc0ad2ca4724cb391ea7b785995ada52467d7dd83
home: https://min.io
@@ -25,7 +69,7 @@ entries:
version: 4.0.8
- apiVersion: v1
appVersion: RELEASE.2022-07-26T00-53-03Z
created: "2022-07-29T16:39:37.341994234-07:00"
created: "2022-08-04T09:08:58.251273001-07:00"
description: Multi-Cloud Object Storage
digest: ca775e08c84331bb5029d4d29867d30c16e2c62e897788eb432212a756e91e4e
home: https://min.io
@@ -47,7 +91,7 @@ entries:
version: 4.0.7
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2022-07-29T16:39:37.340289687-07:00"
created: "2022-08-04T09:08:58.249722259-07:00"
description: Multi-Cloud Object Storage
digest: 06542b8f3d149d5908b15de9a8d6f8cf304af0213830be56dc315785d14f9ccd
home: https://min.io
@@ -69,7 +113,7 @@ entries:
version: 4.0.6
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2022-07-29T16:39:37.339109559-07:00"
created: "2022-08-04T09:08:58.248333148-07:00"
description: Multi-Cloud Object Storage
digest: dd2676362f067454a496cdd293609d0c904b08f521625af49f95402a024ba1f5
home: https://min.io
@@ -91,7 +135,7 @@ entries:
version: 4.0.5
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2022-07-29T16:39:37.338080173-07:00"
created: "2022-08-04T09:08:58.247200301-07:00"
description: Multi-Cloud Object Storage
digest: bab9ef192d4eda4c572ad0ce0cf551736c847f582d1837d6833ee10543c23167
home: https://min.io
@@ -113,7 +157,7 @@ entries:
version: 4.0.4
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2022-07-29T16:39:37.336960542-07:00"
created: "2022-08-04T09:08:58.246137844-07:00"
description: Multi-Cloud Object Storage
digest: c770bb9841c76576e4e8573f78b0ec33e0d729504c9667e67ad62d48df5ed64c
home: https://min.io
@@ -135,7 +179,7 @@ entries:
version: 4.0.3
- apiVersion: v1
appVersion: RELEASE.2022-05-08T23-50-31Z
created: "2022-07-29T16:39:37.335803796-07:00"
created: "2022-08-04T09:08:58.244996488-07:00"
description: Multi-Cloud Object Storage
digest: 95835f4199d963e2a23a2493610b348e6f2ff8b71c1a648c4a3b84af9b7a83eb
home: https://min.io
@@ -157,7 +201,7 @@ entries:
version: 4.0.2
- apiVersion: v1
appVersion: RELEASE.2022-04-30T22-23-53Z
created: "2022-07-29T16:39:37.334606142-07:00"
created: "2022-08-04T09:08:58.242112343-07:00"
description: Multi-Cloud Object Storage
digest: 55a088c403b056e1f055a97426aa11759c3d6cbad38face170fe6cbbec7d568f
home: https://min.io
@@ -179,7 +223,7 @@ entries:
version: 4.0.1
- apiVersion: v1
appVersion: RELEASE.2022-04-26T01-20-24Z
created: "2022-07-29T16:39:37.332950952-07:00"
created: "2022-08-04T09:08:58.240939811-07:00"
description: Multi-Cloud Object Storage
digest: f541237e24336ec3f7f45ae0d523fef694e3a2f9ef648c5b11c15734db6ba2b2
home: https://min.io
@@ -201,7 +245,7 @@ entries:
version: 4.0.0
- apiVersion: v1
appVersion: RELEASE.2022-04-16T04-26-02Z
created: "2022-07-29T16:39:37.331747344-07:00"
created: "2022-08-04T09:08:58.239706827-07:00"
description: Multi-Cloud Object Storage
digest: edc0c3dd6d5246a06b74ba16bb4aff80a6d7225dc9aecf064fd89a8af371b9c1
home: https://min.io
@@ -223,7 +267,7 @@ entries:
version: 3.6.6
- apiVersion: v1
appVersion: RELEASE.2022-04-12T06-55-35Z
created: "2022-07-29T16:39:37.330499201-07:00"
created: "2022-08-04T09:08:58.23846979-07:00"
description: Multi-Cloud Object Storage
digest: 211e89f6b9eb0b9a3583abaa127be60e1f9717a098e6b2858cb9dc1cc50c1650
home: https://min.io
@@ -245,7 +289,7 @@ entries:
version: 3.6.5
- apiVersion: v1
appVersion: RELEASE.2022-04-09T15-09-52Z
created: "2022-07-29T16:39:37.329403232-07:00"
created: "2022-08-04T09:08:58.237385903-07:00"
description: Multi-Cloud Object Storage
digest: 534a879d73b370a18b554b93d0930e1c115419619c4ce4ec7dbaae632acacf06
home: https://min.io
@@ -267,7 +311,7 @@ entries:
version: 3.6.4
- apiVersion: v1
appVersion: RELEASE.2022-03-24T00-43-44Z
created: "2022-07-29T16:39:37.32830842-07:00"
created: "2022-08-04T09:08:58.236139319-07:00"
description: Multi-Cloud Object Storage
digest: 99508b20eb0083a567dcccaf9a6c237e09575ed1d70cd2e8333f89c472d13d75
home: https://min.io
@@ -289,7 +333,7 @@ entries:
version: 3.6.3
- apiVersion: v1
appVersion: RELEASE.2022-03-17T06-34-49Z
created: "2022-07-29T16:39:37.326794288-07:00"
created: "2022-08-04T09:08:58.234503833-07:00"
description: Multi-Cloud Object Storage
digest: b4cd25611ca322b1d23d23112fdfa6b068fd91eefe0b0663b88ff87ea4282495
home: https://min.io
@@ -311,7 +355,7 @@ entries:
version: 3.6.2
- apiVersion: v1
appVersion: RELEASE.2022-03-14T18-25-24Z
created: "2022-07-29T16:39:37.325583838-07:00"
created: "2022-08-04T09:08:58.233424825-07:00"
description: Multi-Cloud Object Storage
digest: d75b88162bfe54740a233bcecf87328bba2ae23d170bec3a35c828bc6fdc224c
home: https://min.io
@@ -333,7 +377,7 @@ entries:
version: 3.6.1
- apiVersion: v1
appVersion: RELEASE.2022-03-11T23-57-45Z
created: "2022-07-29T16:39:37.324523917-07:00"
created: "2022-08-04T09:08:58.23230165-07:00"
description: Multi-Cloud Object Storage
digest: 22e53a1184a21a679bc7d8b94e955777f3506340fc29da5ab0cb6d729bdbde8d
home: https://min.io
@@ -355,7 +399,7 @@ entries:
version: 3.6.0
- apiVersion: v1
appVersion: RELEASE.2022-03-03T21-21-16Z
created: "2022-07-29T16:39:37.323310007-07:00"
created: "2022-08-04T09:08:58.231130041-07:00"
description: Multi-Cloud Object Storage
digest: 6fda968d3fdfd60470c0055a4e1a3bd8e5aee9ad0af5ba2fb7b7b926fdc9e4a0
home: https://min.io
@@ -377,7 +421,7 @@ entries:
version: 3.5.9
- apiVersion: v1
appVersion: RELEASE.2022-02-26T02-54-46Z
created: "2022-07-29T16:39:37.322290091-07:00"
created: "2022-08-04T09:08:58.230080804-07:00"
description: Multi-Cloud Object Storage
digest: 8e015369048a3a82bbd53ad36696786f18561c6b25d14eee9e2c93a7336cef46
home: https://min.io
@@ -399,7 +443,7 @@ entries:
version: 3.5.8
- apiVersion: v1
appVersion: RELEASE.2022-02-18T01-50-10Z
created: "2022-07-29T16:39:37.321127622-07:00"
created: "2022-08-04T09:08:58.22844285-07:00"
description: Multi-Cloud Object Storage
digest: cb3543fe748e5f0d59b3ccf4ab9af8e10b731405ae445d1f5715e30013632373
home: https://min.io
@@ -421,7 +465,7 @@ entries:
version: 3.5.7
- apiVersion: v1
appVersion: RELEASE.2022-02-18T01-50-10Z
created: "2022-07-29T16:39:37.319472315-07:00"
created: "2022-08-04T09:08:58.227311129-07:00"
description: Multi-Cloud Object Storage
digest: f2e359fa5eefffc59abb3d14a8fa94b11ddeaa99f6cd8dd5f40f4e04121000d6
home: https://min.io
@@ -443,7 +487,7 @@ entries:
version: 3.5.6
- apiVersion: v1
appVersion: RELEASE.2022-02-16T00-35-27Z
created: "2022-07-29T16:39:37.318382809-07:00"
created: "2022-08-04T09:08:58.226217616-07:00"
description: Multi-Cloud Object Storage
digest: 529d56cca9d83a3d0e5672e63b6e87b5bcbe10a6b45f7a55ba998cceb32f9c81
home: https://min.io
@@ -465,7 +509,7 @@ entries:
version: 3.5.5
- apiVersion: v1
appVersion: RELEASE.2022-02-12T00-51-25Z
created: "2022-07-29T16:39:37.317505771-07:00"
created: "2022-08-04T09:08:58.22521675-07:00"
description: Multi-Cloud Object Storage
digest: 3d530598f8ece67bec5b7f990d206584893987c713502f9228e4ee24b5535414
home: https://min.io
@@ -487,7 +531,7 @@ entries:
version: 3.5.4
- apiVersion: v1
appVersion: RELEASE.2022-02-12T00-51-25Z
created: "2022-07-29T16:39:37.316559972-07:00"
created: "2022-08-04T09:08:58.224160774-07:00"
description: Multi-Cloud Object Storage
digest: 53937031348b29615f07fc4869b2d668391d8ba9084630a497abd7a7dea9dfb0
home: https://min.io
@@ -509,7 +553,7 @@ entries:
version: 3.5.3
- apiVersion: v1
appVersion: RELEASE.2022-02-07T08-17-33Z
created: "2022-07-29T16:39:37.315513135-07:00"
created: "2022-08-04T09:08:58.223081078-07:00"
description: Multi-Cloud Object Storage
digest: 68d643414ff0d565716c5715034fcbf1af262e041915a5c02eb51ec1a65c1ea0
home: https://min.io
@@ -531,7 +575,7 @@ entries:
version: 3.5.2
- apiVersion: v1
appVersion: RELEASE.2022-02-01T18-00-14Z
created: "2022-07-29T16:39:37.314462761-07:00"
created: "2022-08-04T09:08:58.221516956-07:00"
description: Multi-Cloud Object Storage
digest: a3e855ed0f31233b989fffd775a29d6fbfa0590089010ff16783fd7f142ef6e7
home: https://min.io
@@ -553,7 +597,7 @@ entries:
version: 3.5.1
- apiVersion: v1
appVersion: RELEASE.2022-02-01T18-00-14Z
created: "2022-07-29T16:39:37.312756217-07:00"
created: "2022-08-04T09:08:58.220598727-07:00"
description: Multi-Cloud Object Storage
digest: b1b0ae3c54b4260a698753e11d7781bb8ddc67b7e3fbf0af82796e4cd4ef92a3
home: https://min.io
@@ -575,7 +619,7 @@ entries:
version: 3.5.0
- apiVersion: v1
appVersion: RELEASE.2022-01-28T02-28-16Z
created: "2022-07-29T16:39:37.311587946-07:00"
created: "2022-08-04T09:08:58.219693895-07:00"
description: Multi-Cloud Object Storage
digest: fecf25d2d3fb208c6f894fed642a60780a570b7f6d0adddde846af7236dc80aa
home: https://min.io
@@ -597,7 +641,7 @@ entries:
version: 3.4.8
- apiVersion: v1
appVersion: RELEASE.2022-01-25T19-56-04Z
created: "2022-07-29T16:39:37.310732127-07:00"
created: "2022-08-04T09:08:58.218758028-07:00"
description: Multi-Cloud Object Storage
digest: c78008caa5ce98f64c887630f59d0cbd481cb3f19a7d4e9d3e81bf4e1e45cadc
home: https://min.io
@@ -619,7 +663,7 @@ entries:
version: 3.4.7
- apiVersion: v1
appVersion: RELEASE.2022-01-08T03-11-54Z
created: "2022-07-29T16:39:37.309890982-07:00"
created: "2022-08-04T09:08:58.217845615-07:00"
description: Multi-Cloud Object Storage
digest: 8f2e2691bf897f74ff094dd370ec56ba9d417e5e8926710c14c2ba346330238d
home: https://min.io
@@ -641,7 +685,7 @@ entries:
version: 3.4.6
- apiVersion: v1
appVersion: RELEASE.2022-01-04T07-41-07Z
created: "2022-07-29T16:39:37.309060266-07:00"
created: "2022-08-04T09:08:58.2167898-07:00"
description: Multi-Cloud Object Storage
digest: bacd140f0016fab35f516bde787da6449b3a960c071fad9e4b6563118033ac84
home: https://min.io
@@ -663,7 +707,7 @@ entries:
version: 3.4.5
- apiVersion: v1
appVersion: RELEASE.2021-12-29T06-49-06Z
created: "2022-07-29T16:39:37.308047332-07:00"
created: "2022-08-04T09:08:58.215292363-07:00"
description: Multi-Cloud Object Storage
digest: 48a453ea5ffeef25933904caefd9470bfb26224dfc2d1096bd0031467ba53007
home: https://min.io
@@ -685,7 +729,7 @@ entries:
version: 3.4.4
- apiVersion: v1
appVersion: RELEASE.2021-12-20T22-07-16Z
created: "2022-07-29T16:39:37.307006274-07:00"
created: "2022-08-04T09:08:58.214214407-07:00"
description: Multi-Cloud Object Storage
digest: 47ef4a930713b98f9438ceca913c6e700f85bb25dba5624b056486254b5f0c60
home: https://min.io
@@ -707,7 +751,7 @@ entries:
version: 3.4.3
- apiVersion: v1
appVersion: RELEASE.2021-12-20T22-07-16Z
created: "2022-07-29T16:39:37.305297449-07:00"
created: "2022-08-04T09:08:58.213052355-07:00"
description: Multi-Cloud Object Storage
digest: d6763f7e2ea66810bd55eb225579a9c3b968f9ae1256f45fd469362e55d846ff
home: https://min.io
@@ -729,7 +773,7 @@ entries:
version: 3.4.2
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2022-07-29T16:39:37.304414066-07:00"
created: "2022-08-04T09:08:58.211989739-07:00"
description: Multi-Cloud Object Storage
digest: 2fb822c87216ba3fc2ae51a54a0a3e239aa560d86542991504a841cc2a2b9a37
home: https://min.io
@@ -751,7 +795,7 @@ entries:
version: 3.4.1
- apiVersion: v1
appVersion: RELEASE.2021-12-18T04-42-33Z
created: "2022-07-29T16:39:37.303623867-07:00"
created: "2022-08-04T09:08:58.211100828-07:00"
description: Multi-Cloud Object Storage
digest: fa8ba1aeb1a15316c6be8403416a5e6b5e6139b7166592087e7bddc9e6db5453
home: https://min.io
@@ -773,7 +817,7 @@ entries:
version: 3.4.0
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2022-07-29T16:39:37.302730128-07:00"
created: "2022-08-04T09:08:58.210091543-07:00"
description: Multi-Cloud Object Storage
digest: b9b0af9ca50b8d00868e1f1b989dca275829d9110af6de91bb9b3a398341e894
home: https://min.io
@@ -795,7 +839,7 @@ entries:
version: 3.3.4
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2022-07-29T16:39:37.301936062-07:00"
created: "2022-08-04T09:08:58.208703717-07:00"
description: Multi-Cloud Object Storage
digest: f8b22a5b8fe95a7ddf61b825e17d11c9345fb10e4c126b0d78381608aa300a08
home: https://min.io
@@ -817,7 +861,7 @@ entries:
version: 3.3.3
- apiVersion: v1
appVersion: RELEASE.2021-12-10T23-03-39Z
created: "2022-07-29T16:39:37.301077896-07:00"
created: "2022-08-04T09:08:58.207655631-07:00"
description: Multi-Cloud Object Storage
digest: c48d474f269427abe5ab446f00687d0625b3d1adfc5c73bdb4b21ca9e42853fb
home: https://min.io
@@ -839,7 +883,7 @@ entries:
version: 3.3.2
- apiVersion: v1
appVersion: RELEASE.2021-11-24T23-19-33Z
created: "2022-07-29T16:39:37.299632923-07:00"
created: "2022-08-04T09:08:58.206746456-07:00"
description: Multi-Cloud Object Storage
digest: 7c3da39d9b0090cbf5efedf0cc163a1e2df05becc5152c3add8e837384690bc4
home: https://min.io
@@ -861,7 +905,7 @@ entries:
version: 3.3.1
- apiVersion: v1
appVersion: RELEASE.2021-11-24T23-19-33Z
created: "2022-07-29T16:39:37.298683702-07:00"
created: "2022-08-04T09:08:58.205883172-07:00"
description: Multi-Cloud Object Storage
digest: 50d6590b4cc779c40f81cc13b1586fbe508aa7f3230036c760bfc5f4154fbce4
home: https://min.io
@@ -883,7 +927,7 @@ entries:
version: 3.3.0
- apiVersion: v1
appVersion: RELEASE.2021-10-13T00-23-17Z
created: "2022-07-29T16:39:37.297437447-07:00"
created: "2022-08-04T09:08:58.205004275-07:00"
description: Multi-Cloud Object Storage
digest: 5b797b7208cd904c11a76cd72938c8652160cb5fcd7f09fa41e4e703e6d64054
home: https://min.io
@@ -905,7 +949,7 @@ entries:
version: 3.2.0
- apiVersion: v1
appVersion: RELEASE.2021-10-10T16-53-30Z
created: "2022-07-29T16:39:37.296638572-07:00"
created: "2022-08-04T09:08:58.20397084-07:00"
description: Multi-Cloud Object Storage
digest: e084ac4bb095f071e59f8f08bd092e4ab2404c1ddadacfdce7dbe248f1bafff8
home: https://min.io
@@ -927,7 +971,7 @@ entries:
version: 3.1.9
- apiVersion: v1
appVersion: RELEASE.2021-10-06T23-36-31Z
created: "2022-07-29T16:39:37.295702714-07:00"
created: "2022-08-04T09:08:58.202853766-07:00"
description: Multi-Cloud Object Storage
digest: 2890430a8d9487d1fa5508c26776e4881d0086b2c052aa6bdc65c0e4423b9159
home: https://min.io
@@ -949,7 +993,7 @@ entries:
version: 3.1.8
- apiVersion: v1
appVersion: RELEASE.2021-10-02T16-31-05Z
created: "2022-07-29T16:39:37.294809819-07:00"
created: "2022-08-04T09:08:58.201328524-07:00"
description: Multi-Cloud Object Storage
digest: 01a92196af6c47e3a01e1c68d7cf693a8bc487cba810c2cecff155071e4d6a11
home: https://min.io
@@ -971,7 +1015,7 @@ entries:
version: 3.1.7
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2022-07-29T16:39:37.294026222-07:00"
created: "2022-08-04T09:08:58.200218745-07:00"
description: Multi-Cloud Object Storage
digest: e779d73f80b75f33b9c9d995ab10fa455c9c57ee575ebc54e06725a64cd04310
home: https://min.io
@@ -993,7 +1037,7 @@ entries:
version: 3.1.6
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2022-07-29T16:39:37.291665007-07:00"
created: "2022-08-04T09:08:58.199377564-07:00"
description: Multi-Cloud Object Storage
digest: 19de4bbc8a400f0c2a94c5e85fc25c9bfc666e773fb3e368dd621d5a57dd1c2a
home: https://min.io
@@ -1015,7 +1059,7 @@ entries:
version: 3.1.5
- apiVersion: v1
appVersion: RELEASE.2021-09-18T18-09-59Z
created: "2022-07-29T16:39:37.290864229-07:00"
created: "2022-08-04T09:08:58.198535651-07:00"
description: Multi-Cloud Object Storage
digest: f789d93a171296dd01af0105a5ce067c663597afbb2432faeda293b752b355c0
home: https://min.io
@@ -1037,7 +1081,7 @@ entries:
version: 3.1.4
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2022-07-29T16:39:37.289936482-07:00"
created: "2022-08-04T09:08:58.197701164-07:00"
description: Multi-Cloud Object Storage
digest: e2eb34d31560b012ef6581f0ff6004ea4376c968cbe0daed2d8f3a614a892afb
home: https://min.io
@@ -1059,7 +1103,7 @@ entries:
version: 3.1.3
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2022-07-29T16:39:37.289095277-07:00"
created: "2022-08-04T09:08:58.196849884-07:00"
description: Multi-Cloud Object Storage
digest: 8d7e0cc46b3583abd71b97dc0c071f98321101f90eca17348f1e9e0831be64cd
home: https://min.io
@@ -1081,7 +1125,7 @@ entries:
version: 3.1.2
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2022-07-29T16:39:37.288205835-07:00"
created: "2022-08-04T09:08:58.195661845-07:00"
description: Multi-Cloud Object Storage
digest: 50dcbf366b1b21f4a6fc429d0b884c0c7ff481d0fb95c5e9b3ae157c348dd124
home: https://min.io
@@ -1103,7 +1147,7 @@ entries:
version: 3.1.1
- apiVersion: v1
appVersion: RELEASE.2021-09-09T21-37-07Z
created: "2022-07-29T16:39:37.287243808-07:00"
created: "2022-08-04T09:08:58.19437554-07:00"
description: Multi-Cloud Object Storage
digest: 6c01af55d2e2e5f716eabf6fef3a92a8464d0674529e9bacab292e5478a73b7a
home: https://min.io
@@ -1125,7 +1169,7 @@ entries:
version: 3.1.0
- apiVersion: v1
appVersion: RELEASE.2021-09-03T03-56-13Z
created: "2022-07-29T16:39:37.286100178-07:00"
created: "2022-08-04T09:08:58.193430938-07:00"
description: Multi-Cloud Object Storage
digest: 18e10be4d0458bc590ca9abf753227e0c70f60511495387b8d4fb15a4daf932e
home: https://min.io
@@ -1147,7 +1191,7 @@ entries:
version: 3.0.2
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2022-07-29T16:39:37.284950859-07:00"
created: "2022-08-04T09:08:58.192440923-07:00"
description: Multi-Cloud Object Storage
digest: f5b6e7f6272a9e71aef3b75555f6f756a39eef65cb78873f26451dba79b19906
home: https://min.io
@@ -1169,7 +1213,7 @@ entries:
version: 3.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2022-07-29T16:39:37.284048713-07:00"
created: "2022-08-04T09:08:58.191452883-07:00"
description: Multi-Cloud Object Storage
digest: 6d2ee1336c412affaaf209fdb80215be2a6ebb23ab2443adbaffef9e7df13fab
home: https://min.io
@@ -1191,7 +1235,7 @@ entries:
version: 3.0.0
- apiVersion: v1
appVersion: RELEASE.2021-08-31T05-46-54Z
created: "2022-07-29T16:39:37.283103024-07:00"
created: "2022-08-04T09:08:58.190503764-07:00"
description: Multi-Cloud Object Storage
digest: 0a004aaf5bb61deed6a5c88256d1695ebe2f9ff1553874a93e4acfd75e8d339b
home: https://min.io
@@ -1211,7 +1255,7 @@ entries:
version: 2.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2022-07-29T16:39:37.282144407-07:00"
created: "2022-08-04T09:08:58.189528681-07:00"
description: Multi-Cloud Object Storage
digest: fcd944e837ee481307de6aa3d387ea18c234f995a84c15abb211aab4a4054afc
home: https://min.io
@@ -1231,7 +1275,7 @@ entries:
version: 2.0.0
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2022-07-29T16:39:37.280943466-07:00"
created: "2022-08-04T09:08:58.188032927-07:00"
description: Multi-Cloud Object Storage
digest: 7b6c033d43a856479eb493ab8ca05b230f77c3e42e209e8f298fac6af1a9796f
home: https://min.io
@@ -1251,7 +1295,7 @@ entries:
version: 1.0.5
- apiVersion: v1
appVersion: RELEASE.2021-08-25T00-41-18Z
created: "2022-07-29T16:39:37.279677527-07:00"
created: "2022-08-04T09:08:58.187155379-07:00"
description: Multi-Cloud Object Storage
digest: abd221245ace16c8e0c6c851cf262d1474a5219dcbf25c4b2e7b77142f9c59ed
home: https://min.io
@@ -1271,7 +1315,7 @@ entries:
version: 1.0.4
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2022-07-29T16:39:37.278589589-07:00"
created: "2022-08-04T09:08:58.186308201-07:00"
description: Multi-Cloud Object Storage
digest: 922a333f5413d1042f7aa81929f43767f6ffca9b260c46713f04ce1dda86d57d
home: https://min.io
@@ -1291,7 +1335,7 @@ entries:
version: 1.0.3
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2022-07-29T16:39:37.277344409-07:00"
created: "2022-08-04T09:08:58.185450276-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: 10e22773506bbfb1c66442937956534cf4057b94f06a977db78b8cd223588388
home: https://min.io
@@ -1311,7 +1355,7 @@ entries:
version: 1.0.2
- apiVersion: v1
appVersion: RELEASE.2021-08-20T18-32-01Z
created: "2022-07-29T16:39:37.27655914-07:00"
created: "2022-08-04T09:08:58.182469629-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: ef86ab6df23d6942705da9ef70991b649638c51bc310587d37a425268ba4a06c
home: https://min.io
@@ -1331,7 +1375,7 @@ entries:
version: 1.0.1
- apiVersion: v1
appVersion: RELEASE.2021-08-17T20-53-08Z
created: "2022-07-29T16:39:37.275640295-07:00"
created: "2022-08-04T09:08:58.181538809-07:00"
description: High Performance, Kubernetes Native Object Storage
digest: 1add7608692cbf39aaf9b1252530e566f7b2f306a14e390b0f49b97a20f2b188
home: https://min.io
@@ -1349,4 +1393,4 @@ entries:
urls:
- https://charts.min.io/helm-releases/minio-1.0.0.tgz
version: 1.0.0
generated: "2022-07-29T16:39:37.272439913-07:00"
generated: "2022-08-04T09:08:58.18023578-07:00"
-15
View File
@@ -19,8 +19,6 @@ package config
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/pem"
@@ -103,19 +101,6 @@ func LoadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) {
if err != nil {
return tls.Certificate{}, ErrSSLUnexpectedData(nil).Msg(err.Error())
}
// Ensure that the private key is not a P-384 or P-521 EC key.
// The Go TLS stack does not provide constant-time implementations of P-384 and P-521.
if priv, ok := cert.PrivateKey.(crypto.Signer); ok {
if pub, ok := priv.Public().(*ecdsa.PublicKey); ok {
switch pub.Params().Name {
case "P-384":
fallthrough
case "P-521":
// unfortunately there is no cleaner way to check
return tls.Certificate{}, ErrSSLUnexpectedData(nil).Msg("tls: the ECDSA curve '%s' is not supported", pub.Params().Name)
}
}
}
return cert, nil
}
+121 -11
View File
@@ -22,6 +22,7 @@ import (
"fmt"
"io"
"regexp"
"sort"
"strings"
"github.com/minio/madmin-go"
@@ -51,6 +52,8 @@ const (
Enable = madmin.EnableKey
Comment = madmin.CommentKey
EnvSeparator = "="
// Enable values
EnableOn = madmin.EnableOn
EnableOff = madmin.EnableOff
@@ -836,10 +839,6 @@ func GetSubSys(s string) (subSys string, inputs []string, tgt string, e error) {
return subSys, inputs, tgt, Errorf("unknown sub-system %s", s)
}
if len(inputs) == 1 {
return subSys, inputs, tgt, nil
}
if SubSystemsSingleTargets.Contains(subSystemValue[0]) && len(subSystemValue) == 2 {
return subSys, inputs, tgt, Errorf("sub-system '%s' only supports single target", subSystemValue[0])
}
@@ -1024,7 +1023,8 @@ func (c Config) CheckValidKeys(subSys string, deprecatedKeys []string) error {
// GetAvailableTargets - returns a list of targets configured for the given
// subsystem (whether they are enabled or not). A target could be configured via
// environment variables or via the configuration store. The default target is
// `_` and is always returned.
// `_` and is always returned. The result is sorted so that the default target
// is the first one and the remaining entries are sorted in ascending order.
func (c Config) GetAvailableTargets(subSys string) ([]string, error) {
if SubSystemsSingleTargets.Contains(subSys) {
return []string{Default}, nil
@@ -1036,11 +1036,11 @@ func (c Config) GetAvailableTargets(subSys string) ([]string, error) {
}
kvsMap := c[subSys]
s := set.NewStringSet()
seen := set.NewStringSet()
// Add all targets that are configured in the config store.
for k := range kvsMap {
s.Add(k)
seen.Add(k)
}
// Add targets that are configured via environment variables.
@@ -1050,12 +1050,17 @@ func (c Config) GetAvailableTargets(subSys string) ([]string, error) {
for _, k := range envsWithPrefix {
tgtName := strings.TrimPrefix(k, envVarPrefix)
if tgtName != "" {
s.Add(tgtName)
seen.Add(tgtName)
}
}
}
return s.ToSlice(), nil
seen.Remove(Default)
targets := seen.ToSlice()
sort.Strings(targets)
targets = append([]string{Default}, targets...)
return targets, nil
}
func getEnvVarName(subSys, target, param string) string {
@@ -1063,7 +1068,8 @@ func getEnvVarName(subSys, target, param string) string {
return fmt.Sprintf("%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param))
}
return fmt.Sprintf("%s%s%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param), Default, target)
return fmt.Sprintf("%s%s%s%s%s%s", EnvPrefix, strings.ToUpper(subSys), Default, strings.ToUpper(param),
Default, target)
}
var resolvableSubsystems = set.CreateStringSet(IdentityOpenIDSubSys)
@@ -1106,7 +1112,7 @@ func (c Config) ResolveConfigParam(subSys, target, cfgParam string) (value strin
defValue, isFound := defKVS.Lookup(cfgParam)
// Comments usually are absent from `defKVS`, so we handle it specially.
if cfgParam == Comment {
if !isFound && cfgParam == Comment {
defValue, isFound = "", true
}
if !isFound {
@@ -1194,3 +1200,107 @@ func (c Config) GetResolvedConfigParams(subSys, target string) ([]KVSrc, error)
return r, nil
}
func (c Config) getTargetKVS(subSys, target string) KVS {
store, ok := c[subSys]
if !ok {
return nil
}
return store[target]
}
// EnvPair represents an environment variable and its value.
type EnvPair struct {
Name, Value string
}
// SubsysInfo holds config info for a subsystem target.
type SubsysInfo struct {
SubSys, Target string
Params KVS
// map of config parameter name to EnvPair.
EnvMap map[string]EnvPair
}
// GetSubsysInfo returns `SubsysInfo`s for all targets for the subsystem.
func (c Config) GetSubsysInfo(subSys string) ([]SubsysInfo, error) {
// Check if config param requested is valid.
defKVS1, ok := DefaultKVS[subSys]
if !ok {
return nil, fmt.Errorf("unknown subsystem: %s", subSys)
}
targets, err := c.GetAvailableTargets(subSys)
if err != nil {
return nil, err
}
// The `Comment` configuration variable is optional but is available to be
// set for all sub-systems. It is not present in the `DefaultKVS` map's
// values. To enable fetching a configured comment value from the
// environment we add it to the list of default keys for the subsystem.
defKVS := make([]KV, len(defKVS1), len(defKVS1)+1)
copy(defKVS, defKVS1)
defKVS = append(defKVS, KV{Key: Comment})
r := make([]SubsysInfo, 0, len(targets))
for _, target := range targets {
kvs := c.getTargetKVS(subSys, target)
cs := SubsysInfo{
SubSys: subSys,
Target: target,
Params: kvs,
EnvMap: make(map[string]EnvPair),
}
// Add all env vars that are set.
for _, kv := range defKVS {
envName := getEnvVarName(subSys, target, kv.Key)
envPair := EnvPair{
Name: envName,
Value: env.Get(envName, ""),
}
if envPair.Value != "" {
cs.EnvMap[kv.Key] = envPair
}
}
r = append(r, cs)
}
return r, nil
}
// AddEnvString adds env vars to the given string builder.
func (cs *SubsysInfo) AddEnvString(b *strings.Builder) {
for _, v := range cs.Params {
if ep, ok := cs.EnvMap[v.Key]; ok {
b.WriteString(KvComment)
b.WriteString(KvSpaceSeparator)
b.WriteString(ep.Name)
b.WriteString(EnvSeparator)
b.WriteString(ep.Value)
b.WriteString(KvNewline)
}
}
}
// AddString adds the string representation of the configuration to the given
// builder. When off is true, adds a comment character before the config system
// output.
func (cs *SubsysInfo) AddString(b *strings.Builder, off bool) {
cs.AddEnvString(b)
if off {
b.WriteString(KvComment)
b.WriteString(KvSpaceSeparator)
}
b.WriteString(cs.SubSys)
if cs.Target != Default {
b.WriteString(SubSystemSeparator)
b.WriteString(cs.Target)
}
b.WriteString(KvSpaceSeparator)
b.WriteString(cs.Params.String())
b.WriteString(KvNewline)
}
+1 -1
View File
@@ -214,7 +214,7 @@ Example 1:
ErrUnsupportedBackend = newErrFn(
"Unable to write to the backend",
"Please ensure your disk supports O_DIRECT",
"Please ensure your drive supports O_DIRECT",
"",
)
@@ -132,12 +132,12 @@ func (sc *StorageClass) String() string {
}
// Parses given storageClassEnv and returns a storageClass structure.
// Supported Storage Class format is "Scheme:Number of parity disks".
// Supported Storage Class format is "Scheme:Number of parity drives".
// Currently only supported scheme is "EC".
func parseStorageClass(storageClassEnv string) (sc StorageClass, err error) {
s := strings.Split(storageClassEnv, ":")
// only two elements allowed in the string - "scheme" and "number of parity disks"
// only two elements allowed in the string - "scheme" and "number of parity drives"
if len(s) > 2 {
return StorageClass{}, config.ErrStorageClassValue(nil).Msg("Too many sections in " + storageClassEnv)
} else if len(s) < 2 {
@@ -203,7 +203,7 @@ func validateParity(ssParity, rrsParity, setDriveCount int) (err error) {
if ssParity > 0 && rrsParity > 0 {
if ssParity > 0 && ssParity < rrsParity {
return fmt.Errorf("Standard storage class parity disks %d should be greater than or equal to Reduced redundancy storage class parity disks %d", ssParity, rrsParity)
return fmt.Errorf("Standard storage class parity drives %d should be greater than or equal to Reduced redundancy storage class parity drives %d", ssParity, rrsParity)
}
}
return nil
@@ -158,11 +158,11 @@ func TestParityCount(t *testing.T) {
}
parity := scfg.GetParityForSC(tt.sc)
if (tt.disksCount - parity) != tt.expectedData {
t.Errorf("Test %d, Expected data disks %d, got %d", i+1, tt.expectedData, tt.disksCount-parity)
t.Errorf("Test %d, Expected data drives %d, got %d", i+1, tt.expectedData, tt.disksCount-parity)
continue
}
if parity != tt.expectedParity {
t.Errorf("Test %d, Expected parity disks %d, got %d", i+1, tt.expectedParity, parity)
t.Errorf("Test %d, Expected parity drives %d, got %d", i+1, tt.expectedParity, parity)
}
}
}
+1 -1
View File
@@ -41,7 +41,7 @@ func GetInfo(path string) (info Info, err error) {
FSType: getFSType(s.Fstypename[:]),
}
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -41,7 +41,7 @@ func GetInfo(path string) (info Info, err error) {
FSType: getFSType(s.Fstypename[:]),
}
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -46,7 +46,7 @@ func GetInfo(path string) (info Info, err error) {
// XFS can show wrong values at times error out
// in such scenarios.
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -76,7 +76,7 @@ func GetInfo(path string) (info Info, err error) {
// XFS can show wrong values at times error out
// in such scenarios.
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -76,7 +76,7 @@ func GetInfo(path string) (info Info, err error) {
// XFS can show wrong values at times error out
// in such scenarios.
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -41,7 +41,7 @@ func GetInfo(path string) (info Info, err error) {
FSType: string(s.Fstypename[:]),
}
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -41,7 +41,7 @@ func GetInfo(path string) (info Info, err error) {
FSType: getFSType(s.F_fstypename[:]),
}
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -41,7 +41,7 @@ func GetInfo(path string) (info Info, err error) {
FSType: getFSType(s.Fstr[:]),
}
if info.Free > info.Total {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'", info.Free, info.Total, path)
}
info.Used = info.Total - info.Free
return info, nil
+1 -1
View File
@@ -70,7 +70,7 @@ func GetInfo(path string) (info Info, err error) {
uintptr(unsafe.Pointer(&lpTotalNumberOfFreeBytes)))
if uint64(lpTotalNumberOfFreeBytes) > uint64(lpTotalNumberOfBytes) {
return info, fmt.Errorf("detected free space (%d) > total disk space (%d), fs corruption at (%s). please run 'fsck'",
return info, fmt.Errorf("detected free space (%d) > total drive space (%d), fs corruption at (%s). please run 'fsck'",
uint64(lpTotalNumberOfFreeBytes), uint64(lpTotalNumberOfBytes), path)
}
+19
View File
@@ -20,6 +20,12 @@ package event
import (
"fmt"
"sync"
"sync/atomic"
)
const (
// The maximum allowed number of concurrent Send() calls to all configured notifications targets
maxConcurrentTargetSendCalls = 20000
)
// Target - event target interface
@@ -34,6 +40,9 @@ type Target interface {
// TargetList - holds list of targets indexed by target ID.
type TargetList struct {
// The number of concurrent async Send calls to all targets
currentSendCalls int64
sync.RWMutex
targets map[TargetID]Target
}
@@ -124,6 +133,14 @@ func (list *TargetList) TargetMap() map[TargetID]Target {
// Send - sends events to targets identified by target IDs.
func (list *TargetList) Send(event Event, targetIDset TargetIDSet, resCh chan<- TargetIDResult) {
if atomic.LoadInt64(&list.currentSendCalls) > maxConcurrentTargetSendCalls {
err := fmt.Errorf("concurrent target notifications exceeded %d", maxConcurrentTargetSendCalls)
for id := range targetIDset {
resCh <- TargetIDResult{ID: id, Err: err}
}
return
}
go func() {
var wg sync.WaitGroup
for id := range targetIDset {
@@ -133,6 +150,8 @@ func (list *TargetList) Send(event Event, targetIDset TargetIDSet, resCh chan<-
if ok {
wg.Add(1)
go func(id TargetID, target Target) {
atomic.AddInt64(&list.currentSendCalls, 1)
defer atomic.AddInt64(&list.currentSendCalls, -1)
defer wg.Done()
tgtRes := TargetIDResult{ID: id}
if err := target.Save(event); err != nil {
+46 -4
View File
@@ -23,8 +23,10 @@ import (
"crypto/x509"
"errors"
"strings"
"sync"
"github.com/minio/kes"
"github.com/minio/pkg/certs"
)
const (
@@ -46,7 +48,11 @@ type Config struct {
// Certificate is the client TLS certificate
// to authenticate to KMS via mTLS.
Certificate tls.Certificate
Certificate *certs.Certificate
// ReloadCertEvents is an event channel that receives
// the reloaded client certificate.
ReloadCertEvents <-chan tls.Certificate
// RootCAs is a set of root CA certificates
// to verify the KMS server TLS certificate.
@@ -64,7 +70,7 @@ func NewWithConfig(config Config) (KMS, error) {
client := kes.NewClientWithConfig("", &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{config.Certificate},
Certificates: []tls.Certificate{config.Certificate.Get()},
RootCAs: config.RootCAs,
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
})
@@ -81,14 +87,35 @@ func NewWithConfig(config Config) (KMS, error) {
}
}
}
return &kesClient{
c := &kesClient{
client: client,
defaultKeyID: config.DefaultKeyID,
bulkAvailable: bulkAvailable,
}, nil
}
go func() {
for {
select {
case certificate := <-config.ReloadCertEvents:
client := kes.NewClientWithConfig("", &tls.Config{
MinVersion: tls.VersionTLS12,
Certificates: []tls.Certificate{certificate},
RootCAs: config.RootCAs,
ClientSessionCache: tls.NewLRUClientSessionCache(tlsClientSessionCacheSize),
})
client.Endpoints = endpoints
c.lock.Lock()
c.client = client
c.lock.Unlock()
}
}
}()
return c, nil
}
type kesClient struct {
lock sync.RWMutex
defaultKeyID string
client *kes.Client
@@ -113,6 +140,9 @@ func (c *kesClient) Stat(ctx context.Context) (Status, error) {
}
func (c *kesClient) Metrics(ctx context.Context) (kes.Metric, error) {
c.lock.RLock()
defer c.lock.RUnlock()
return c.client.Metrics(ctx)
}
@@ -122,6 +152,9 @@ func (c *kesClient) Metrics(ctx context.Context) (kes.Metric, error) {
// If the a key with the same keyID already exists then
// CreateKey returns kes.ErrKeyExists.
func (c *kesClient) CreateKey(ctx context.Context, keyID string) error {
c.lock.RLock()
defer c.lock.RUnlock()
return c.client.CreateKey(ctx, keyID)
}
@@ -134,6 +167,9 @@ func (c *kesClient) CreateKey(ctx context.Context, keyID string) error {
// The same context must be provided when the generated
// key should be decrypted.
func (c *kesClient) GenerateKey(ctx context.Context, keyID string, cryptoCtx Context) (DEK, error) {
c.lock.RLock()
defer c.lock.RUnlock()
if keyID == "" {
keyID = c.defaultKeyID
}
@@ -156,6 +192,9 @@ func (c *kesClient) GenerateKey(ctx context.Context, keyID string, cryptoCtx Con
// server referenced by the key ID. The context must match the
// context value used to generate the ciphertext.
func (c *kesClient) DecryptKey(keyID string, ciphertext []byte, ctx Context) ([]byte, error) {
c.lock.RLock()
defer c.lock.RUnlock()
ctxBytes, err := ctx.MarshalText()
if err != nil {
return nil, err
@@ -164,6 +203,9 @@ func (c *kesClient) DecryptKey(keyID string, ciphertext []byte, ctx Context) ([]
}
func (c *kesClient) DecryptAll(ctx context.Context, keyID string, ciphertexts [][]byte, contexts []Context) ([][]byte, error) {
c.lock.RLock()
defer c.lock.RUnlock()
if c.bulkAvailable {
CCPs := make([]kes.CCP, 0, len(ciphertexts))
for i := range ciphertexts {
+3
View File
@@ -75,6 +75,9 @@ func unwrapErrs(err error) (leafErr error) {
break
}
}
if uerr == nil {
leafErr = err
}
return leafErr
}
+1 -1
View File
@@ -65,5 +65,5 @@ func ansiRestoreAttributes() {
// logIgnoreError if true,the error will ignore.
func logIgnoreError(err error) bool {
return err == nil || errors.Is(err, context.Canceled) || errors.Is(err, http.ErrServerClosed) || err.Error() == "disk not found"
return err == nil || errors.Is(err, context.Canceled) || errors.Is(err, http.ErrServerClosed) || err.Error() == "drive not found"
}
+1 -1
View File
@@ -263,7 +263,7 @@ func (c *Client) Call(ctx context.Context, method string, values url.Values, bod
// fully it should make sure to respond with '412'
// instead, see cmd/storage-rest-server.go for ideas.
if c.HealthCheckFn != nil && resp.StatusCode == http.StatusPreconditionFailed {
err = fmt.Errorf("Marking %s offline temporarily; caused by PreconditionFailed with disk ID mismatch", c.url.Host)
err = fmt.Errorf("Marking %s offline temporarily; caused by PreconditionFailed with drive ID mismatch", c.url.Host)
logger.LogOnceIf(ctx, err, c.url.Host)
c.MarkOffline(err)
}