mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 10:43:15 +03:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 496027b589 | |||
| 8bd4f6568b | |||
| 9d7660b409 | |||
| da55499db0 | |||
| 22f8e39b58 | |||
| eba23bbac4 | |||
| 4550535cbb | |||
| 8432fd5ac2 | |||
| 7c948adf88 | |||
| 56b7045c20 | |||
| b1a109a611 | |||
| 7a311a3b66 | |||
| d55b6b9909 | |||
| f4389fb322 | |||
| 331208bec1 | |||
| 7680e5f81d | |||
| 6acf038a84 | |||
| bdf4e386cf | |||
| ad8a34858f | |||
| 162eced7d2 | |||
| bec1f7c26a | |||
| 8771617199 | |||
| 54bc995f0a | |||
| 6c89a81af4 | |||
| 8fa2898ff1 | |||
| 10ca0a6936 | |||
| b3314e97a6 | |||
| 3b9a948045 | |||
| 3781a0f9ad | |||
| e79b289325 | |||
| 6d4c1156d6 | |||
| 3f72c7fcc7 | |||
| d521c84d55 | |||
| 946b070744 | |||
| 4a21dce2b5 | |||
| 5fe7f9fa93 | |||
| 65f34cd823 | |||
| 196e7e072b | |||
| 6f97663174 | |||
| aed7a1818a | |||
| b50d90183e | |||
| 6b06da76cb | |||
| 6ca6788bb7 | |||
| 2e23e61a45 | |||
| 9cdf490bc5 | |||
| cfed671ea3 | |||
| 53ce92b9ca | |||
| 7350a29fec | |||
| 5cc2c62c66 | |||
| 4bc5ed6c76 | |||
| e99a597899 |
@@ -36,6 +36,12 @@ jobs:
|
||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||
make test-decom
|
||||
|
||||
- name: Test Config File
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||
make test-configfile
|
||||
|
||||
- name: Test Replication
|
||||
run: |
|
||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||
|
||||
@@ -29,5 +29,8 @@ linters:
|
||||
issues:
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- "empty-block:"
|
||||
- "unused-parameter:"
|
||||
- "dot-imports:"
|
||||
- should have a package comment
|
||||
- error strings should not be capitalized or end with punctuation or a newline
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ ENV CGO_ENABLED 0
|
||||
# Install curl and minisign
|
||||
RUN apk add -U --no-cache ca-certificates && \
|
||||
apk add -U --no-cache curl && \
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||
|
||||
# Download minio binary and signature file
|
||||
RUN curl -s -q https://dl.min.io/server/minio/hotfixes/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ ENV CGO_ENABLED 0
|
||||
# Install curl and minisign
|
||||
RUN apk add -U --no-cache ca-certificates && \
|
||||
apk add -U --no-cache curl && \
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||
|
||||
# Download minio binary and signature file
|
||||
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||
|
||||
@@ -9,7 +9,7 @@ ENV CGO_ENABLED 0
|
||||
# Install curl and minisign
|
||||
RUN apk add -U --no-cache ca-certificates && \
|
||||
apk add -U --no-cache curl && \
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||
|
||||
# Download minio binary and signature file
|
||||
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE}.fips -o /go/bin/minio && \
|
||||
|
||||
@@ -9,7 +9,7 @@ ENV CGO_ENABLED 0
|
||||
# Install curl and minisign
|
||||
RUN apk add -U --no-cache ca-certificates && \
|
||||
apk add -U --no-cache curl && \
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.0
|
||||
go install aead.dev/minisign/cmd/minisign@v0.2.1
|
||||
|
||||
# Download minio binary and signature file
|
||||
RUN curl -s -q https://dl.min.io/server/minio/release/linux-${TARGETARCH}/archive/minio.${RELEASE} -o /go/bin/minio && \
|
||||
|
||||
@@ -8,7 +8,6 @@ GOOS := $(shell go env GOOS)
|
||||
VERSION ?= $(shell git describe --tags)
|
||||
TAG ?= "quay.io/minio/minio:$(VERSION)"
|
||||
|
||||
GOLANGCI_VERSION = v1.51.2
|
||||
GOLANGCI_DIR = .bin/golangci/$(GOLANGCI_VERSION)
|
||||
GOLANGCI = $(GOLANGCI_DIR)/golangci-lint
|
||||
|
||||
@@ -23,7 +22,7 @@ help: ## print this help
|
||||
|
||||
getdeps: ## fetch necessary dependencies
|
||||
@mkdir -p ${GOPATH}/bin
|
||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR) $(GOLANGCI_VERSION)
|
||||
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
|
||||
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@6ac204f0b4d48d17ab4fa442134c7fba13127a4e
|
||||
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
|
||||
|
||||
@@ -60,6 +59,9 @@ test-decom: install-race
|
||||
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
|
||||
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
|
||||
|
||||
test-configfile: install-race
|
||||
@env bash $(PWD)/docs/distributed/distributed-from-config-file.sh
|
||||
|
||||
test-upgrade: install-race
|
||||
@echo "Running minio upgrade tests"
|
||||
@(env bash $(PWD)/buildscripts/minio-upgrade.sh)
|
||||
|
||||
@@ -9,7 +9,7 @@ function _init() {
|
||||
export CGO_ENABLED=0
|
||||
|
||||
## List of architectures and OS to test coss compilation.
|
||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips"
|
||||
SUPPORTED_OSARCH="linux/ppc64le linux/mips64 linux/amd64 linux/arm64 linux/s390x darwin/arm64 darwin/amd64 freebsd/amd64 windows/amd64 linux/arm linux/386 netbsd/amd64 linux/mips openbsd/amd64"
|
||||
}
|
||||
|
||||
function _build() {
|
||||
|
||||
@@ -94,7 +94,7 @@ func (a adminAPIHandlers) PutBucketQuotaConfigHandler(w http.ResponseWriter, r *
|
||||
Quota: data,
|
||||
UpdatedAt: updatedAt,
|
||||
}
|
||||
if quotaConfig.Size == 0 || quotaConfig.Quota == 0 {
|
||||
if quotaConfig.Size == 0 && quotaConfig.Quota == 0 {
|
||||
bucketMeta.Quota = nil
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,14 @@ package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/auth"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/mux"
|
||||
"github.com/minio/pkg/v2/policy"
|
||||
@@ -175,3 +179,267 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
||||
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
}
|
||||
|
||||
// AddServiceAccountLDAP adds a new service account for provided LDAP username or DN
|
||||
//
|
||||
// PUT /minio/admin/v3/idp/ldap/add-service-account
|
||||
func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cred, opts, createReq, targetUser, APIError := commonAddServiceAccount(r)
|
||||
if APIError.Code != "" {
|
||||
writeErrorResponseJSON(ctx, w, APIError, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// fail if ldap is not enabled
|
||||
if !globalIAMSys.LDAPConfig.Enabled() {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errors.New("LDAP not enabled")), r.URL)
|
||||
}
|
||||
|
||||
// Find the user for the request sender (as it may be sent via a service
|
||||
// account or STS account):
|
||||
requestorUser := cred.AccessKey
|
||||
requestorParentUser := cred.AccessKey
|
||||
requestorGroups := cred.Groups
|
||||
requestorIsDerivedCredential := false
|
||||
if cred.IsServiceAccount() || cred.IsTemp() {
|
||||
requestorParentUser = cred.ParentUser
|
||||
requestorIsDerivedCredential = true
|
||||
}
|
||||
|
||||
// Check if we are creating svc account for request sender.
|
||||
isSvcAccForRequestor := false
|
||||
if targetUser == requestorUser || targetUser == requestorParentUser {
|
||||
isSvcAccForRequestor = true
|
||||
}
|
||||
|
||||
var (
|
||||
targetGroups []string
|
||||
err error
|
||||
)
|
||||
|
||||
// If we are creating svc account for request sender, ensure
|
||||
// that targetUser is a real user (i.e. not derived
|
||||
// credentials).
|
||||
if isSvcAccForRequestor {
|
||||
if requestorIsDerivedCredential {
|
||||
if requestorParentUser == "" {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||
errors.New("service accounts cannot be generated for temporary credentials without parent")), r.URL)
|
||||
return
|
||||
}
|
||||
targetUser = requestorParentUser
|
||||
}
|
||||
targetGroups = requestorGroups
|
||||
|
||||
// Deny if the target user is not LDAP
|
||||
isLDAP, err := globalIAMSys.LDAPConfig.DoesUsernameExist(targetUser)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if isLDAP == "" {
|
||||
err := errors.New("Specified user does not exist on LDAP server")
|
||||
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
|
||||
writeErrorResponseJSON(ctx, w, APIErr, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// In case of LDAP/OIDC we need to set `opts.claims` to ensure
|
||||
// it is associated with the LDAP/OIDC user properly.
|
||||
for k, v := range cred.Claims {
|
||||
if k == expClaim {
|
||||
continue
|
||||
}
|
||||
opts.claims[k] = v
|
||||
}
|
||||
} else {
|
||||
isDN := globalIAMSys.LDAPConfig.IsLDAPUserDN(targetUser)
|
||||
|
||||
opts.claims[ldapUserN] = targetUser // simple username
|
||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||
if err != nil {
|
||||
// if not found, check if DN
|
||||
if strings.Contains(err.Error(), "not found") && isDN {
|
||||
// warn user that DNs are not allowed
|
||||
err = fmt.Errorf("Must use short username to add service account. %w", err)
|
||||
}
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
opts.claims[ldapUser] = targetUser // DN
|
||||
}
|
||||
|
||||
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
createResp := madmin.AddServiceAccountResp{
|
||||
Credentials: madmin.Credentials{
|
||||
AccessKey: newCred.AccessKey,
|
||||
SecretKey: newCred.SecretKey,
|
||||
Expiration: newCred.Expiration,
|
||||
},
|
||||
}
|
||||
|
||||
data, err := json.Marshal(createResp)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
|
||||
// Call hook for cluster-replication if the service account is not for a
|
||||
// root user.
|
||||
if newCred.ParentUser != globalActiveCred.AccessKey {
|
||||
logger.LogIf(ctx, globalSiteReplicationSys.IAMChangeHook(ctx, madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSvcAcc,
|
||||
SvcAccChange: &madmin.SRSvcAccChange{
|
||||
Create: &madmin.SRSvcAccCreate{
|
||||
Parent: newCred.ParentUser,
|
||||
AccessKey: newCred.AccessKey,
|
||||
SecretKey: newCred.SecretKey,
|
||||
Groups: newCred.Groups,
|
||||
Name: newCred.Name,
|
||||
Description: newCred.Description,
|
||||
Claims: opts.claims,
|
||||
SessionPolicy: createReq.Policy,
|
||||
Status: auth.AccountOn,
|
||||
Expiration: createReq.Expiration,
|
||||
},
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// ListAccessKeysLDAP - GET /minio/admin/v3/idp/ldap/list-access-keys
|
||||
func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil || globalNotificationSys == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
userDN := r.Form.Get("userDN")
|
||||
|
||||
// If listing is requested for a specific user (who is not the request
|
||||
// sender), check that the user has permissions.
|
||||
if userDN != "" && userDN != cred.ParentUser {
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.ListServiceAccountsAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.ListServiceAccountsAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
DenyOnly: true,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
userDN = cred.AccessKey
|
||||
if cred.ParentUser != "" {
|
||||
userDN = cred.ParentUser
|
||||
}
|
||||
}
|
||||
|
||||
targetAccount, err := globalIAMSys.LDAPConfig.DoesUsernameExist(userDN)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
} else if userDN == "" {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
listType := r.Form.Get("listType")
|
||||
if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
|
||||
// default to both
|
||||
listType = ""
|
||||
}
|
||||
|
||||
var serviceAccounts []auth.Credentials
|
||||
var stsKeys []auth.Credentials
|
||||
|
||||
if listType == "" || listType == "sts-only" {
|
||||
stsKeys, err = globalIAMSys.ListSTSAccounts(ctx, targetAccount)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
if listType == "" || listType == "svcacc-only" {
|
||||
serviceAccounts, err = globalIAMSys.ListServiceAccounts(ctx, targetAccount)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var serviceAccountList []madmin.ServiceAccountInfo
|
||||
var stsKeyList []madmin.ServiceAccountInfo
|
||||
|
||||
for _, svc := range serviceAccounts {
|
||||
expiryTime := svc.Expiration
|
||||
serviceAccountList = append(serviceAccountList, madmin.ServiceAccountInfo{
|
||||
AccessKey: svc.AccessKey,
|
||||
Expiration: &expiryTime,
|
||||
})
|
||||
}
|
||||
for _, sts := range stsKeys {
|
||||
expiryTime := sts.Expiration
|
||||
stsKeyList = append(stsKeyList, madmin.ServiceAccountInfo{
|
||||
AccessKey: sts.AccessKey,
|
||||
Expiration: &expiryTime,
|
||||
})
|
||||
}
|
||||
|
||||
listResp := madmin.ListAccessKeysLDAPResp{
|
||||
ServiceAccounts: serviceAccountList,
|
||||
STSKeys: stsKeyList,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(listResp)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, encryptedData)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -66,16 +67,28 @@ func (a adminAPIHandlers) StartDecommission(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
vars := mux.Vars(r)
|
||||
v := vars["pool"]
|
||||
byID := vars["by-id"] == "true"
|
||||
|
||||
pools := strings.Split(v, ",")
|
||||
poolIndices := make([]int, 0, len(pools))
|
||||
|
||||
for _, pool := range pools {
|
||||
idx := globalEndpoints.GetPoolIdx(pool)
|
||||
if idx == -1 {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
var idx int
|
||||
if byID {
|
||||
var err error
|
||||
idx, err = strconv.Atoi(pool)
|
||||
if err != nil {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
idx = globalEndpoints.GetPoolIdx(pool)
|
||||
if idx == -1 {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
var pool *erasureSets
|
||||
for pidx := range z.serverPools {
|
||||
@@ -132,8 +145,17 @@ func (a adminAPIHandlers) CancelDecommission(w http.ResponseWriter, r *http.Requ
|
||||
|
||||
vars := mux.Vars(r)
|
||||
v := vars["pool"]
|
||||
byID := vars["by-id"] == "true"
|
||||
idx := -1
|
||||
|
||||
if byID {
|
||||
if i, err := strconv.Atoi(v); err == nil && i >= 0 && i < len(globalEndpoints) {
|
||||
idx = i
|
||||
}
|
||||
} else {
|
||||
idx = globalEndpoints.GetPoolIdx(v)
|
||||
}
|
||||
|
||||
idx := globalEndpoints.GetPoolIdx(v)
|
||||
if idx == -1 {
|
||||
// We didn't find any matching pools, invalid input
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errInvalidArgument), r.URL)
|
||||
@@ -178,8 +200,17 @@ func (a adminAPIHandlers) StatusPool(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
vars := mux.Vars(r)
|
||||
v := vars["pool"]
|
||||
byID := vars["by-id"] == "true"
|
||||
idx := -1
|
||||
|
||||
if byID {
|
||||
if i, err := strconv.Atoi(v); err == nil && i >= 0 && i < len(globalEndpoints) {
|
||||
idx = i
|
||||
}
|
||||
} else {
|
||||
idx = globalEndpoints.GetPoolIdx(v)
|
||||
}
|
||||
|
||||
idx := globalEndpoints.GetPoolIdx(v)
|
||||
if idx == -1 {
|
||||
apiErr := toAdminAPIErr(ctx, errInvalidArgument)
|
||||
apiErr.Description = fmt.Sprintf("specified pool '%s' not found, please specify a valid pool", v)
|
||||
|
||||
+94
-113
@@ -614,72 +614,17 @@ func (a adminAPIHandlers) TemporaryAccountInfo(w http.ResponseWriter, r *http.Re
|
||||
|
||||
// AddServiceAccount - PUT /minio/admin/v3/add-service-account
|
||||
func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil || globalNotificationSys == nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
password := cred.SecretKey
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var createReq madmin.AddServiceAccountReq
|
||||
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
// service account access key cannot have space characters beginning and end of the string.
|
||||
if hasSpaceBE(createReq.AccessKey) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if err := createReq.Validate(); err != nil {
|
||||
// Since this validation would happen client side as well, we only send
|
||||
// a generic error message here.
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
|
||||
ctx, cred, opts, createReq, targetUser, APIError := commonAddServiceAccount(r)
|
||||
if APIError.Code != "" {
|
||||
writeErrorResponseJSON(ctx, w, APIError, r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
targetUser string
|
||||
targetGroups []string
|
||||
err error
|
||||
)
|
||||
|
||||
// If the request did not set a TargetUser, the service account is
|
||||
// created for the request sender.
|
||||
targetUser = createReq.TargetUser
|
||||
if targetUser == "" {
|
||||
targetUser = cred.AccessKey
|
||||
}
|
||||
|
||||
description := createReq.Description
|
||||
if description == "" {
|
||||
description = createReq.Comment
|
||||
}
|
||||
opts := newServiceAccountOpts{
|
||||
accessKey: createReq.AccessKey,
|
||||
secretKey: createReq.SecretKey,
|
||||
name: createReq.Name,
|
||||
description: description,
|
||||
expiration: createReq.Expiration,
|
||||
claims: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Find the user for the request sender (as it may be sent via a service
|
||||
// account or STS account):
|
||||
requestorUser := cred.AccessKey
|
||||
@@ -713,23 +658,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
// that targetUser is a real user (i.e. not derived
|
||||
// credentials).
|
||||
if isSvcAccForRequestor {
|
||||
// Check if adding service account is explicitly denied.
|
||||
//
|
||||
// This allows turning off service accounts for request sender,
|
||||
// if there is no deny statement this call is implicitly enabled.
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: requestorUser,
|
||||
Groups: requestorGroups,
|
||||
Action: policy.CreateServiceAccountAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
DenyOnly: true,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
if requestorIsDerivedCredential {
|
||||
if requestorParentUser == "" {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx,
|
||||
@@ -748,32 +676,16 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
}
|
||||
opts.claims[k] = v
|
||||
}
|
||||
} else {
|
||||
// Need permission if we are creating a service account for a
|
||||
// user <> to the request sender
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: requestorUser,
|
||||
Groups: requestorGroups,
|
||||
Action: policy.CreateServiceAccountAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
} else if globalIAMSys.LDAPConfig.Enabled() {
|
||||
// In case of LDAP we need to resolve the targetUser to a DN and
|
||||
// query their groups:
|
||||
if globalIAMSys.LDAPConfig.Enabled() {
|
||||
opts.claims[ldapUserN] = targetUser // simple username
|
||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
opts.claims[ldapUser] = targetUser // username DN
|
||||
opts.claims[ldapUserN] = targetUser // simple username
|
||||
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
opts.claims[ldapUser] = targetUser // username DN
|
||||
|
||||
// NOTE: if not using LDAP, then internal IDP or open ID is
|
||||
// being used - in the former, group info is enforced when
|
||||
@@ -781,19 +693,6 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
// latter, a group notion is not supported.
|
||||
}
|
||||
|
||||
var sp *policy.Policy
|
||||
if len(createReq.Policy) > 0 {
|
||||
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
if sp.Version == "" && len(sp.Statements) == 0 {
|
||||
sp = nil
|
||||
}
|
||||
}
|
||||
|
||||
opts.sessionPolicy = sp
|
||||
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
@@ -814,7 +713,7 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
|
||||
encryptedData, err := madmin.EncryptData(password, data)
|
||||
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -2514,3 +2413,85 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
|
||||
ctx := r.Context()
|
||||
|
||||
// Get current object layer instance.
|
||||
objectAPI := newObjectLayerFn()
|
||||
if objectAPI == nil || globalNotificationSys == nil {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrServerNotInitialized)
|
||||
}
|
||||
|
||||
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
|
||||
if s3Err != ErrNone {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(s3Err)
|
||||
}
|
||||
|
||||
password := cred.SecretKey
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||
}
|
||||
|
||||
var createReq madmin.AddServiceAccountReq
|
||||
if err = json.Unmarshal(reqBytes, &createReq); err != nil {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErrWithErr(ErrAdminConfigBadJSON, err)
|
||||
}
|
||||
|
||||
// service account access key cannot have space characters beginning and end of the string.
|
||||
if hasSpaceBE(createReq.AccessKey) {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||
}
|
||||
|
||||
if err := createReq.Validate(); err != nil {
|
||||
// Since this validation would happen client side as well, we only send
|
||||
// a generic error message here.
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument)
|
||||
}
|
||||
// If the request did not set a TargetUser, the service account is
|
||||
// created for the request sender.
|
||||
targetUser := createReq.TargetUser
|
||||
if targetUser == "" {
|
||||
targetUser = cred.AccessKey
|
||||
}
|
||||
|
||||
description := createReq.Description
|
||||
if description == "" {
|
||||
description = createReq.Comment
|
||||
}
|
||||
opts := newServiceAccountOpts{
|
||||
accessKey: createReq.AccessKey,
|
||||
secretKey: createReq.SecretKey,
|
||||
name: createReq.Name,
|
||||
description: description,
|
||||
expiration: createReq.Expiration,
|
||||
claims: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Check if action is allowed if creating access key for another user
|
||||
// Check if action is explicitly denied if for self
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
Action: policy.CreateServiceAccountAdminAction,
|
||||
ConditionValues: getConditionValues(r, "", cred),
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
DenyOnly: (targetUser == cred.AccessKey || targetUser == cred.ParentUser),
|
||||
}) {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", errorCodes.ToAPIErr(ErrAccessDenied)
|
||||
}
|
||||
|
||||
var sp *policy.Policy
|
||||
if len(createReq.Policy) > 0 {
|
||||
sp, err = policy.ParseConfig(bytes.NewReader(createReq.Policy))
|
||||
if err != nil {
|
||||
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
opts.sessionPolicy = sp
|
||||
|
||||
return ctx, cred, opts, createReq, targetUser, APIError{}
|
||||
}
|
||||
|
||||
+14
-7
@@ -340,7 +340,7 @@ func (a adminAPIHandlers) StorageInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
return
|
||||
}
|
||||
|
||||
storageInfo := objectAPI.StorageInfo(ctx)
|
||||
storageInfo := objectAPI.StorageInfo(ctx, true)
|
||||
|
||||
// Collect any disk healing.
|
||||
healing, _ := getAggregatedBackgroundHealState(ctx, nil)
|
||||
@@ -1313,7 +1313,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
||||
duration = time.Second * 10
|
||||
}
|
||||
|
||||
storageInfo := objectAPI.StorageInfo(ctx)
|
||||
storageInfo := objectAPI.StorageInfo(ctx, true)
|
||||
|
||||
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
|
||||
if !sufficientCapacity {
|
||||
@@ -1911,7 +1911,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
log, audit := fetchLoggerInfo()
|
||||
log, audit := fetchLoggerInfo(ctx)
|
||||
|
||||
// Get the notification target info
|
||||
notifyTarget := fetchLambdaInfo()
|
||||
@@ -2660,19 +2660,26 @@ func fetchKMSStatusV2(ctx context.Context) []madmin.KMS {
|
||||
return stats
|
||||
}
|
||||
|
||||
func targetStatus(ctx context.Context, h logger.Target) madmin.Status {
|
||||
if h.IsOnline(ctx) {
|
||||
return madmin.Status{Status: string(madmin.ItemOnline)}
|
||||
}
|
||||
return madmin.Status{Status: string(madmin.ItemOffline)}
|
||||
}
|
||||
|
||||
// fetchLoggerDetails return log info
|
||||
func fetchLoggerInfo() ([]madmin.Logger, []madmin.Audit) {
|
||||
func fetchLoggerInfo(ctx context.Context) ([]madmin.Logger, []madmin.Audit) {
|
||||
var loggerInfo []madmin.Logger
|
||||
var auditloggerInfo []madmin.Audit
|
||||
for _, tgt := range logger.SystemTargets() {
|
||||
if tgt.Endpoint() != "" {
|
||||
loggerInfo = append(loggerInfo, madmin.Logger{tgt.String(): logger.TargetStatus(GlobalContext, tgt)})
|
||||
loggerInfo = append(loggerInfo, madmin.Logger{tgt.String(): targetStatus(ctx, tgt)})
|
||||
}
|
||||
}
|
||||
|
||||
for _, tgt := range logger.AuditTargets() {
|
||||
if tgt.Endpoint() != "" {
|
||||
auditloggerInfo = append(auditloggerInfo, madmin.Audit{tgt.String(): logger.TargetStatus(GlobalContext, tgt)})
|
||||
auditloggerInfo = append(auditloggerInfo, madmin.Audit{tgt.String(): targetStatus(ctx, tgt)})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2726,7 +2733,7 @@ func getClusterMetaInfo(ctx context.Context) []byte {
|
||||
ci.Info.NoOfServers = len(globalEndpoints.Hostnames())
|
||||
ci.Info.MinioVersion = Version
|
||||
|
||||
si := objectAPI.StorageInfo(ctx)
|
||||
si := objectAPI.StorageInfo(ctx, true)
|
||||
|
||||
ci.Info.NoOfDrives = len(si.Disks)
|
||||
for _, disk := range si.Disks {
|
||||
|
||||
@@ -810,7 +810,7 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
|
||||
// NOTE: Healing on meta is run regardless
|
||||
// of any bucket being selected, this is to ensure that
|
||||
// meta are always upto date and correct.
|
||||
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string) error {
|
||||
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
|
||||
if h.isQuitting() {
|
||||
return errHealStopSignalled
|
||||
}
|
||||
@@ -867,7 +867,7 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
|
||||
|
||||
if !h.settings.Recursive {
|
||||
if h.object != "" {
|
||||
if err := h.healObject(bucket, h.object, ""); err != nil {
|
||||
if err := h.healObject(bucket, h.object, "", h.settings.ScanMode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -882,7 +882,7 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
|
||||
}
|
||||
|
||||
// healObject - heal the given object and record result
|
||||
func (h *healSequence) healObject(bucket, object, versionID string) error {
|
||||
func (h *healSequence) healObject(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
|
||||
if h.isQuitting() {
|
||||
return errHealStopSignalled
|
||||
}
|
||||
|
||||
@@ -300,6 +300,12 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.GetIdentityProviderCfg))
|
||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/idp-config/{type}/{name}").HandlerFunc(adminMiddleware(adminAPI.DeleteIdentityProviderCfg))
|
||||
|
||||
// LDAP specific service accounts ops
|
||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp/ldap/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccountLDAP))
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys").
|
||||
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)).
|
||||
Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}")
|
||||
|
||||
// LDAP IAM operations
|
||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
|
||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/idp/ldap/policy/{operation}").HandlerFunc(adminMiddleware(adminAPI.AttachDetachPolicyLDAP))
|
||||
|
||||
@@ -141,7 +141,7 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
||||
|
||||
objLayer := newObjectLayerFn()
|
||||
if objLayer != nil {
|
||||
storageInfo := objLayer.LocalStorageInfo(GlobalContext)
|
||||
storageInfo := objLayer.LocalStorageInfo(GlobalContext, true)
|
||||
props.State = string(madmin.ItemOnline)
|
||||
props.Disks = storageInfo.Disks
|
||||
} else {
|
||||
|
||||
@@ -345,18 +345,18 @@ func initAutoHeal(ctx context.Context, objAPI ObjectLayer) {
|
||||
|
||||
initBackgroundHealing(ctx, objAPI) // start quick background healing
|
||||
|
||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||
|
||||
if env.Get("_MINIO_AUTO_DRIVE_HEALING", config.EnableOn) == config.EnableOn || env.Get("_MINIO_AUTO_DISK_HEALING", config.EnableOn) == config.EnableOn {
|
||||
globalBackgroundHealState.pushHealLocalDisks(getLocalDisksToHeal()...)
|
||||
|
||||
go monitorLocalDisksAndHeal(ctx, z)
|
||||
}
|
||||
}
|
||||
|
||||
func getLocalDisksToHeal() (disksToHeal Endpoints) {
|
||||
globalLocalDrivesMu.RLock()
|
||||
globalLocalDrives := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
for _, disk := range globalLocalDrives {
|
||||
for _, disk := range localDrives {
|
||||
_, err := disk.GetDiskID()
|
||||
if errors.Is(err, errUnformattedDisk) {
|
||||
disksToHeal = append(disksToHeal, disk.Endpoint())
|
||||
|
||||
@@ -1007,12 +1007,9 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
// None of the provided metadata filters match skip the object.
|
||||
return false
|
||||
}
|
||||
// if one of source or target is non MinIO, just replicate the top most version like `mc mirror`
|
||||
if (r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3) && !info.IsLatest {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
// if one of source or target is non MinIO, just replicate the top most version like `mc mirror`
|
||||
return !((r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3) && !info.IsLatest)
|
||||
}
|
||||
|
||||
u, err := url.Parse(r.Target.Endpoint)
|
||||
@@ -1123,8 +1120,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
// one of source/target is s3, skip delete marker and all versions under the same object name.
|
||||
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
|
||||
|
||||
results := make(chan ObjectInfo, 100)
|
||||
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, results, WalkOptions{
|
||||
if err := api.Walk(ctx, r.Source.Bucket, r.Source.Prefix, walkCh, WalkOptions{
|
||||
Marker: lastObject,
|
||||
Filter: selectObj,
|
||||
AskDisks: walkQuorum,
|
||||
|
||||
@@ -1657,9 +1657,11 @@ func (api objectAPIHandlers) DeleteBucketHandler(w http.ResponseWriter, r *http.
|
||||
}
|
||||
|
||||
// Return an error if the bucket does not exist
|
||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil && !forceDelete {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
if !forceDelete {
|
||||
if _, err := objectAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to delete bucket.
|
||||
|
||||
@@ -507,9 +507,13 @@ func auditTierActions(ctx context.Context, tier string, bytes int64) func(err er
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
op.TimeToResponseNS = time.Since(startTime).Nanoseconds()
|
||||
since := time.Since(startTime)
|
||||
op.TimeToResponseNS = since.Nanoseconds()
|
||||
globalTierMetrics.Observe(tier, since)
|
||||
globalTierMetrics.logSuccess(tier)
|
||||
} else {
|
||||
op.Error = err.Error()
|
||||
globalTierMetrics.logFailure(tier)
|
||||
}
|
||||
|
||||
logger.GetReqInfo(ctx).AppendTags("tierStats", op)
|
||||
|
||||
@@ -121,7 +121,7 @@ func getConditionValues(r *http.Request, lc string, cred auth.Credentials) map[s
|
||||
"CurrentTime": {currTime.Format(time.RFC3339)},
|
||||
"EpochTime": {strconv.FormatInt(currTime.Unix(), 10)},
|
||||
"SecureTransport": {strconv.FormatBool(r.TLS != nil)},
|
||||
"SourceIp": {handlers.GetSourceIP(r)},
|
||||
"SourceIp": {handlers.GetSourceIPRaw(r)},
|
||||
"UserAgent": {r.UserAgent()},
|
||||
"Referer": {r.Referer()},
|
||||
"principaltype": {principalType},
|
||||
|
||||
@@ -337,13 +337,13 @@ type SMA struct {
|
||||
filledBuf bool
|
||||
}
|
||||
|
||||
func newSMA(len int) *SMA {
|
||||
if len <= 0 {
|
||||
len = defaultWindowSize
|
||||
func newSMA(ln int) *SMA {
|
||||
if ln <= 0 {
|
||||
ln = defaultWindowSize
|
||||
}
|
||||
return &SMA{
|
||||
buf: make([]float64, len),
|
||||
window: len,
|
||||
buf: make([]float64, ln),
|
||||
window: ln,
|
||||
idx: 0,
|
||||
}
|
||||
}
|
||||
|
||||
+77
-27
@@ -373,15 +373,14 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
||||
if oi.DeleteMarker && (validReplStatus || replicate) {
|
||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||
continue
|
||||
} else {
|
||||
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
||||
// is issued - this still needs to be replicated back to the other target
|
||||
if !oi.VersionPurgeStatus.Empty() {
|
||||
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
|
||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||
}
|
||||
continue
|
||||
}
|
||||
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
||||
// is issued - this still needs to be replicated back to the other target
|
||||
if !oi.VersionPurgeStatus.Empty() {
|
||||
replicate = oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed
|
||||
dsc.Set(newReplicateTargetDecision(tgtArn, replicate, sync))
|
||||
}
|
||||
continue
|
||||
}
|
||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(bucket, tgtArn)
|
||||
// the target online status should not be used here while deciding
|
||||
@@ -1469,6 +1468,24 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
|
||||
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
||||
},
|
||||
}
|
||||
if tagTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+TaggingTimestamp]; ok {
|
||||
ondiskTimestamp, err := time.Parse(time.RFC3339, tagTmStr)
|
||||
if err == nil {
|
||||
dstOpts.Internal.TaggingTimestamp = ondiskTimestamp
|
||||
}
|
||||
}
|
||||
if retTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp]; ok {
|
||||
ondiskTimestamp, err := time.Parse(time.RFC3339, retTmStr)
|
||||
if err == nil {
|
||||
dstOpts.Internal.RetentionTimestamp = ondiskTimestamp
|
||||
}
|
||||
}
|
||||
if lholdTmStr, ok := objInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockLegalHoldTimestamp]; ok {
|
||||
ondiskTimestamp, err := time.Parse(time.RFC3339, lholdTmStr)
|
||||
if err == nil {
|
||||
dstOpts.Internal.LegalholdTimestamp = ondiskTimestamp
|
||||
}
|
||||
}
|
||||
if _, rinfo.Err = c.CopyObject(ctx, tgt.Bucket, object, tgt.Bucket, object, getCopyObjMetadata(objInfo, tgt.StorageClass), srcOpts, dstOpts); rinfo.Err != nil {
|
||||
rinfo.ReplicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to replicate metadata for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, rinfo.Err))
|
||||
@@ -1698,12 +1715,13 @@ type ReplicationPool struct {
|
||||
activeWorkers int32
|
||||
activeMRFWorkers int32
|
||||
|
||||
objLayer ObjectLayer
|
||||
ctx context.Context
|
||||
priority string
|
||||
mu sync.RWMutex
|
||||
mrfMU sync.Mutex
|
||||
resyncer *replicationResyncer
|
||||
objLayer ObjectLayer
|
||||
ctx context.Context
|
||||
priority string
|
||||
maxWorkers int
|
||||
mu sync.RWMutex
|
||||
mrfMU sync.Mutex
|
||||
resyncer *replicationResyncer
|
||||
|
||||
// workers:
|
||||
workers []chan ReplicationWorkerOperation
|
||||
@@ -1749,9 +1767,13 @@ const (
|
||||
func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPoolOpts) *ReplicationPool {
|
||||
var workers, failedWorkers int
|
||||
priority := "auto"
|
||||
maxWorkers := WorkerMaxLimit
|
||||
if opts.Priority != "" {
|
||||
priority = opts.Priority
|
||||
}
|
||||
if opts.MaxWorkers > 0 {
|
||||
maxWorkers = opts.MaxWorkers
|
||||
}
|
||||
switch priority {
|
||||
case "fast":
|
||||
workers = WorkerMaxLimit
|
||||
@@ -1763,7 +1785,13 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
||||
workers = WorkerAutoDefault
|
||||
failedWorkers = MRFWorkerAutoDefault
|
||||
}
|
||||
if maxWorkers > 0 && workers > maxWorkers {
|
||||
workers = maxWorkers
|
||||
}
|
||||
|
||||
if maxWorkers > 0 && failedWorkers > maxWorkers {
|
||||
failedWorkers = maxWorkers
|
||||
}
|
||||
pool := &ReplicationPool{
|
||||
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
||||
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
||||
@@ -1775,6 +1803,7 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
||||
ctx: ctx,
|
||||
objLayer: o,
|
||||
priority: priority,
|
||||
maxWorkers: maxWorkers,
|
||||
}
|
||||
|
||||
pool.AddLargeWorkers()
|
||||
@@ -1930,7 +1959,7 @@ func (p *ReplicationPool) ResizeWorkers(n, checkOld int) {
|
||||
}
|
||||
|
||||
// ResizeWorkerPriority sets replication failed workers pool size
|
||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string) {
|
||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers int) {
|
||||
var workers, mrfWorkers int
|
||||
p.mu.Lock()
|
||||
switch pri {
|
||||
@@ -1950,7 +1979,15 @@ func (p *ReplicationPool) ResizeWorkerPriority(pri string) {
|
||||
mrfWorkers = int(math.Min(float64(p.mrfWorkerSize+1), MRFWorkerAutoDefault))
|
||||
}
|
||||
}
|
||||
if maxWorkers > 0 && workers > maxWorkers {
|
||||
workers = maxWorkers
|
||||
}
|
||||
|
||||
if maxWorkers > 0 && mrfWorkers > maxWorkers {
|
||||
mrfWorkers = maxWorkers
|
||||
}
|
||||
p.priority = pri
|
||||
p.maxWorkers = maxWorkers
|
||||
p.mu.Unlock()
|
||||
p.ResizeWorkers(workers, 0)
|
||||
p.ResizeFailedWorkers(mrfWorkers)
|
||||
@@ -2024,6 +2061,7 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
||||
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
||||
p.mu.RLock()
|
||||
prio := p.priority
|
||||
maxWorkers := p.maxWorkers
|
||||
p.mu.RUnlock()
|
||||
switch prio {
|
||||
case "fast":
|
||||
@@ -2031,16 +2069,18 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
||||
case "slow":
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming traffic - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
||||
default:
|
||||
if p.ActiveWorkers() < WorkerMaxLimit {
|
||||
maxWorkers = int(math.Min(float64(maxWorkers), WorkerMaxLimit))
|
||||
if p.ActiveWorkers() < maxWorkers {
|
||||
p.mu.RLock()
|
||||
workers := int(math.Min(float64(len(p.workers)+1), WorkerMaxLimit))
|
||||
workers := int(math.Min(float64(len(p.workers)+1), float64(maxWorkers)))
|
||||
existing := len(p.workers)
|
||||
p.mu.RUnlock()
|
||||
p.ResizeWorkers(workers, existing)
|
||||
}
|
||||
if p.ActiveMRFWorkers() < MRFWorkerMaxLimit {
|
||||
maxMRFWorkers := int(math.Min(float64(maxWorkers), MRFWorkerMaxLimit))
|
||||
if p.ActiveMRFWorkers() < maxMRFWorkers {
|
||||
p.mu.RLock()
|
||||
workers := int(math.Min(float64(p.mrfWorkerSize+1), MRFWorkerMaxLimit))
|
||||
workers := int(math.Min(float64(p.mrfWorkerSize+1), float64(maxMRFWorkers)))
|
||||
p.mu.RUnlock()
|
||||
p.ResizeFailedWorkers(workers)
|
||||
}
|
||||
@@ -2078,6 +2118,7 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
||||
globalReplicationPool.queueMRFSave(doi.ToMRFEntry())
|
||||
p.mu.RLock()
|
||||
prio := p.priority
|
||||
maxWorkers := p.maxWorkers
|
||||
p.mu.RUnlock()
|
||||
switch prio {
|
||||
case "fast":
|
||||
@@ -2085,9 +2126,10 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
||||
case "slow":
|
||||
logger.LogOnceIf(GlobalContext, fmt.Errorf("WARNING: Unable to keep up with incoming deletes - we recommend increasing replication priority with `mc admin config set api replication_priority=auto`"), string(replicationSubsystem))
|
||||
default:
|
||||
if p.ActiveWorkers() < WorkerMaxLimit {
|
||||
maxWorkers = int(math.Min(float64(maxWorkers), WorkerMaxLimit))
|
||||
if p.ActiveWorkers() < maxWorkers {
|
||||
p.mu.RLock()
|
||||
workers := int(math.Min(float64(len(p.workers)+1), WorkerMaxLimit))
|
||||
workers := int(math.Min(float64(len(p.workers)+1), float64(maxWorkers)))
|
||||
existing := len(p.workers)
|
||||
p.mu.RUnlock()
|
||||
p.ResizeWorkers(workers, existing)
|
||||
@@ -2097,13 +2139,12 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
||||
}
|
||||
|
||||
type replicationPoolOpts struct {
|
||||
Priority string
|
||||
Priority string
|
||||
MaxWorkers int
|
||||
}
|
||||
|
||||
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||
globalReplicationPool = NewReplicationPool(ctx, objectAPI, replicationPoolOpts{
|
||||
Priority: globalAPIConfig.getReplicationPriority(),
|
||||
})
|
||||
globalReplicationPool = NewReplicationPool(ctx, objectAPI, globalAPIConfig.getReplicationOpts())
|
||||
globalReplicationStats = NewReplicationStats(ctx, objectAPI)
|
||||
go globalReplicationStats.trackEWMA()
|
||||
}
|
||||
@@ -3222,7 +3263,11 @@ func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntr
|
||||
return r
|
||||
}
|
||||
|
||||
for _, localDrive := range globalLocalDrives {
|
||||
globalLocalDrivesMu.RLock()
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
for _, localDrive := range localDrives {
|
||||
r := newReader()
|
||||
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
||||
r.Close()
|
||||
@@ -3284,7 +3329,12 @@ func (p *ReplicationPool) loadMRF() (mrfRec MRFReplicateEntries, err error) {
|
||||
|
||||
return re, nil
|
||||
}
|
||||
for _, localDrive := range globalLocalDrives {
|
||||
|
||||
globalLocalDrivesMu.RLock()
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
for _, localDrive := range localDrives {
|
||||
rc, err := localDrive.ReadFileStream(p.ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), 0, -1)
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
+106
-66
@@ -310,84 +310,121 @@ func checkUpdate(mode string) {
|
||||
logger.Info(prepareUpdateMessage("Run `mc admin update`", lrTime.Sub(crTime)))
|
||||
}
|
||||
|
||||
func newConfigDirFromCtx(ctx *cli.Context, option string, getDefaultDir func() string) (*ConfigDir, bool) {
|
||||
var dir string
|
||||
var dirSet bool
|
||||
|
||||
switch {
|
||||
case ctx.IsSet(option):
|
||||
dir = ctx.String(option)
|
||||
dirSet = true
|
||||
case ctx.GlobalIsSet(option):
|
||||
dir = ctx.GlobalString(option)
|
||||
dirSet = true
|
||||
// cli package does not expose parent's option option. Below code is workaround.
|
||||
if dir == "" || dir == getDefaultDir() {
|
||||
dirSet = false // Unset to false since GlobalIsSet() true is a false positive.
|
||||
if ctx.Parent().GlobalIsSet(option) {
|
||||
dir = ctx.Parent().GlobalString(option)
|
||||
dirSet = true
|
||||
}
|
||||
}
|
||||
default:
|
||||
// Neither local nor global option is provided. In this case, try to use
|
||||
// default directory.
|
||||
func newConfigDir(dir string, dirSet bool, getDefaultDir func() string) (*ConfigDir, error) {
|
||||
if dir == "" {
|
||||
dir = getDefaultDir()
|
||||
if dir == "" {
|
||||
logger.FatalIf(errInvalidArgument, "%s option must be provided", option)
|
||||
}
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
logger.FatalIf(errors.New("empty directory"), "%s directory cannot be empty", option)
|
||||
if !dirSet {
|
||||
return nil, fmt.Errorf("missing option must be provided")
|
||||
}
|
||||
return nil, fmt.Errorf("provided option cannot be empty")
|
||||
}
|
||||
|
||||
// Disallow relative paths, figure out absolute paths.
|
||||
dirAbs, err := filepath.Abs(dir)
|
||||
logger.FatalIf(err, "Unable to fetch absolute path for %s=%s", option, dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = mkdirAllIgnorePerm(dirAbs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create the directory `%s`: %w", dirAbs, err)
|
||||
}
|
||||
|
||||
logger.FatalIf(mkdirAllIgnorePerm(dirAbs), "Unable to create directory specified %s=%s", option, dir)
|
||||
|
||||
return &ConfigDir{path: dirAbs}, dirSet
|
||||
return &ConfigDir{path: dirAbs}, nil
|
||||
}
|
||||
|
||||
func handleCommonCmdArgs(ctx *cli.Context) {
|
||||
func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
||||
// Get "json" flag from command line argument and
|
||||
// enable json and quite modes if json flag is turned on.
|
||||
globalCLIContext.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
|
||||
if globalCLIContext.JSON {
|
||||
logger.EnableJSON()
|
||||
}
|
||||
|
||||
ctxt.JSON = ctx.IsSet("json") || ctx.GlobalIsSet("json")
|
||||
// Get quiet flag from command line argument.
|
||||
globalCLIContext.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
|
||||
if globalCLIContext.Quiet {
|
||||
logger.EnableQuiet()
|
||||
}
|
||||
|
||||
ctxt.Quiet = ctx.IsSet("quiet") || ctx.GlobalIsSet("quiet")
|
||||
// Get anonymous flag from command line argument.
|
||||
globalCLIContext.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous")
|
||||
if globalCLIContext.Anonymous {
|
||||
logger.EnableAnonymous()
|
||||
}
|
||||
|
||||
ctxt.Anonymous = ctx.IsSet("anonymous") || ctx.GlobalIsSet("anonymous")
|
||||
// Fetch address option
|
||||
addr := ctx.GlobalString("address")
|
||||
if addr == "" || addr == ":"+GlobalMinioDefaultPort {
|
||||
addr = ctx.String("address")
|
||||
ctxt.Addr = ctx.GlobalString("address")
|
||||
if ctxt.Addr == "" || ctxt.Addr == ":"+GlobalMinioDefaultPort {
|
||||
ctxt.Addr = ctx.String("address")
|
||||
}
|
||||
|
||||
// Fetch console address option
|
||||
consoleAddr := ctx.GlobalString("console-address")
|
||||
if consoleAddr == "" {
|
||||
consoleAddr = ctx.String("console-address")
|
||||
ctxt.ConsoleAddr = ctx.GlobalString("console-address")
|
||||
if ctxt.ConsoleAddr == "" {
|
||||
ctxt.ConsoleAddr = ctx.String("console-address")
|
||||
}
|
||||
|
||||
// Check "no-compat" flag from command line argument.
|
||||
ctxt.StrictS3Compat = !(ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat"))
|
||||
ctxt.PreAllocate = ctx.IsSet("pre-allocate") || ctx.GlobalIsSet("pre-allocate")
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("config-dir"):
|
||||
ctxt.ConfigDir = ctx.String("config-dir")
|
||||
ctxt.configDirSet = true
|
||||
case ctx.GlobalIsSet("config-dir"):
|
||||
ctxt.ConfigDir = ctx.GlobalString("config-dir")
|
||||
ctxt.configDirSet = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("certs-dir"):
|
||||
ctxt.CertsDir = ctx.String("certs-dir")
|
||||
ctxt.certsDirSet = true
|
||||
case ctx.GlobalIsSet("certs-dir"):
|
||||
ctxt.CertsDir = ctx.GlobalString("certs-dir")
|
||||
ctxt.certsDirSet = true
|
||||
}
|
||||
|
||||
ctxt.FTP = ctx.StringSlice("ftp")
|
||||
ctxt.SFTP = ctx.StringSlice("sftp")
|
||||
|
||||
ctxt.Interface = ctx.String("interface")
|
||||
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
|
||||
ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
|
||||
ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
|
||||
|
||||
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
|
||||
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
|
||||
ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")
|
||||
|
||||
if conf := ctx.String("config"); len(conf) > 0 {
|
||||
err = mergeServerCtxtFromConfigFile(conf, ctxt)
|
||||
} else {
|
||||
err = mergeDisksLayoutFromArgs(serverCmdArgs(ctx), ctxt)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func handleCommonArgs(ctxt serverCtxt) {
|
||||
if ctxt.JSON {
|
||||
logger.EnableJSON()
|
||||
}
|
||||
if ctxt.Quiet {
|
||||
logger.EnableQuiet()
|
||||
}
|
||||
if ctxt.Anonymous {
|
||||
logger.EnableAnonymous()
|
||||
}
|
||||
|
||||
consoleAddr := ctxt.ConsoleAddr
|
||||
addr := ctxt.Addr
|
||||
configDir := ctxt.ConfigDir
|
||||
configSet := ctxt.configDirSet
|
||||
certsDir := ctxt.CertsDir
|
||||
certsSet := ctxt.certsDirSet
|
||||
|
||||
if consoleAddr == "" {
|
||||
p, err := xnet.GetFreePort()
|
||||
if err != nil {
|
||||
logger.FatalIf(err, "Unable to get free port for Console UI on the host")
|
||||
}
|
||||
// hold the port
|
||||
l, err := net.Listen("TCP", fmt.Sprintf(":%s", p.String()))
|
||||
if err == nil {
|
||||
defer l.Close()
|
||||
}
|
||||
consoleAddr = net.JoinHostPort("", p.String())
|
||||
}
|
||||
|
||||
@@ -417,16 +454,12 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
||||
|
||||
globalMinioAddr = addr
|
||||
|
||||
// Check "no-compat" flag from command line argument.
|
||||
globalCLIContext.StrictS3Compat = true
|
||||
if ctx.IsSet("no-compat") || ctx.GlobalIsSet("no-compat") {
|
||||
globalCLIContext.StrictS3Compat = false
|
||||
}
|
||||
|
||||
// Set all config, certs and CAs directories.
|
||||
var configSet, certsSet bool
|
||||
globalConfigDir, configSet = newConfigDirFromCtx(ctx, "config-dir", defaultConfigDir.Get)
|
||||
globalCertsDir, certsSet = newConfigDirFromCtx(ctx, "certs-dir", defaultCertsDir.Get)
|
||||
var err error
|
||||
globalConfigDir, err = newConfigDir(configDir, configSet, defaultConfigDir.Get)
|
||||
logger.FatalIf(err, "Unable to initialize the (deprecated) config directory")
|
||||
globalCertsDir, err = newConfigDir(certsDir, certsSet, defaultCertsDir.Get)
|
||||
logger.FatalIf(err, "Unable to initialize the certs directory")
|
||||
|
||||
// Remove this code when we deprecate and remove config-dir.
|
||||
// This code is to make sure we inherit from the config-dir
|
||||
@@ -438,9 +471,11 @@ func handleCommonCmdArgs(ctx *cli.Context) {
|
||||
globalCertsCADir = &ConfigDir{path: filepath.Join(globalCertsDir.Get(), certsCADir)}
|
||||
|
||||
logger.FatalIf(mkdirAllIgnorePerm(globalCertsCADir.Get()), "Unable to create certs CA directory at %s", globalCertsCADir.Get())
|
||||
}
|
||||
|
||||
// Check if we have configured a custom DNS cache TTL.
|
||||
func runDNSCache(ctx *cli.Context) {
|
||||
dnsTTL := ctx.Duration("dns-cache-ttl")
|
||||
// Check if we have configured a custom DNS cache TTL.
|
||||
if dnsTTL <= 0 {
|
||||
dnsTTL = 10 * time.Minute
|
||||
}
|
||||
@@ -621,7 +656,7 @@ func loadEnvVarsFromFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
func handleCommonEnvVars() {
|
||||
func serverHandleEnvVars() {
|
||||
var err error
|
||||
globalBrowserEnabled, err = config.ParseBool(env.Get(config.EnvBrowser, config.EnableOn))
|
||||
if err != nil {
|
||||
@@ -749,6 +784,10 @@ func handleCommonEnvVars() {
|
||||
}
|
||||
}
|
||||
|
||||
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
|
||||
}
|
||||
|
||||
func loadRootCredentials() {
|
||||
// At this point, either both environment variables
|
||||
// are defined or both are not defined.
|
||||
// Check both cases and authenticate them if correctly defined
|
||||
@@ -763,6 +802,9 @@ func handleCommonEnvVars() {
|
||||
user = env.Get(config.EnvAccessKey, "")
|
||||
password = env.Get(config.EnvSecretKey, "")
|
||||
hasCredentials = true
|
||||
} else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" {
|
||||
user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd
|
||||
hasCredentials = true
|
||||
}
|
||||
if hasCredentials {
|
||||
cred, err := auth.CreateCredentials(user, password)
|
||||
@@ -782,8 +824,6 @@ func handleCommonEnvVars() {
|
||||
} else {
|
||||
globalActiveCred = auth.DefaultCredentials
|
||||
}
|
||||
|
||||
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog
|
||||
}
|
||||
|
||||
// Initialize KMS global variable after valiadating and loading the configuration.
|
||||
|
||||
@@ -223,7 +223,7 @@ func initHelp() {
|
||||
config.HelpKV{
|
||||
Key: config.CacheSubSys,
|
||||
Type: "string",
|
||||
Description: "enable various cache optimizations on MinIO for reads",
|
||||
Description: "enable cache plugin on MinIO for GET/HEAD requests",
|
||||
Optional: true,
|
||||
},
|
||||
}
|
||||
|
||||
+11
-11
@@ -121,13 +121,13 @@ func readBackgroundHealInfo(ctx context.Context, objAPI ObjectLayer) backgroundH
|
||||
buf, err := readConfig(ctx, objAPI, backgroundHealInfoPath)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errConfigNotFound) {
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, backgroundHealInfoPath)
|
||||
}
|
||||
return backgroundHealInfo{}
|
||||
}
|
||||
var info backgroundHealInfo
|
||||
if err = json.Unmarshal(buf, &info); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, backgroundHealInfoPath)
|
||||
}
|
||||
return info
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
results := make(chan DataUsageInfo, 1)
|
||||
go storeDataUsageInBackend(ctx, objAPI, results)
|
||||
err := objAPI.NSScanner(ctx, results, uint32(cycleInfo.current), scanMode)
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, "ns-scanner")
|
||||
res := map[string]string{"cycle": strconv.FormatUint(cycleInfo.current, 10)}
|
||||
if err != nil {
|
||||
res["error"] = err.Error()
|
||||
@@ -223,7 +223,7 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
binary.LittleEndian.PutUint64(tmp, cycleInfo.next)
|
||||
tmp, _ = cycleInfo.MarshalMsg(tmp)
|
||||
err = saveConfig(ctx, objAPI, dataUsageBloomNamePath, tmp)
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, dataUsageBloomNamePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -581,12 +581,12 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
foundAny = true
|
||||
break
|
||||
}
|
||||
if next := f.updateCache.searchParent(parent); next == nil {
|
||||
next := f.updateCache.searchParent(parent)
|
||||
if next == nil {
|
||||
foundAny = true
|
||||
break
|
||||
} else {
|
||||
parent = *next
|
||||
}
|
||||
parent = *next
|
||||
}
|
||||
if !foundAny {
|
||||
// Add non-compacted empty entry.
|
||||
@@ -729,7 +729,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
versionID: "",
|
||||
}, madmin.HealItemObject)
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, entry.name)
|
||||
}
|
||||
foundObjs = foundObjs || err == nil
|
||||
return
|
||||
@@ -746,7 +746,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
|
||||
}, madmin.HealItemObject)
|
||||
stopFn(int(ver.Size))
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, fiv.Name)
|
||||
}
|
||||
if err == nil {
|
||||
successVersions++
|
||||
@@ -1133,7 +1133,7 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, oi Object
|
||||
err := o.CheckAbandonedParts(ctx, i.bucket, i.objectPath(), madmin.HealOpts{Remove: healDeleteDangling})
|
||||
done()
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err))
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("unable to check object %s/%s for abandoned data: %w", i.bucket, i.objectPath(), err), i.objectPath())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1199,7 +1199,7 @@ func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer,
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
return false
|
||||
}
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, obj.Name)
|
||||
return false
|
||||
}
|
||||
// Notification already sent in *expireTransitionedObject*, just return 'true' here.
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
||||
attempts = 1
|
||||
}
|
||||
if err = saveConfig(ctx, objAPI, dataUsageObjNamePath, dataUsageJSON); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
logger.LogOnceIf(ctx, err, dataUsageObjNamePath)
|
||||
}
|
||||
attempts++
|
||||
}
|
||||
|
||||
+150
-33
@@ -18,12 +18,15 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/pkg/v2/ellipses"
|
||||
@@ -335,13 +338,128 @@ const (
|
||||
|
||||
var globalCustomErasureDriveCount = false
|
||||
|
||||
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
|
||||
// both ellipses and without ellipses transparently.
|
||||
func createServerEndpoints(serverAddr string, args ...string) (
|
||||
endpointServerPools EndpointServerPools, setupType SetupType, err error,
|
||||
) {
|
||||
type node struct {
|
||||
nodeName string
|
||||
disks []string
|
||||
}
|
||||
|
||||
type endpointsList []node
|
||||
|
||||
func (el *endpointsList) add(arg string) error {
|
||||
u, err := url.Parse(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
found := false
|
||||
list := *el
|
||||
for i := range list {
|
||||
if list[i].nodeName == u.Host {
|
||||
list[i].disks = append(list[i].disks, u.String())
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
list = append(list, node{nodeName: u.Host, disks: []string{u.String()}})
|
||||
}
|
||||
*el = list
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildDisksLayoutFromConfFile supports with and without ellipses transparently.
|
||||
func buildDisksLayoutFromConfFile(pools [][]string) (layout disksLayout, err error) {
|
||||
if len(pools) == 0 {
|
||||
return layout, errInvalidArgument
|
||||
}
|
||||
|
||||
for _, list := range pools {
|
||||
var endpointsList endpointsList
|
||||
|
||||
for _, arg := range list {
|
||||
switch {
|
||||
case ellipses.HasList(arg):
|
||||
patterns, err := ellipses.FindListPatterns(arg)
|
||||
if err != nil {
|
||||
return layout, err
|
||||
}
|
||||
for _, exp := range patterns.Expand() {
|
||||
for _, ep := range exp {
|
||||
if err := endpointsList.add(ep); err != nil {
|
||||
return layout, err
|
||||
}
|
||||
}
|
||||
}
|
||||
case ellipses.HasEllipses(arg):
|
||||
patterns, err := ellipses.FindEllipsesPatterns(arg)
|
||||
if err != nil {
|
||||
return layout, err
|
||||
}
|
||||
for _, exp := range patterns.Expand() {
|
||||
if err := endpointsList.add(strings.Join(exp, "")); err != nil {
|
||||
return layout, err
|
||||
}
|
||||
}
|
||||
default:
|
||||
if err := endpointsList.add(arg); err != nil {
|
||||
return layout, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stopping bool
|
||||
var singleNode bool
|
||||
var eps []string
|
||||
|
||||
for i := 0; ; i++ {
|
||||
for _, node := range endpointsList {
|
||||
if node.nodeName == "" {
|
||||
singleNode = true
|
||||
}
|
||||
|
||||
if len(node.disks) <= i {
|
||||
stopping = true
|
||||
continue
|
||||
}
|
||||
if stopping {
|
||||
return layout, errors.New("number of disks per node does not match")
|
||||
}
|
||||
eps = append(eps, node.disks[i])
|
||||
}
|
||||
if stopping {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, node := range endpointsList {
|
||||
if node.nodeName != "" && singleNode {
|
||||
return layout, errors.New("all arguments must but either single node or distributed")
|
||||
}
|
||||
}
|
||||
|
||||
setArgs, err := GetAllSets(eps...)
|
||||
if err != nil {
|
||||
return layout, err
|
||||
}
|
||||
|
||||
h := xxhash.New()
|
||||
for _, s := range setArgs {
|
||||
for _, d := range s {
|
||||
h.WriteString(d)
|
||||
}
|
||||
}
|
||||
|
||||
layout.pools = append(layout.pools, poolDisksLayout{
|
||||
cmdline: fmt.Sprintf("hash:%x", h.Sum(nil)),
|
||||
layout: setArgs,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// mergeDisksLayoutFromArgs supports with and without ellipses transparently.
|
||||
func mergeDisksLayoutFromArgs(args []string, ctxt *serverCtxt) (err error) {
|
||||
if len(args) == 0 {
|
||||
return nil, -1, errInvalidArgument
|
||||
return errInvalidArgument
|
||||
}
|
||||
|
||||
ok := true
|
||||
@@ -349,44 +467,42 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
||||
ok = ok && !ellipses.HasEllipses(arg)
|
||||
}
|
||||
|
||||
var setArgs [][]string
|
||||
|
||||
// None of the args have ellipses use the old style.
|
||||
if ok {
|
||||
setArgs, err := GetAllSets(args...)
|
||||
setArgs, err = GetAllSets(args...)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
return err
|
||||
}
|
||||
endpointList, newSetupType, err := CreateEndpoints(serverAddr, setArgs...)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
ctxt.Layout = disksLayout{
|
||||
legacy: true,
|
||||
pools: []poolDisksLayout{{layout: setArgs}},
|
||||
}
|
||||
for i := range endpointList {
|
||||
endpointList[i].SetPoolIndex(0)
|
||||
}
|
||||
endpointServerPools = append(endpointServerPools, PoolEndpoints{
|
||||
Legacy: true,
|
||||
SetCount: len(setArgs),
|
||||
DrivesPerSet: len(setArgs[0]),
|
||||
Endpoints: endpointList,
|
||||
CmdLine: strings.Join(args, " "),
|
||||
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||
})
|
||||
setupType = newSetupType
|
||||
return endpointServerPools, setupType, nil
|
||||
return
|
||||
}
|
||||
|
||||
var poolArgs [][][]string
|
||||
for _, arg := range args {
|
||||
if !ellipses.HasEllipses(arg) && len(args) > 1 {
|
||||
// TODO: support SNSD deployments to be decommissioned in future
|
||||
return nil, -1, fmt.Errorf("all args must have ellipses for pool expansion (%w) args: %s", errInvalidArgument, args)
|
||||
return fmt.Errorf("all args must have ellipses for pool expansion (%w) args: %s", errInvalidArgument, args)
|
||||
}
|
||||
|
||||
setArgs, err := GetAllSets(arg)
|
||||
setArgs, err = GetAllSets(arg)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
return err
|
||||
}
|
||||
ctxt.Layout.pools = append(ctxt.Layout.pools, poolDisksLayout{cmdline: arg, layout: setArgs})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
poolArgs = append(poolArgs, setArgs)
|
||||
// CreateServerEndpoints - validates and creates new endpoints from input args, supports
|
||||
// both ellipses and without ellipses transparently.
|
||||
func createServerEndpoints(serverAddr string, poolArgs []poolDisksLayout, legacy bool) (
|
||||
endpointServerPools EndpointServerPools, setupType SetupType, err error,
|
||||
) {
|
||||
if len(poolArgs) == 0 {
|
||||
return nil, -1, errInvalidArgument
|
||||
}
|
||||
|
||||
poolEndpoints, setupType, err := CreatePoolEndpoints(serverAddr, poolArgs...)
|
||||
@@ -396,11 +512,12 @@ func createServerEndpoints(serverAddr string, args ...string) (
|
||||
|
||||
for i, endpointList := range poolEndpoints {
|
||||
if err = endpointServerPools.Add(PoolEndpoints{
|
||||
SetCount: len(poolArgs[i]),
|
||||
DrivesPerSet: len(poolArgs[i][0]),
|
||||
Legacy: legacy,
|
||||
SetCount: len(poolArgs[i].layout),
|
||||
DrivesPerSet: len(poolArgs[i].layout[0]),
|
||||
Endpoints: endpointList,
|
||||
Platform: fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH),
|
||||
CmdLine: args[i],
|
||||
CmdLine: poolArgs[i].cmdline,
|
||||
}); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
@@ -54,15 +54,20 @@ func TestCreateServerEndpoints(t *testing.T) {
|
||||
{":9001", []string{"http://localhost:9001/export{01...64}"}, true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
for i, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run("", func(t *testing.T) {
|
||||
_, _, err := createServerEndpoints(testCase.serverAddr, testCase.args...)
|
||||
srvCtxt := serverCtxt{}
|
||||
err := mergeDisksLayoutFromArgs(testCase.args, &srvCtxt)
|
||||
if err != nil && testCase.success {
|
||||
t.Errorf("Expected success but failed instead %s", err)
|
||||
t.Fatalf("Test %d: unexpected error: %v", i+1, err)
|
||||
}
|
||||
_, _, err = createServerEndpoints(testCase.serverAddr, srvCtxt.Layout.pools, srvCtxt.Layout.legacy)
|
||||
if err != nil && testCase.success {
|
||||
t.Errorf("Test %d: Expected success but failed instead %s", i+1, err)
|
||||
}
|
||||
if err == nil && !testCase.success {
|
||||
t.Errorf("Expected failure but passed instead")
|
||||
t.Errorf("Test %d: Expected failure but passed instead", i+1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+21
-200
@@ -803,11 +803,23 @@ func (p PoolEndpointList) UpdateIsLocal() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEmptyLayout(poolsLayout ...poolDisksLayout) bool {
|
||||
return len(poolsLayout) == 0 || len(poolsLayout[0].layout) == 0 || len(poolsLayout[0].layout[0]) == 0 || len(poolsLayout[0].layout[0][0]) == 0
|
||||
}
|
||||
|
||||
func isSingleDriveLayout(poolsLayout ...poolDisksLayout) bool {
|
||||
return len(poolsLayout) == 1 && len(poolsLayout[0].layout) == 1 && len(poolsLayout[0].layout[0]) == 1
|
||||
}
|
||||
|
||||
// CreatePoolEndpoints creates a list of endpoints per pool, resolves their relevant hostnames and
|
||||
// discovers those are local or remote.
|
||||
func CreatePoolEndpoints(serverAddr string, poolArgs ...[][]string) ([]Endpoints, SetupType, error) {
|
||||
func CreatePoolEndpoints(serverAddr string, poolsLayout ...poolDisksLayout) ([]Endpoints, SetupType, error) {
|
||||
var setupType SetupType
|
||||
|
||||
if isEmptyLayout(poolsLayout...) {
|
||||
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg("invalid number of endpoints")
|
||||
}
|
||||
|
||||
// Check whether serverAddr is valid for this host.
|
||||
if err := CheckLocalServerAddr(serverAddr); err != nil {
|
||||
return nil, setupType, err
|
||||
@@ -815,11 +827,11 @@ func CreatePoolEndpoints(serverAddr string, poolArgs ...[][]string) ([]Endpoints
|
||||
|
||||
_, serverAddrPort := mustSplitHostPort(serverAddr)
|
||||
|
||||
poolEndpoints := make(PoolEndpointList, len(poolArgs))
|
||||
poolEndpoints := make(PoolEndpointList, len(poolsLayout))
|
||||
|
||||
// For single arg, return single drive EC setup.
|
||||
if len(poolArgs) == 1 && len(poolArgs[0]) == 1 && len(poolArgs[0][0]) == 1 && len(poolArgs[0][0][0]) == 1 {
|
||||
endpoint, err := NewEndpoint(poolArgs[0][0][0])
|
||||
if isSingleDriveLayout(poolsLayout...) {
|
||||
endpoint, err := NewEndpoint(poolsLayout[0].layout[0][0])
|
||||
if err != nil {
|
||||
return nil, setupType, err
|
||||
}
|
||||
@@ -847,11 +859,11 @@ func CreatePoolEndpoints(serverAddr string, poolArgs ...[][]string) ([]Endpoints
|
||||
return poolEndpoints, setupType, nil
|
||||
}
|
||||
|
||||
for poolIdx, args := range poolArgs {
|
||||
for poolIdx, pool := range poolsLayout {
|
||||
var endpoints Endpoints
|
||||
for setIdx, iargs := range args {
|
||||
for setIdx, setLayout := range pool.layout {
|
||||
// Convert args to endpoints
|
||||
eps, err := NewEndpoints(iargs...)
|
||||
eps, err := NewEndpoints(setLayout...)
|
||||
if err != nil {
|
||||
return nil, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
||||
}
|
||||
@@ -1003,6 +1015,8 @@ func CreatePoolEndpoints(serverAddr string, poolArgs ...[][]string) ([]Endpoints
|
||||
poolEndpoints[i] = endpoints
|
||||
}
|
||||
|
||||
// TODO: ensure that each pool has at least two nodes in a distributed setup
|
||||
|
||||
publicIPs := env.Get(config.EnvPublicIPs, "")
|
||||
if len(publicIPs) == 0 {
|
||||
updateDomainIPs(uniqueArgs)
|
||||
@@ -1021,199 +1035,6 @@ func CreatePoolEndpoints(serverAddr string, poolArgs ...[][]string) ([]Endpoints
|
||||
return poolEndpoints, setupType, nil
|
||||
}
|
||||
|
||||
// CreateEndpoints - validates and creates new endpoints for given args.
|
||||
func CreateEndpoints(serverAddr string, args ...[]string) (Endpoints, SetupType, error) {
|
||||
var endpoints Endpoints
|
||||
var setupType SetupType
|
||||
var err error
|
||||
|
||||
// Check whether serverAddr is valid for this host.
|
||||
if err = CheckLocalServerAddr(serverAddr); err != nil {
|
||||
return endpoints, setupType, err
|
||||
}
|
||||
|
||||
_, serverAddrPort := mustSplitHostPort(serverAddr)
|
||||
|
||||
// For single arg, return single drive setup.
|
||||
if len(args) == 1 && len(args[0]) == 1 {
|
||||
var endpoint Endpoint
|
||||
endpoint, err = NewEndpoint(args[0][0])
|
||||
if err != nil {
|
||||
return endpoints, setupType, err
|
||||
}
|
||||
if err := endpoint.UpdateIsLocal(); err != nil {
|
||||
return endpoints, setupType, err
|
||||
}
|
||||
if endpoint.Type() != PathEndpointType {
|
||||
return endpoints, setupType, config.ErrInvalidEndpoint(nil).Msg("use path style endpoint for single node setup")
|
||||
}
|
||||
|
||||
endpoint.SetPoolIndex(0)
|
||||
endpoint.SetSetIndex(0)
|
||||
endpoint.SetDiskIndex(0)
|
||||
|
||||
endpoints = append(endpoints, endpoint)
|
||||
setupType = ErasureSDSetupType
|
||||
|
||||
// Check for cross device mounts if any.
|
||||
if err = checkCrossDeviceMounts(endpoints); err != nil {
|
||||
return endpoints, setupType, config.ErrInvalidEndpoint(nil).Msg(err.Error())
|
||||
}
|
||||
|
||||
return endpoints, setupType, nil
|
||||
}
|
||||
|
||||
for setIdx, iargs := range args {
|
||||
// Convert args to endpoints
|
||||
eps, err := NewEndpoints(iargs...)
|
||||
if err != nil {
|
||||
return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
||||
}
|
||||
|
||||
// Check for cross device mounts if any.
|
||||
if err = checkCrossDeviceMounts(eps); err != nil {
|
||||
return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
||||
}
|
||||
|
||||
for diskIdx := range eps {
|
||||
eps[diskIdx].SetSetIndex(setIdx)
|
||||
eps[diskIdx].SetDiskIndex(diskIdx)
|
||||
}
|
||||
|
||||
endpoints = append(endpoints, eps...)
|
||||
}
|
||||
|
||||
if len(endpoints) == 0 {
|
||||
return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg("invalid number of endpoints")
|
||||
}
|
||||
|
||||
// Return Erasure setup when all endpoints are path style.
|
||||
if endpoints[0].Type() == PathEndpointType {
|
||||
setupType = ErasureSetupType
|
||||
return endpoints, setupType, nil
|
||||
}
|
||||
|
||||
if err = endpoints.UpdateIsLocal(); err != nil {
|
||||
return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(err.Error())
|
||||
}
|
||||
|
||||
// Here all endpoints are URL style.
|
||||
endpointPathSet := set.NewStringSet()
|
||||
localEndpointCount := 0
|
||||
localServerHostSet := set.NewStringSet()
|
||||
localPortSet := set.NewStringSet()
|
||||
|
||||
for _, endpoint := range endpoints {
|
||||
endpointPathSet.Add(endpoint.Path)
|
||||
if endpoint.IsLocal {
|
||||
localServerHostSet.Add(endpoint.Hostname())
|
||||
|
||||
var port string
|
||||
_, port, err = net.SplitHostPort(endpoint.Host)
|
||||
if err != nil {
|
||||
port = serverAddrPort
|
||||
}
|
||||
localPortSet.Add(port)
|
||||
|
||||
localEndpointCount++
|
||||
}
|
||||
}
|
||||
|
||||
orchestrated := IsKubernetes() || IsDocker()
|
||||
reverseProxy := (env.Get("_MINIO_REVERSE_PROXY", "") != "") && ((env.Get("MINIO_CI_CD", "") != "") || (env.Get("CI", "") != ""))
|
||||
// If not orchestrated
|
||||
if !orchestrated &&
|
||||
// and not setup in reverse proxy
|
||||
!reverseProxy {
|
||||
// Check whether same path is not used in endpoints of a host on different port.
|
||||
// Only verify this on baremetal setups, DNS is not available in orchestrated
|
||||
// environments so we can't do much here.
|
||||
pathIPMap := make(map[string]set.StringSet)
|
||||
hostIPCache := make(map[string]set.StringSet)
|
||||
for _, endpoint := range endpoints {
|
||||
host := endpoint.Hostname()
|
||||
hostIPSet, ok := hostIPCache[host]
|
||||
if !ok {
|
||||
hostIPSet, err = getHostIP(host)
|
||||
if err != nil {
|
||||
return endpoints, setupType, config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("host '%s' cannot resolve: %s", host, err))
|
||||
}
|
||||
hostIPCache[host] = hostIPSet
|
||||
}
|
||||
if IPSet, ok := pathIPMap[endpoint.Path]; ok {
|
||||
if !IPSet.Intersection(hostIPSet).IsEmpty() {
|
||||
return endpoints, setupType,
|
||||
config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("same path '%s' can not be served by different port on same address", endpoint.Path))
|
||||
}
|
||||
pathIPMap[endpoint.Path] = IPSet.Union(hostIPSet)
|
||||
} else {
|
||||
pathIPMap[endpoint.Path] = hostIPSet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether same path is used for more than 1 local endpoints.
|
||||
{
|
||||
localPathSet := set.CreateStringSet()
|
||||
for _, endpoint := range endpoints {
|
||||
if !endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
if localPathSet.Contains(endpoint.Path) {
|
||||
return endpoints, setupType,
|
||||
config.ErrInvalidErasureEndpoints(nil).Msg(fmt.Sprintf("path '%s' cannot be served by different address on same server", endpoint.Path))
|
||||
}
|
||||
localPathSet.Add(endpoint.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Add missing port in all endpoints.
|
||||
for i := range endpoints {
|
||||
_, port, err := net.SplitHostPort(endpoints[i].Host)
|
||||
if err != nil {
|
||||
endpoints[i].Host = net.JoinHostPort(endpoints[i].Host, serverAddrPort)
|
||||
} else if endpoints[i].IsLocal && serverAddrPort != port {
|
||||
// If endpoint is local, but port is different than serverAddrPort, then make it as remote.
|
||||
endpoints[i].IsLocal = false
|
||||
}
|
||||
}
|
||||
|
||||
// All endpoints are pointing to local host
|
||||
if len(endpoints) == localEndpointCount {
|
||||
// If all endpoints have same port number, Just treat it as local erasure setup
|
||||
// using URL style endpoints.
|
||||
if len(localPortSet) == 1 {
|
||||
if len(localServerHostSet) > 1 {
|
||||
return endpoints, setupType,
|
||||
config.ErrInvalidErasureEndpoints(nil).Msg("all local endpoints should not have different hostnames/ips")
|
||||
}
|
||||
return endpoints, ErasureSetupType, nil
|
||||
}
|
||||
|
||||
// Even though all endpoints are local, but those endpoints use different ports.
|
||||
// This means it is DistErasure setup.
|
||||
}
|
||||
|
||||
uniqueArgs := set.NewStringSet()
|
||||
for _, endpoint := range endpoints {
|
||||
uniqueArgs.Add(endpoint.Host)
|
||||
}
|
||||
|
||||
// Error out if we have less than 2 unique servers.
|
||||
if len(uniqueArgs.ToSlice()) < 2 && setupType == DistErasureSetupType {
|
||||
err := fmt.Errorf("Unsupported number of endpoints (%s), minimum number of servers cannot be less than 2 in distributed setup", endpoints)
|
||||
return endpoints, setupType, err
|
||||
}
|
||||
|
||||
publicIPs := env.Get(config.EnvPublicIPs, "")
|
||||
if len(publicIPs) == 0 {
|
||||
updateDomainIPs(uniqueArgs)
|
||||
}
|
||||
|
||||
setupType = DistErasureSetupType
|
||||
return endpoints, setupType, nil
|
||||
}
|
||||
|
||||
// GetLocalPeer - returns local peer value, returns globalMinioAddr
|
||||
// for FS and Erasure mode. In case of distributed server return
|
||||
// the first element from the set of peers which indicate that
|
||||
|
||||
+33
-25
@@ -225,24 +225,24 @@ func TestCreateEndpoints(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
serverAddr string
|
||||
args [][]string
|
||||
args []string
|
||||
expectedServerAddr string
|
||||
expectedEndpoints Endpoints
|
||||
expectedSetupType SetupType
|
||||
expectedErr error
|
||||
}{
|
||||
{"localhost", [][]string{}, "", Endpoints{}, -1, fmt.Errorf("address localhost: missing port in address")},
|
||||
{"localhost", []string{}, "", Endpoints{}, -1, fmt.Errorf("address localhost: missing port in address")},
|
||||
|
||||
// Erasure Single Drive
|
||||
{"localhost:9000", [][]string{{"http://localhost/d1"}}, "", Endpoints{}, -1, fmt.Errorf("use path style endpoint for FS setup")},
|
||||
{":443", [][]string{{"/d1"}}, ":443", Endpoints{Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true}}, ErasureSDSetupType, nil},
|
||||
{"localhost:10000", [][]string{{"/d1"}}, "localhost:10000", Endpoints{Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true}}, ErasureSDSetupType, nil},
|
||||
{"localhost:9000", [][]string{{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}}, "", Endpoints{}, -1, fmt.Errorf("path '/d1' can not be served by different port on same address")},
|
||||
{"localhost:9000", []string{"http://localhost/d1"}, "", Endpoints{}, -1, fmt.Errorf("use path style endpoint for SD setup")},
|
||||
{":443", []string{"/d1"}, ":443", Endpoints{Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true}}, ErasureSDSetupType, nil},
|
||||
{"localhost:10000", []string{"/d1"}, "localhost:10000", Endpoints{Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true}}, ErasureSDSetupType, nil},
|
||||
{"localhost:9000", []string{"https://127.0.0.1:9000/d1", "https://localhost:9001/d1", "https://example.com/d1", "https://example.com/d2"}, "", Endpoints{}, -1, fmt.Errorf("path '/d1' can not be served by different port on same address")},
|
||||
|
||||
// Erasure Setup with PathEndpointType
|
||||
{
|
||||
":1234",
|
||||
[][]string{{"/d1", "/d2", "/d3", "/d4"}},
|
||||
[]string{"/d1", "/d2", "/d3", "/d4"},
|
||||
":1234",
|
||||
Endpoints{
|
||||
Endpoint{URL: &url.URL{Path: mustAbs("/d1")}, IsLocal: true},
|
||||
@@ -253,49 +253,49 @@ func TestCreateEndpoints(t *testing.T) {
|
||||
ErasureSetupType, nil,
|
||||
},
|
||||
// DistErasure Setup with URLEndpointType
|
||||
{":9000", [][]string{{"http://localhost/d1", "http://localhost/d2", "http://localhost/d3", "http://localhost/d4"}}, ":9000", Endpoints{
|
||||
{":9000", []string{"http://localhost/d1", "http://localhost/d2", "http://localhost/d3", "http://localhost/d4"}, ":9000", Endpoints{
|
||||
Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d1"}, IsLocal: true},
|
||||
Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d2"}, IsLocal: true},
|
||||
Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d3"}, IsLocal: true},
|
||||
Endpoint{URL: &url.URL{Scheme: "http", Host: "localhost:9000", Path: "/d4"}, IsLocal: true},
|
||||
}, ErasureSetupType, nil},
|
||||
}, DistErasureSetupType, nil},
|
||||
// DistErasure Setup with URLEndpointType having mixed naming to local host.
|
||||
{"127.0.0.1:10000", [][]string{{"http://localhost/d1", "http://localhost/d2", "http://127.0.0.1/d3", "http://127.0.0.1/d4"}}, "", Endpoints{}, -1, fmt.Errorf("all local endpoints should not have different hostnames/ips")},
|
||||
{"127.0.0.1:10000", []string{"http://localhost/d1", "http://localhost/d2", "http://127.0.0.1/d3", "http://127.0.0.1/d4"}, "", Endpoints{}, -1, fmt.Errorf("all local endpoints should not have different hostnames/ips")},
|
||||
|
||||
{":9001", [][]string{{"http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export", "http://" + nonLoopBackIP + ":9001/export", "http://10.0.0.2:9001/export"}}, "", Endpoints{}, -1, fmt.Errorf("path '/export' can not be served by different port on same address")},
|
||||
{":9001", []string{"http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export", "http://" + nonLoopBackIP + ":9001/export", "http://10.0.0.2:9001/export"}, "", Endpoints{}, -1, fmt.Errorf("path '/export' can not be served by different port on same address")},
|
||||
|
||||
{":9000", [][]string{{"http://127.0.0.1:9000/export", "http://" + nonLoopBackIP + ":9000/export", "http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export"}}, "", Endpoints{}, -1, fmt.Errorf("path '/export' cannot be served by different address on same server")},
|
||||
{":9000", []string{"http://127.0.0.1:9000/export", "http://" + nonLoopBackIP + ":9000/export", "http://10.0.0.1:9000/export", "http://10.0.0.2:9000/export"}, "", Endpoints{}, -1, fmt.Errorf("path '/export' cannot be served by different address on same server")},
|
||||
|
||||
// DistErasure type
|
||||
{"127.0.0.1:10000", [][]string{{case1Endpoint1, case1Endpoint2, "http://example.org/d3", "http://example.com/d4"}}, "127.0.0.1:10000", Endpoints{
|
||||
{"127.0.0.1:10000", []string{case1Endpoint1, case1Endpoint2, "http://example.org/d3", "http://example.com/d4"}, "127.0.0.1:10000", Endpoints{
|
||||
Endpoint{URL: case1URLs[0], IsLocal: case1LocalFlags[0]},
|
||||
Endpoint{URL: case1URLs[1], IsLocal: case1LocalFlags[1]},
|
||||
Endpoint{URL: case1URLs[2], IsLocal: case1LocalFlags[2]},
|
||||
Endpoint{URL: case1URLs[3], IsLocal: case1LocalFlags[3]},
|
||||
}, DistErasureSetupType, nil},
|
||||
|
||||
{"127.0.0.1:10000", [][]string{{case2Endpoint1, case2Endpoint2, "http://example.org/d3", "http://example.com/d4"}}, "127.0.0.1:10000", Endpoints{
|
||||
{"127.0.0.1:10000", []string{case2Endpoint1, case2Endpoint2, "http://example.org/d3", "http://example.com/d4"}, "127.0.0.1:10000", Endpoints{
|
||||
Endpoint{URL: case2URLs[0], IsLocal: case2LocalFlags[0]},
|
||||
Endpoint{URL: case2URLs[1], IsLocal: case2LocalFlags[1]},
|
||||
Endpoint{URL: case2URLs[2], IsLocal: case2LocalFlags[2]},
|
||||
Endpoint{URL: case2URLs[3], IsLocal: case2LocalFlags[3]},
|
||||
}, DistErasureSetupType, nil},
|
||||
|
||||
{":80", [][]string{{case3Endpoint1, "http://example.org:9000/d2", "http://example.com/d3", "http://example.net/d4"}}, ":80", Endpoints{
|
||||
{":80", []string{case3Endpoint1, "http://example.org:9000/d2", "http://example.com/d3", "http://example.net/d4"}, ":80", Endpoints{
|
||||
Endpoint{URL: case3URLs[0], IsLocal: case3LocalFlags[0]},
|
||||
Endpoint{URL: case3URLs[1], IsLocal: case3LocalFlags[1]},
|
||||
Endpoint{URL: case3URLs[2], IsLocal: case3LocalFlags[2]},
|
||||
Endpoint{URL: case3URLs[3], IsLocal: case3LocalFlags[3]},
|
||||
}, DistErasureSetupType, nil},
|
||||
|
||||
{":9000", [][]string{{case4Endpoint1, "http://example.org/d2", "http://example.com/d3", "http://example.net/d4"}}, ":9000", Endpoints{
|
||||
{":9000", []string{case4Endpoint1, "http://example.org/d2", "http://example.com/d3", "http://example.net/d4"}, ":9000", Endpoints{
|
||||
Endpoint{URL: case4URLs[0], IsLocal: case4LocalFlags[0]},
|
||||
Endpoint{URL: case4URLs[1], IsLocal: case4LocalFlags[1]},
|
||||
Endpoint{URL: case4URLs[2], IsLocal: case4LocalFlags[2]},
|
||||
Endpoint{URL: case4URLs[3], IsLocal: case4LocalFlags[3]},
|
||||
}, DistErasureSetupType, nil},
|
||||
|
||||
{":9000", [][]string{{case5Endpoint1, case5Endpoint2, case5Endpoint3, case5Endpoint4}}, ":9000", Endpoints{
|
||||
{":9000", []string{case5Endpoint1, case5Endpoint2, case5Endpoint3, case5Endpoint4}, ":9000", Endpoints{
|
||||
Endpoint{URL: case5URLs[0], IsLocal: case5LocalFlags[0]},
|
||||
Endpoint{URL: case5URLs[1], IsLocal: case5LocalFlags[1]},
|
||||
Endpoint{URL: case5URLs[2], IsLocal: case5LocalFlags[2]},
|
||||
@@ -303,7 +303,7 @@ func TestCreateEndpoints(t *testing.T) {
|
||||
}, DistErasureSetupType, nil},
|
||||
|
||||
// DistErasure Setup using only local host.
|
||||
{":9003", [][]string{{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://127.0.0.1:9002/d3", case6Endpoint}}, ":9003", Endpoints{
|
||||
{":9003", []string{"http://localhost:9000/d1", "http://localhost:9001/d2", "http://127.0.0.1:9002/d3", case6Endpoint}, ":9003", Endpoints{
|
||||
Endpoint{URL: case6URLs[0], IsLocal: case6LocalFlags[0]},
|
||||
Endpoint{URL: case6URLs[1], IsLocal: case6LocalFlags[1]},
|
||||
Endpoint{URL: case6URLs[2], IsLocal: case6LocalFlags[2]},
|
||||
@@ -311,24 +311,32 @@ func TestCreateEndpoints(t *testing.T) {
|
||||
}, DistErasureSetupType, nil},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
for i, testCase := range testCases {
|
||||
i := i
|
||||
testCase := testCase
|
||||
t.Run("", func(t *testing.T) {
|
||||
endpoints, setupType, err := CreateEndpoints(testCase.serverAddr, testCase.args...)
|
||||
var srvCtxt serverCtxt
|
||||
err := mergeDisksLayoutFromArgs(testCase.args, &srvCtxt)
|
||||
if err != nil && testCase.expectedErr == nil {
|
||||
t.Errorf("Test %d: unexpected error: %v", i+1, err)
|
||||
}
|
||||
pools, setupType, err := CreatePoolEndpoints(testCase.serverAddr, srvCtxt.Layout.pools...)
|
||||
if err == nil && testCase.expectedErr != nil {
|
||||
t.Errorf("error: expected = %v, got = <nil>", testCase.expectedErr)
|
||||
t.Errorf("Test %d: expected = %v, got = <nil>", i+1, testCase.expectedErr)
|
||||
}
|
||||
if err == nil {
|
||||
if setupType != testCase.expectedSetupType {
|
||||
t.Errorf("setupType: expected = %v, got = %v", testCase.expectedSetupType, setupType)
|
||||
t.Errorf("Test %d: setupType: expected = %v, got = %v", i+1, testCase.expectedSetupType, setupType)
|
||||
}
|
||||
endpoints := pools[0]
|
||||
if len(endpoints) != len(testCase.expectedEndpoints) {
|
||||
t.Errorf("endpoints: expected = %d, got = %d", len(testCase.expectedEndpoints),
|
||||
t.Errorf("Test %d: endpoints: expected = %d, got = %d", i+1, len(testCase.expectedEndpoints),
|
||||
len(endpoints))
|
||||
} else {
|
||||
for i, endpoint := range endpoints {
|
||||
if testCase.expectedEndpoints[i].String() != endpoint.String() {
|
||||
t.Errorf("endpoints: expected = %s, got = %s",
|
||||
t.Errorf("Test %d: endpoints: expected = %s, got = %s",
|
||||
i+1,
|
||||
testCase.expectedEndpoints[i],
|
||||
endpoint)
|
||||
}
|
||||
@@ -336,7 +344,7 @@ func TestCreateEndpoints(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if err != nil && testCase.expectedErr == nil {
|
||||
t.Errorf("error: expected = <nil>, got = %v, testCase: %v", err, testCase)
|
||||
t.Errorf("Test %d: error: expected = <nil>, got = %v, testCase: %v", i+1, err, testCase)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ func (fi FileInfo) DataShardFixed() bool {
|
||||
return fi.Metadata[reservedMetadataPrefixLowerDataShardFix] == "true"
|
||||
}
|
||||
|
||||
func (er erasureObjects) listAndHeal(bucket, prefix string, healEntry func(string, metaCacheEntry) error) error {
|
||||
func (er erasureObjects) listAndHeal(bucket, prefix string, scanMode madmin.HealScanMode, healEntry func(string, metaCacheEntry, madmin.HealScanMode) error) error {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
@@ -101,7 +101,7 @@ func (er erasureObjects) listAndHeal(bucket, prefix string, healEntry func(strin
|
||||
minDisks: 1,
|
||||
reportNotFound: false,
|
||||
agreed: func(entry metaCacheEntry) {
|
||||
if err := healEntry(bucket, entry); err != nil {
|
||||
if err := healEntry(bucket, entry, scanMode); err != nil {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
@@ -113,7 +113,7 @@ func (er erasureObjects) listAndHeal(bucket, prefix string, healEntry func(strin
|
||||
entry, _ = entries.firstFound()
|
||||
}
|
||||
|
||||
if err := healEntry(bucket, *entry); err != nil {
|
||||
if err := healEntry(bucket, *entry, scanMode); err != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -647,8 +647,8 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
}
|
||||
|
||||
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true},
|
||||
func(bucket, object, vid string) error {
|
||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{Remove: true})
|
||||
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
||||
return err
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -694,8 +694,8 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
}
|
||||
|
||||
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true},
|
||||
func(bucket, object, vid string) error {
|
||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{Remove: true})
|
||||
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
||||
return err
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -743,8 +743,8 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
}
|
||||
|
||||
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true},
|
||||
func(bucket, object, vid string) error {
|
||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{Remove: true})
|
||||
func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
|
||||
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
|
||||
return err
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -662,12 +662,12 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
||||
// Account for padding and forced compression overhead and encryption.
|
||||
buffer = make([]byte, data.ActualSize()+256+32+32, data.ActualSize()*2+512)
|
||||
} else {
|
||||
buffer = er.bp.Get()
|
||||
defer er.bp.Put(buffer)
|
||||
buffer = globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(buffer)
|
||||
}
|
||||
case size >= fi.Erasure.BlockSize:
|
||||
buffer = er.bp.Get()
|
||||
defer er.bp.Put(buffer)
|
||||
buffer = globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(buffer)
|
||||
case size < fi.Erasure.BlockSize:
|
||||
// No need to allocate fully fi.Erasure.BlockSize buffer if the incoming data is smaller.
|
||||
buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1))
|
||||
@@ -688,10 +688,10 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
||||
if data.Size() > bigFileThreshold {
|
||||
// Add input readahead.
|
||||
// We use 2 buffers, so we always have a full buffer of input.
|
||||
bufA := er.bp.Get()
|
||||
bufB := er.bp.Get()
|
||||
defer er.bp.Put(bufA)
|
||||
defer er.bp.Put(bufB)
|
||||
bufA := globalBytePoolCap.Get()
|
||||
bufB := globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(bufA)
|
||||
defer globalBytePoolCap.Put(bufB)
|
||||
ra, err := readahead.NewReaderBuffer(data, [][]byte{bufA[:fi.Erasure.BlockSize], bufB[:fi.Erasure.BlockSize]})
|
||||
if err == nil {
|
||||
toEncode = ra
|
||||
|
||||
+12
-11
@@ -413,6 +413,7 @@ func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, obje
|
||||
queued: time.Now(),
|
||||
setIndex: er.setIndex,
|
||||
poolIndex: er.poolIndex,
|
||||
scanMode: scan,
|
||||
})
|
||||
})
|
||||
// Healing is triggered and we have written
|
||||
@@ -1126,8 +1127,8 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
|
||||
case size == 0:
|
||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||
case size >= fi.Erasure.BlockSize:
|
||||
buffer = er.bp.Get()
|
||||
defer er.bp.Put(buffer)
|
||||
buffer = globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(buffer)
|
||||
case size < fi.Erasure.BlockSize:
|
||||
// No need to allocate fully blockSizeV1 buffer if the incoming data is smaller.
|
||||
buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1))
|
||||
@@ -1217,7 +1218,7 @@ func (er erasureObjects) PutObject(ctx context.Context, bucket string, object st
|
||||
}
|
||||
|
||||
// Heal up to two versions of one object when there is disparity between disks
|
||||
func healObjectVersionsDisparity(bucket string, entry metaCacheEntry) error {
|
||||
func healObjectVersionsDisparity(bucket string, entry metaCacheEntry, scanMode madmin.HealScanMode) error {
|
||||
if entry.isDir() {
|
||||
return nil
|
||||
}
|
||||
@@ -1241,13 +1242,13 @@ func healObjectVersionsDisparity(bucket string, entry metaCacheEntry) error {
|
||||
|
||||
fivs, err := entry.fileInfoVersions(bucket)
|
||||
if err != nil {
|
||||
go healObject(bucket, entry.name, "", madmin.HealDeepScan)
|
||||
healObject(bucket, entry.name, "", madmin.HealDeepScan)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(fivs.Versions) <= 2 {
|
||||
for _, version := range fivs.Versions {
|
||||
go healObject(bucket, entry.name, version.VersionID, madmin.HealNormalScan)
|
||||
healObject(bucket, entry.name, version.VersionID, scanMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1377,8 +1378,8 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
case size == 0:
|
||||
buffer = make([]byte, 1) // Allocate atleast a byte to reach EOF
|
||||
case size >= fi.Erasure.BlockSize || size == -1:
|
||||
buffer = er.bp.Get()
|
||||
defer er.bp.Put(buffer)
|
||||
buffer = globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(buffer)
|
||||
case size < fi.Erasure.BlockSize:
|
||||
// No need to allocate fully blockSizeV1 buffer if the incoming data is smaller.
|
||||
buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1))
|
||||
@@ -1437,10 +1438,10 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
toEncode := io.Reader(data)
|
||||
if data.Size() > bigFileThreshold {
|
||||
// We use 2 buffers, so we always have a full buffer of input.
|
||||
bufA := er.bp.Get()
|
||||
bufB := er.bp.Get()
|
||||
defer er.bp.Put(bufA)
|
||||
defer er.bp.Put(bufB)
|
||||
bufA := globalBytePoolCap.Get()
|
||||
bufB := globalBytePoolCap.Get()
|
||||
defer globalBytePoolCap.Put(bufA)
|
||||
defer globalBytePoolCap.Put(bufB)
|
||||
ra, err := readahead.NewReaderBuffer(data, [][]byte{bufA[:fi.Erasure.BlockSize], bufB[:fi.Erasure.BlockSize]})
|
||||
if err == nil {
|
||||
toEncode = ra
|
||||
|
||||
@@ -716,7 +716,7 @@ func (set *erasureObjects) listObjectsToDecommission(ctx context.Context, bi dec
|
||||
path: bi.Prefix,
|
||||
recursive: true,
|
||||
forwardTo: "",
|
||||
minDisks: len(disks) / 2,
|
||||
minDisks: listQuorum,
|
||||
reportNotFound: false,
|
||||
agreed: fn,
|
||||
partial: func(entries metaCacheEntries, _ []error) {
|
||||
|
||||
@@ -129,7 +129,7 @@ func (z *erasureServerPools) initRebalanceMeta(ctx context.Context, buckets []st
|
||||
}
|
||||
|
||||
// Fetch disk capacity and available space.
|
||||
si := z.StorageInfo(ctx)
|
||||
si := z.StorageInfo(ctx, true)
|
||||
diskStats := make([]struct {
|
||||
AvailableSpace uint64
|
||||
TotalSpace uint64
|
||||
@@ -624,10 +624,12 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
listQuorum := (len(disks) + 1) / 2
|
||||
|
||||
// How to resolve partial results.
|
||||
resolver := metadataResolutionParams{
|
||||
dirQuorum: len(disks) / 2, // make sure to capture all quorum ratios
|
||||
objQuorum: len(disks) / 2, // make sure to capture all quorum ratios
|
||||
dirQuorum: listQuorum, // make sure to capture all quorum ratios
|
||||
objQuorum: listQuorum, // make sure to capture all quorum ratios
|
||||
bucket: bucket,
|
||||
}
|
||||
err := listPathRaw(ctx, listPathRawOptions{
|
||||
@@ -635,7 +637,7 @@ func (z *erasureServerPools) rebalanceBucket(ctx context.Context, bucket string,
|
||||
bucket: bucket,
|
||||
recursive: true,
|
||||
forwardTo: "",
|
||||
minDisks: len(disks) / 2, // to capture all quorum ratios
|
||||
minDisks: listQuorum,
|
||||
reportNotFound: false,
|
||||
agreed: func(entry metaCacheEntry) {
|
||||
workers <- struct{}{}
|
||||
|
||||
+70
-56
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/bpool"
|
||||
"github.com/minio/minio/internal/config/storageclass"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
@@ -82,6 +83,21 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
}
|
||||
)
|
||||
|
||||
// Maximum number of reusable buffers per node at any given point in time.
|
||||
n := 1024 // single node single/multiple drives set this to 1024 entries
|
||||
|
||||
if globalIsDistErasure {
|
||||
n = 2048
|
||||
}
|
||||
|
||||
// Initialize byte pool once for all sets, bpool size is set to
|
||||
// setCount * setDriveCount with each memory upto blockSizeV2.
|
||||
globalBytePoolCap = bpool.NewBytePoolCap(n, blockSizeV2, blockSizeV2*2)
|
||||
|
||||
if globalServerCtxt.PreAllocate {
|
||||
globalBytePoolCap.Populate()
|
||||
}
|
||||
|
||||
var localDrives []StorageAPI
|
||||
local := endpointServerPools.FirstLocal()
|
||||
for i, ep := range endpointServerPools {
|
||||
@@ -107,12 +123,6 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, storageDisk := range storageDisks[i] {
|
||||
if storageDisk != nil && storageDisk.IsLocal() {
|
||||
localDrives = append(localDrives, storageDisk)
|
||||
}
|
||||
}
|
||||
|
||||
if deploymentID == "" {
|
||||
// all pools should have same deployment ID
|
||||
deploymentID = formats[i].ID
|
||||
@@ -139,6 +149,18 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
if distributionAlgo != "" && z.distributionAlgo == "" {
|
||||
z.distributionAlgo = distributionAlgo
|
||||
}
|
||||
|
||||
for _, storageDisk := range storageDisks[i] {
|
||||
if storageDisk != nil && storageDisk.IsLocal() {
|
||||
localDrives = append(localDrives, storageDisk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !globalIsDistErasure {
|
||||
globalLocalDrivesMu.Lock()
|
||||
globalLocalDrives = localDrives
|
||||
globalLocalDrivesMu.Unlock()
|
||||
}
|
||||
|
||||
z.decommissionCancelers = make([]context.CancelFunc, len(z.serverPools))
|
||||
@@ -167,10 +189,6 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
break
|
||||
}
|
||||
|
||||
globalLocalDrivesMu.Lock()
|
||||
globalLocalDrives = localDrives
|
||||
defer globalLocalDrivesMu.Unlock()
|
||||
|
||||
return z, nil
|
||||
}
|
||||
|
||||
@@ -595,7 +613,7 @@ func (z *erasureServerPools) BackendInfo() (b madmin.BackendInfo) {
|
||||
return
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
func (z *erasureServerPools) LocalStorageInfo(ctx context.Context, metrics bool) StorageInfo {
|
||||
var storageInfo StorageInfo
|
||||
|
||||
storageInfos := make([]StorageInfo, len(z.serverPools))
|
||||
@@ -603,7 +621,7 @@ func (z *erasureServerPools) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
for index := range z.serverPools {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
storageInfos[index] = z.serverPools[index].LocalStorageInfo(ctx)
|
||||
storageInfos[index] = z.serverPools[index].LocalStorageInfo(ctx, metrics)
|
||||
return nil
|
||||
}, index)
|
||||
}
|
||||
@@ -619,8 +637,8 @@ func (z *erasureServerPools) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
return storageInfo
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) StorageInfo(ctx context.Context) StorageInfo {
|
||||
return globalNotificationSys.StorageInfo(z)
|
||||
func (z *erasureServerPools) StorageInfo(ctx context.Context, metrics bool) StorageInfo {
|
||||
return globalNotificationSys.StorageInfo(z, metrics)
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) NSScanner(ctx context.Context, updates chan<- DataUsageInfo, wantCycle uint32, healScanMode madmin.HealScanMode) error {
|
||||
@@ -2108,10 +2126,10 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
|
||||
}
|
||||
|
||||
// HealObjectFn closure function heals the object.
|
||||
type HealObjectFn func(bucket, object, versionID string) error
|
||||
type HealObjectFn func(bucket, object, versionID string, scanMode madmin.HealScanMode) error
|
||||
|
||||
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObjectFn HealObjectFn) error {
|
||||
healEntry := func(bucket string, entry metaCacheEntry) error {
|
||||
healEntry := func(bucket string, entry metaCacheEntry, scanMode madmin.HealScanMode) error {
|
||||
if entry.isDir() {
|
||||
return nil
|
||||
}
|
||||
@@ -2134,7 +2152,7 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
|
||||
}
|
||||
fivs, err := entry.fileInfoVersions(bucket)
|
||||
if err != nil {
|
||||
return healObjectFn(bucket, entry.name, "")
|
||||
return healObjectFn(bucket, entry.name, "", scanMode)
|
||||
}
|
||||
if opts.Remove && !opts.DryRun {
|
||||
err := z.CheckAbandonedParts(ctx, bucket, entry.name, opts)
|
||||
@@ -2143,7 +2161,7 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
|
||||
}
|
||||
}
|
||||
for _, version := range fivs.Versions {
|
||||
err := healObjectFn(bucket, version.Name, version.VersionID)
|
||||
err := healObjectFn(bucket, version.Name, version.VersionID, scanMode)
|
||||
if err != nil && !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
return err
|
||||
}
|
||||
@@ -2167,7 +2185,7 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
|
||||
go func(idx int, set *erasureObjects) {
|
||||
defer wg.Done()
|
||||
|
||||
errs[idx] = set.listAndHeal(bucket, prefix, healEntry)
|
||||
errs[idx] = set.listAndHeal(bucket, prefix, opts.ScanMode, healEntry)
|
||||
}(idx, set)
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -2267,6 +2285,7 @@ type HealthResult struct {
|
||||
Maintenance bool
|
||||
PoolID, SetID int
|
||||
HealthyDrives int
|
||||
HealingDrives int
|
||||
WriteQuorum int
|
||||
}
|
||||
WriteQuorum int
|
||||
@@ -2313,29 +2332,36 @@ func (z *erasureServerPools) ReadHealth(ctx context.Context) bool {
|
||||
// can be used to query scenarios if health may be lost
|
||||
// if this node is taken down by an external orchestrator.
|
||||
func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) HealthResult {
|
||||
erasureSetUpCount := make([][]int, len(z.serverPools))
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("maintenance", strconv.FormatBool(opts.Maintenance))
|
||||
|
||||
type setInfo struct {
|
||||
online int
|
||||
healing int
|
||||
}
|
||||
|
||||
var drivesHealing int
|
||||
|
||||
erasureSetUpCount := make([][]setInfo, len(z.serverPools))
|
||||
for i := range z.serverPools {
|
||||
erasureSetUpCount[i] = make([]int, len(z.serverPools[i].sets))
|
||||
erasureSetUpCount[i] = make([]setInfo, len(z.serverPools[i].sets))
|
||||
}
|
||||
|
||||
diskIDs := globalNotificationSys.GetLocalDiskIDs(ctx)
|
||||
if !opts.Maintenance {
|
||||
diskIDs = append(diskIDs, getLocalDiskIDs(z))
|
||||
}
|
||||
storageInfo := z.StorageInfo(ctx, false)
|
||||
|
||||
for _, localDiskIDs := range diskIDs {
|
||||
for _, id := range localDiskIDs {
|
||||
poolIdx, setIdx, _, err := z.getPoolAndSet(id)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
for _, disk := range storageInfo.Disks {
|
||||
if disk.PoolIndex > -1 && disk.SetIndex > -1 {
|
||||
if disk.State == madmin.DriveStateOk {
|
||||
si := erasureSetUpCount[disk.PoolIndex][disk.SetIndex]
|
||||
si.online++
|
||||
if disk.Healing {
|
||||
si.healing++
|
||||
drivesHealing++
|
||||
}
|
||||
erasureSetUpCount[disk.PoolIndex][disk.SetIndex] = si
|
||||
}
|
||||
erasureSetUpCount[poolIdx][setIdx]++
|
||||
}
|
||||
}
|
||||
|
||||
reqInfo := (&logger.ReqInfo{}).AppendTags("maintenance", strconv.FormatBool(opts.Maintenance))
|
||||
|
||||
b := z.BackendInfo()
|
||||
poolWriteQuorums := make([]int, len(b.StandardSCData))
|
||||
for i, data := range b.StandardSCData {
|
||||
@@ -2345,23 +2371,10 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
}
|
||||
}
|
||||
|
||||
var aggHealStateResult madmin.BgHealState
|
||||
// Check if disks are healing on in-case of VMware vsphere deployments.
|
||||
if opts.Maintenance && opts.DeploymentType == vmware {
|
||||
// check if local disks are being healed, if they are being healed
|
||||
// we need to tell healthy status as 'false' so that this server
|
||||
// is not taken down for maintenance
|
||||
var err error
|
||||
aggHealStateResult, err = getAggregatedBackgroundHealState(ctx, nil)
|
||||
if err != nil {
|
||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo), fmt.Errorf("Unable to verify global heal status: %w", err))
|
||||
return HealthResult{
|
||||
Healthy: false,
|
||||
}
|
||||
}
|
||||
|
||||
if len(aggHealStateResult.HealDisks) > 0 {
|
||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo), fmt.Errorf("Total drives to be healed %d", len(aggHealStateResult.HealDisks)))
|
||||
if drivesHealing > 0 {
|
||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo), fmt.Errorf("Total drives to be healed %d", drivesHealing))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2389,18 +2402,19 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
for poolIdx := range erasureSetUpCount {
|
||||
for setIdx := range erasureSetUpCount[poolIdx] {
|
||||
result.ESHealth = append(result.ESHealth, struct {
|
||||
Maintenance bool
|
||||
PoolID, SetID int
|
||||
HealthyDrives, WriteQuorum int
|
||||
Maintenance bool
|
||||
PoolID, SetID int
|
||||
HealthyDrives, HealingDrives, WriteQuorum int
|
||||
}{
|
||||
Maintenance: opts.Maintenance,
|
||||
SetID: setIdx,
|
||||
PoolID: poolIdx,
|
||||
HealthyDrives: erasureSetUpCount[poolIdx][setIdx],
|
||||
HealthyDrives: erasureSetUpCount[poolIdx][setIdx].online,
|
||||
HealingDrives: erasureSetUpCount[poolIdx][setIdx].healing,
|
||||
WriteQuorum: poolWriteQuorums[poolIdx],
|
||||
})
|
||||
|
||||
if erasureSetUpCount[poolIdx][setIdx] < poolWriteQuorums[poolIdx] {
|
||||
if erasureSetUpCount[poolIdx][setIdx].online < poolWriteQuorums[poolIdx] {
|
||||
logger.LogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||
fmt.Errorf("Write quorum may be lost on pool: %d, set: %d, expected write quorum: %d",
|
||||
poolIdx, setIdx, poolWriteQuorums[poolIdx]))
|
||||
@@ -2410,8 +2424,8 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
}
|
||||
|
||||
if opts.Maintenance {
|
||||
result.Healthy = result.Healthy && len(aggHealStateResult.HealDisks) == 0
|
||||
result.HealingDrives = len(aggHealStateResult.HealDisks)
|
||||
result.Healthy = result.Healthy && drivesHealing == 0
|
||||
result.HealingDrives = drivesHealing
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
+6
-20
@@ -36,7 +36,6 @@ import (
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio-go/v7/pkg/tags"
|
||||
"github.com/minio/minio/internal/bpool"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/console"
|
||||
@@ -262,6 +261,10 @@ func (s *erasureSets) connectDisks() {
|
||||
disk.SetDiskLoc(s.poolIndex, setIndex, diskIndex)
|
||||
s.erasureDisks[setIndex][diskIndex] = disk
|
||||
s.erasureDisksMu.Unlock()
|
||||
|
||||
globalLocalDrivesMu.Lock()
|
||||
globalLocalSetDrives[s.poolIndex][setIndex][diskIndex] = disk
|
||||
globalLocalDrivesMu.Unlock()
|
||||
}(endpoint)
|
||||
}
|
||||
|
||||
@@ -365,21 +368,6 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
||||
|
||||
mutex := newNSLock(globalIsDistErasure)
|
||||
|
||||
// Number of buffers, max 2GB
|
||||
n := (2 * humanize.GiByte) / (blockSizeV2 * 2)
|
||||
|
||||
// Initialize byte pool once for all sets, bpool size is set to
|
||||
// setCount * setDriveCount with each memory upto blockSizeV2.
|
||||
bp := bpool.NewBytePoolCap(n, blockSizeV2, blockSizeV2*2)
|
||||
|
||||
// Initialize byte pool for all sets, bpool size is set to
|
||||
// setCount * setDriveCount with each memory upto blockSizeV1
|
||||
//
|
||||
// Number of buffers, max 10GiB
|
||||
m := (10 * humanize.GiByte) / (blockSizeV1 * 2)
|
||||
|
||||
bpOld := bpool.NewBytePoolCap(m, blockSizeV1, blockSizeV1*2)
|
||||
|
||||
for i := 0; i < setCount; i++ {
|
||||
s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
|
||||
}
|
||||
@@ -455,8 +443,6 @@ func newErasureSets(ctx context.Context, endpoints PoolEndpoints, storageDisks [
|
||||
getLockers: s.GetLockers(i),
|
||||
getEndpoints: s.GetEndpoints(i),
|
||||
nsMutex: mutex,
|
||||
bp: bp,
|
||||
bpOld: bpOld,
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
@@ -611,7 +597,7 @@ func (s *erasureSets) StorageInfo(ctx context.Context) StorageInfo {
|
||||
}
|
||||
|
||||
// StorageInfo - combines output of StorageInfo across all erasure coded object sets.
|
||||
func (s *erasureSets) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
func (s *erasureSets) LocalStorageInfo(ctx context.Context, metrics bool) StorageInfo {
|
||||
var storageInfo StorageInfo
|
||||
|
||||
storageInfos := make([]StorageInfo, len(s.sets))
|
||||
@@ -620,7 +606,7 @@ func (s *erasureSets) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
for index := range s.sets {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
storageInfos[index] = s.sets[index].LocalStorageInfo(ctx)
|
||||
storageInfos[index] = s.sets[index].LocalStorageInfo(ctx, metrics)
|
||||
return nil
|
||||
}, index)
|
||||
}
|
||||
|
||||
@@ -72,15 +72,15 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
|
||||
// Decrement offset.
|
||||
offset -= int64(len(block))
|
||||
continue
|
||||
} else {
|
||||
// Skip until offset.
|
||||
block = block[offset:]
|
||||
|
||||
// Reset the offset for next iteration to read everything
|
||||
// from subsequent blocks.
|
||||
offset = 0
|
||||
}
|
||||
|
||||
// Skip until offset.
|
||||
block = block[offset:]
|
||||
|
||||
// Reset the offset for next iteration to read everything
|
||||
// from subsequent blocks.
|
||||
offset = 0
|
||||
|
||||
// We have written all the blocks, write the last remaining block.
|
||||
if write < int64(len(block)) {
|
||||
n, err := dst.Write(block[:write])
|
||||
|
||||
+7
-15
@@ -29,7 +29,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bpool"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
@@ -65,13 +64,6 @@ type erasureObjects struct {
|
||||
|
||||
// Locker mutex map.
|
||||
nsMutex *nsLockMap
|
||||
|
||||
// Byte pools used for temporary i/o buffers.
|
||||
bp *bpool.BytePoolCap
|
||||
|
||||
// Byte pools used for temporary i/o buffers,
|
||||
// legacy objects.
|
||||
bpOld *bpool.BytePoolCap
|
||||
}
|
||||
|
||||
// NewNSLock - initialize a new namespace RWLocker instance.
|
||||
@@ -168,7 +160,7 @@ func getOnlineOfflineDisksStats(disksInfo []madmin.Disk) (onlineDisks, offlineDi
|
||||
}
|
||||
|
||||
// getDisksInfo - fetch disks info across all other storage API.
|
||||
func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.Disk) {
|
||||
func getDisksInfo(disks []StorageAPI, endpoints []Endpoint, metrics bool) (disksInfo []madmin.Disk) {
|
||||
disksInfo = make([]madmin.Disk, len(disks))
|
||||
|
||||
g := errgroup.WithNErrs(len(disks))
|
||||
@@ -186,7 +178,7 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
|
||||
disksInfo[index] = di
|
||||
return nil
|
||||
}
|
||||
info, err := disks[index].DiskInfo(context.TODO(), true)
|
||||
info, err := disks[index].DiskInfo(context.TODO(), metrics)
|
||||
di.DrivePath = info.MountPath
|
||||
di.TotalSpace = info.Total
|
||||
di.UsedSpace = info.Used
|
||||
@@ -233,8 +225,8 @@ func getDisksInfo(disks []StorageAPI, endpoints []Endpoint) (disksInfo []madmin.
|
||||
}
|
||||
|
||||
// Get an aggregated storage info across all disks.
|
||||
func getStorageInfo(disks []StorageAPI, endpoints []Endpoint) StorageInfo {
|
||||
disksInfo := getDisksInfo(disks, endpoints)
|
||||
func getStorageInfo(disks []StorageAPI, endpoints []Endpoint, metrics bool) StorageInfo {
|
||||
disksInfo := getDisksInfo(disks, endpoints, metrics)
|
||||
|
||||
// Sort so that the first element is the smallest.
|
||||
sort.Slice(disksInfo, func(i, j int) bool {
|
||||
@@ -253,11 +245,11 @@ func getStorageInfo(disks []StorageAPI, endpoints []Endpoint) StorageInfo {
|
||||
func (er erasureObjects) StorageInfo(ctx context.Context) StorageInfo {
|
||||
disks := er.getDisks()
|
||||
endpoints := er.getEndpoints()
|
||||
return getStorageInfo(disks, endpoints)
|
||||
return getStorageInfo(disks, endpoints, true)
|
||||
}
|
||||
|
||||
// LocalStorageInfo - returns underlying local storage statistics.
|
||||
func (er erasureObjects) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
func (er erasureObjects) LocalStorageInfo(ctx context.Context, metrics bool) StorageInfo {
|
||||
disks := er.getDisks()
|
||||
endpoints := er.getEndpoints()
|
||||
|
||||
@@ -271,7 +263,7 @@ func (er erasureObjects) LocalStorageInfo(ctx context.Context) StorageInfo {
|
||||
}
|
||||
}
|
||||
|
||||
return getStorageInfo(localDisks, localEndpoints)
|
||||
return getStorageInfo(localDisks, localEndpoints, metrics)
|
||||
}
|
||||
|
||||
// getOnlineDisksWithHealing - returns online disks and overall healing status.
|
||||
|
||||
+67
-48
@@ -248,12 +248,19 @@ func (driver *ftpDriver) CheckPasswd(c *ftp.Context, username, password string)
|
||||
defer stopFn(err)
|
||||
|
||||
if globalIAMSys.LDAPConfig.Enabled() {
|
||||
ldapUserDN, groupDistNames, err := globalIAMSys.LDAPConfig.Bind(username, password)
|
||||
if err != nil {
|
||||
sa, _, err := globalIAMSys.getServiceAccount(context.Background(), username)
|
||||
if err != nil && !errors.Is(err, errNoSuchServiceAccount) {
|
||||
return false, err
|
||||
}
|
||||
ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, groupDistNames...)
|
||||
return len(ldapPolicies) > 0, nil
|
||||
if errors.Is(err, errNoSuchServiceAccount) {
|
||||
ldapUserDN, groupDistNames, err := globalIAMSys.LDAPConfig.Bind(username, password)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
ldapPolicies, _ := globalIAMSys.PolicyDBGet(ldapUserDN, groupDistNames...)
|
||||
return len(ldapPolicies) > 0, nil
|
||||
}
|
||||
return subtle.ConstantTimeCompare([]byte(sa.Credentials.SecretKey), []byte(password)) == 1, nil
|
||||
}
|
||||
|
||||
ui, ok := globalIAMSys.GetUser(context.Background(), username)
|
||||
@@ -269,58 +276,70 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
|
||||
return nil, errNoSuchUser
|
||||
}
|
||||
if !ok && globalIAMSys.LDAPConfig.Enabled() {
|
||||
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(ctx.Sess.LoginUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...)
|
||||
if len(ldapPolicies) == 0 {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claims := make(map[string]interface{})
|
||||
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
|
||||
claims[ldapUser] = targetUser
|
||||
claims[ldapUserN] = ctx.Sess.LoginUser()
|
||||
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
|
||||
if err != nil {
|
||||
sa, _, err := globalIAMSys.getServiceAccount(context.Background(), ctx.Sess.LoginUser())
|
||||
if err != nil && !errors.Is(err, errNoSuchServiceAccount) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the parent of the temporary access key, this is useful
|
||||
// in obtaining service accounts by this cred.
|
||||
cred.ParentUser = targetUser
|
||||
var mcreds *credentials.Credentials
|
||||
if errors.Is(err, errNoSuchServiceAccount) {
|
||||
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(ctx.Sess.LoginUser())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...)
|
||||
if len(ldapPolicies) == 0 {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claims := make(map[string]interface{})
|
||||
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
|
||||
claims[ldapUser] = targetUser
|
||||
claims[ldapUserN] = ctx.Sess.LoginUser()
|
||||
|
||||
// Set this value to LDAP groups, LDAP user can be part
|
||||
// of large number of groups
|
||||
cred.Groups = targetGroups
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the newly generated credentials, policyName is empty on purpose
|
||||
// LDAP policies are applied automatically using their ldapUser, ldapGroups
|
||||
// mapping.
|
||||
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Set the parent of the temporary access key, this is useful
|
||||
// in obtaining service accounts by this cred.
|
||||
cred.ParentUser = targetUser
|
||||
|
||||
// Set this value to LDAP groups, LDAP user can be part
|
||||
// of large number of groups
|
||||
cred.Groups = targetGroups
|
||||
|
||||
// Set the newly generated credentials, policyName is empty on purpose
|
||||
// LDAP policies are applied automatically using their ldapUser, ldapGroups
|
||||
// mapping.
|
||||
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
SecretKey: cred.SecretKey,
|
||||
SessionToken: cred.SessionToken,
|
||||
ParentUser: cred.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
|
||||
mcreds = credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken)
|
||||
} else {
|
||||
mcreds = credentials.NewStaticV4(sa.Credentials.AccessKey, sa.Credentials.SecretKey, "")
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
SecretKey: cred.SecretKey,
|
||||
SessionToken: cred.SessionToken,
|
||||
ParentUser: cred.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
|
||||
return minio.New(driver.endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Creds: mcreds,
|
||||
Secure: globalIsTLS,
|
||||
Transport: globalRemoteFTPClientTransport,
|
||||
})
|
||||
|
||||
+1
-4
@@ -23,7 +23,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
ftp "goftp.io/server/v2"
|
||||
)
|
||||
@@ -69,9 +68,7 @@ func (log *minioLogger) PrintResponse(sessionID string, code int, message string
|
||||
}
|
||||
}
|
||||
|
||||
func startFTPServer(c *cli.Context) {
|
||||
args := c.StringSlice("ftp")
|
||||
|
||||
func startFTPServer(args []string) {
|
||||
var (
|
||||
port int
|
||||
publicIP string
|
||||
|
||||
@@ -153,7 +153,7 @@ func setBrowserRedirectMiddleware(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
read := r.Method == http.MethodGet || r.Method == http.MethodHead
|
||||
// Re-direction is handled specifically for browser requests.
|
||||
if guessIsBrowserReq(r) && read && globalBrowserRedirect {
|
||||
if !guessIsHealthCheckReq(r) && guessIsBrowserReq(r) && read && globalBrowserRedirect {
|
||||
// Fetch the redirect location if any.
|
||||
if u := getRedirectLocation(r); u != nil {
|
||||
// Employ a temporary re-direct.
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ func getLocalBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.Bg
|
||||
return status, true
|
||||
}
|
||||
|
||||
si := o.LocalStorageInfo(ctx)
|
||||
si := o.LocalStorageInfo(ctx, true)
|
||||
|
||||
indexed := make(map[string][]madmin.Disk)
|
||||
for _, disk := range si.Disks {
|
||||
|
||||
+41
-5
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/bpool"
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
@@ -127,13 +128,47 @@ const (
|
||||
tlsClientSessionCacheSize = 100
|
||||
)
|
||||
|
||||
var globalCLIContext = struct {
|
||||
JSON, Quiet bool
|
||||
Anonymous bool
|
||||
StrictS3Compat bool
|
||||
}{}
|
||||
type poolDisksLayout struct {
|
||||
cmdline string
|
||||
layout [][]string
|
||||
}
|
||||
|
||||
type disksLayout struct {
|
||||
legacy bool
|
||||
pools []poolDisksLayout
|
||||
}
|
||||
|
||||
type serverCtxt struct {
|
||||
JSON, Quiet bool
|
||||
Anonymous bool
|
||||
StrictS3Compat bool
|
||||
PreAllocate bool
|
||||
Addr, ConsoleAddr string
|
||||
ConfigDir, CertsDir string
|
||||
configDirSet, certsDirSet bool
|
||||
Interface string
|
||||
|
||||
RootUser, RootPwd string
|
||||
|
||||
FTP []string
|
||||
SFTP []string
|
||||
|
||||
UserTimeout time.Duration
|
||||
ConnReadDeadline time.Duration
|
||||
ConnWriteDeadline time.Duration
|
||||
|
||||
ShutdownTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
ReadHeaderTimeout time.Duration
|
||||
|
||||
// The layout of disks as interpreted
|
||||
Layout disksLayout
|
||||
}
|
||||
|
||||
var (
|
||||
// Global user opts context
|
||||
globalServerCtxt serverCtxt
|
||||
|
||||
// Indicates if the running minio server is distributed setup.
|
||||
globalIsDistErasure = false
|
||||
|
||||
@@ -192,6 +227,7 @@ var (
|
||||
globalBucketMonitor *bandwidth.Monitor
|
||||
globalPolicySys *PolicySys
|
||||
globalIAMSys *IAMSys
|
||||
globalBytePoolCap *bpool.BytePoolCap
|
||||
|
||||
globalLifecycleSys *LifecycleSys
|
||||
globalBucketSSEConfigSys *BucketSSEConfigSys
|
||||
|
||||
+16
-8
@@ -42,9 +42,10 @@ type apiConfig struct {
|
||||
listQuorum string
|
||||
corsAllowOrigins []string
|
||||
// total drives per erasure set across pools.
|
||||
totalDriveCount int
|
||||
replicationPriority string
|
||||
transitionWorkers int
|
||||
totalDriveCount int
|
||||
replicationPriority string
|
||||
replicationMaxWorkers int
|
||||
transitionWorkers int
|
||||
|
||||
staleUploadsExpiry time.Duration
|
||||
staleUploadsCleanupInterval time.Duration
|
||||
@@ -152,10 +153,11 @@ func (t *apiConfig) init(cfg api.Config, setDriveCounts []int) {
|
||||
}
|
||||
t.listQuorum = listQuorum
|
||||
if globalReplicationPool != nil &&
|
||||
cfg.ReplicationPriority != t.replicationPriority {
|
||||
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority)
|
||||
(cfg.ReplicationPriority != t.replicationPriority || cfg.ReplicationMaxWorkers != t.replicationMaxWorkers) {
|
||||
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority, cfg.ReplicationMaxWorkers)
|
||||
}
|
||||
t.replicationPriority = cfg.ReplicationPriority
|
||||
t.replicationMaxWorkers = cfg.ReplicationMaxWorkers
|
||||
if globalTransitionState != nil && cfg.TransitionWorkers != t.transitionWorkers {
|
||||
globalTransitionState.UpdateWorkers(cfg.TransitionWorkers)
|
||||
}
|
||||
@@ -334,15 +336,21 @@ func maxClients(f http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *apiConfig) getReplicationPriority() string {
|
||||
func (t *apiConfig) getReplicationOpts() replicationPoolOpts {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
|
||||
if t.replicationPriority == "" {
|
||||
return "auto"
|
||||
return replicationPoolOpts{
|
||||
Priority: "auto",
|
||||
MaxWorkers: WorkerMaxLimit,
|
||||
}
|
||||
}
|
||||
|
||||
return t.replicationPriority
|
||||
return replicationPoolOpts{
|
||||
Priority: t.replicationPriority,
|
||||
MaxWorkers: t.replicationMaxWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *apiConfig) getTransitionWorkers() int {
|
||||
|
||||
@@ -2319,6 +2319,27 @@ func (store *IAMStoreSys) ListServiceAccounts(ctx context.Context, accessKey str
|
||||
return serviceAccounts, nil
|
||||
}
|
||||
|
||||
// ListSTSAccounts - lists only STS accounts from the cache.
|
||||
func (store *IAMStoreSys) ListSTSAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
||||
cache := store.rlock()
|
||||
defer store.runlock()
|
||||
|
||||
var stsAccounts []auth.Credentials
|
||||
for _, u := range cache.iamSTSAccountsMap {
|
||||
v := u.Credentials
|
||||
if accessKey != "" && v.ParentUser == accessKey {
|
||||
if v.IsTemp() {
|
||||
// Hide secret key & session key here
|
||||
v.SecretKey = ""
|
||||
v.SessionToken = ""
|
||||
stsAccounts = append(stsAccounts, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stsAccounts, nil
|
||||
}
|
||||
|
||||
// AddUser - adds/updates long term user account to storage.
|
||||
func (store *IAMStoreSys) AddUser(ctx context.Context, accessKey string, ureq madmin.AddOrUpdateUserReq) (updatedAt time.Time, err error) {
|
||||
cache := store.lock()
|
||||
|
||||
+16
-2
@@ -1041,7 +1041,7 @@ func (sys *IAMSys) UpdateServiceAccount(ctx context.Context, accessKey string, o
|
||||
return updatedAt, nil
|
||||
}
|
||||
|
||||
// ListServiceAccounts - lists all services accounts associated to a specific user
|
||||
// ListServiceAccounts - lists all service accounts associated to a specific user
|
||||
func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
@@ -1055,7 +1055,7 @@ func (sys *IAMSys) ListServiceAccounts(ctx context.Context, accessKey string) ([
|
||||
}
|
||||
}
|
||||
|
||||
// ListTempAccounts - lists all services accounts associated to a specific user
|
||||
// ListTempAccounts - lists all temporary service accounts associated to a specific user
|
||||
func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]UserIdentity, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
@@ -1069,6 +1069,20 @@ func (sys *IAMSys) ListTempAccounts(ctx context.Context, accessKey string) ([]Us
|
||||
}
|
||||
}
|
||||
|
||||
// ListSTSAccounts - lists all STS accounts associated to a specific user
|
||||
func (sys *IAMSys) ListSTSAccounts(ctx context.Context, accessKey string) ([]auth.Credentials, error) {
|
||||
if !sys.Initialized() {
|
||||
return nil, errServerNotInitialized
|
||||
}
|
||||
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
return sys.store.ListSTSAccounts(ctx, accessKey)
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// GetServiceAccount - wrapper method to get information about a service account
|
||||
func (sys *IAMSys) GetServiceAccount(ctx context.Context, accessKey string) (auth.Credentials, *policy.Policy, error) {
|
||||
sa, embeddedPolicy, err := sys.getServiceAccount(ctx, accessKey)
|
||||
|
||||
@@ -89,7 +89,7 @@ func performLicenseUpdate(ctx context.Context, objectAPI ObjectLayer) {
|
||||
return
|
||||
}
|
||||
|
||||
r := gjson.Parse(resp).Get("license")
|
||||
r := gjson.Parse(resp).Get("license_v2")
|
||||
if r.Index == 0 {
|
||||
logger.LogIf(ctx, fmt.Errorf("license not found in response from %s", url))
|
||||
return
|
||||
|
||||
@@ -91,7 +91,7 @@ func collectLocalMetrics(types madmin.MetricType, opts collectMetricsOpts) (m ma
|
||||
}
|
||||
cm, err := c.Times(false)
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (cputimes)", globalMinioAddr, err.Error()))
|
||||
m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (cpuTimes)", globalMinioAddr, err.Error()))
|
||||
} else {
|
||||
// not collecting per-cpu stats, so there will be only one element
|
||||
if len(cm) == 1 {
|
||||
@@ -100,6 +100,13 @@ func collectLocalMetrics(types madmin.MetricType, opts collectMetricsOpts) (m ma
|
||||
m.Errors = append(m.Errors, fmt.Sprintf("%s: Expected one CPU stat, got %d", globalMinioAddr, len(cm)))
|
||||
}
|
||||
}
|
||||
cpuCount, err := c.Counts(true)
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (cpuCount)", globalMinioAddr, err.Error()))
|
||||
} else {
|
||||
m.Aggregated.CPU.CPUCount = cpuCount
|
||||
}
|
||||
|
||||
loadStat, err := load.Avg()
|
||||
if err != nil {
|
||||
m.Errors = append(m.Errors, fmt.Sprintf("%s: %v (loadStat)", globalMinioAddr, err.Error()))
|
||||
@@ -129,7 +136,7 @@ func collectLocalDisksMetrics(disks map[string]struct{}) map[string]madmin.DiskM
|
||||
return metrics
|
||||
}
|
||||
|
||||
storageInfo := objLayer.LocalStorageInfo(GlobalContext)
|
||||
storageInfo := objLayer.LocalStorageInfo(GlobalContext, true)
|
||||
for _, d := range storageInfo.Disks {
|
||||
if len(disks) != 0 {
|
||||
_, ok := disks[d.Endpoint]
|
||||
|
||||
+29
-11
@@ -53,6 +53,7 @@ const (
|
||||
|
||||
// memory stats
|
||||
memUsed MetricName = "used"
|
||||
memUsedPerc MetricName = "used_perc"
|
||||
memFree MetricName = "free"
|
||||
memShared MetricName = "shared"
|
||||
memBuffers MetricName = "buffers"
|
||||
@@ -60,15 +61,18 @@ const (
|
||||
memAvailable MetricName = "available"
|
||||
|
||||
// cpu stats
|
||||
cpuUser MetricName = "user"
|
||||
cpuSystem MetricName = "system"
|
||||
cpuIOWait MetricName = "iowait"
|
||||
cpuIdle MetricName = "idle"
|
||||
cpuNice MetricName = "nice"
|
||||
cpuSteal MetricName = "steal"
|
||||
cpuLoad1 MetricName = "load1"
|
||||
cpuLoad5 MetricName = "load5"
|
||||
cpuLoad15 MetricName = "load15"
|
||||
cpuUser MetricName = "user"
|
||||
cpuSystem MetricName = "system"
|
||||
cpuIOWait MetricName = "iowait"
|
||||
cpuIdle MetricName = "idle"
|
||||
cpuNice MetricName = "nice"
|
||||
cpuSteal MetricName = "steal"
|
||||
cpuLoad1 MetricName = "load1"
|
||||
cpuLoad5 MetricName = "load5"
|
||||
cpuLoad15 MetricName = "load15"
|
||||
cpuLoad1Perc MetricName = "load1_perc"
|
||||
cpuLoad5Perc MetricName = "load5_perc"
|
||||
cpuLoad15Perc MetricName = "load15_perc"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -126,6 +130,7 @@ func init() {
|
||||
interfaceTxErrors: "Transmit errors in " + interval,
|
||||
total: "Total memory on the node",
|
||||
memUsed: "Used memory on the node",
|
||||
memUsedPerc: "Used memory percentage on the node",
|
||||
memFree: "Free memory on the node",
|
||||
memShared: "Shared memory on the node",
|
||||
memBuffers: "Buffers memory on the node",
|
||||
@@ -151,6 +156,9 @@ func init() {
|
||||
cpuLoad1: "CPU load average 1min",
|
||||
cpuLoad5: "CPU load average 5min",
|
||||
cpuLoad15: "CPU load average 15min",
|
||||
cpuLoad1Perc: "CPU load average 1min (perentage)",
|
||||
cpuLoad5Perc: "CPU load average 5min (percentage)",
|
||||
cpuLoad15Perc: "CPU load average 15min (percentage)",
|
||||
}
|
||||
resourceMetricsGroups = []*MetricsGroup{
|
||||
getResourceMetrics(),
|
||||
@@ -244,10 +252,10 @@ func collectDriveMetrics(m madmin.RealtimeMetrics) {
|
||||
}
|
||||
|
||||
globalLocalDrivesMu.RLock()
|
||||
gld := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
for _, d := range gld {
|
||||
for _, d := range localDrives {
|
||||
labels := map[string]string{"drive": d.Endpoint().RawPath}
|
||||
di, err := d.DiskInfo(GlobalContext, false)
|
||||
if err == nil {
|
||||
@@ -283,6 +291,8 @@ func collectLocalResourceMetrics() {
|
||||
stats := hm.Mem.Info
|
||||
updateResourceMetrics(memSubsystem, total, float64(stats.Total), labels, false)
|
||||
updateResourceMetrics(memSubsystem, memUsed, float64(stats.Used), labels, false)
|
||||
perc := math.Round(float64(stats.Used*100*100)/float64(stats.Total)) / 100
|
||||
updateResourceMetrics(memSubsystem, memUsedPerc, perc, labels, false)
|
||||
updateResourceMetrics(memSubsystem, memFree, float64(stats.Free), labels, false)
|
||||
updateResourceMetrics(memSubsystem, memShared, float64(stats.Shared), labels, false)
|
||||
updateResourceMetrics(memSubsystem, memBuffers, float64(stats.Buffers), labels, false)
|
||||
@@ -312,6 +322,14 @@ func collectLocalResourceMetrics() {
|
||||
updateResourceMetrics(cpuSubsystem, cpuLoad1, ls.Load1, labels, false)
|
||||
updateResourceMetrics(cpuSubsystem, cpuLoad5, ls.Load5, labels, false)
|
||||
updateResourceMetrics(cpuSubsystem, cpuLoad15, ls.Load15, labels, false)
|
||||
if hm.CPU.CPUCount > 0 {
|
||||
perc := math.Round(ls.Load1*100*100/float64(hm.CPU.CPUCount)) / 100
|
||||
updateResourceMetrics(cpuSubsystem, cpuLoad1Perc, perc, labels, false)
|
||||
perc = math.Round(ls.Load5*100*100/float64(hm.CPU.CPUCount)) / 100
|
||||
updateResourceMetrics(cpuSubsystem, cpuLoad5Perc, perc, labels, false)
|
||||
perc = math.Round(ls.Load15*100*100/float64(hm.CPU.CPUCount)) / 100
|
||||
updateResourceMetrics(cpuSubsystem, cpuLoad15Perc, perc, labels, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
break // only one host expected
|
||||
|
||||
+147
-88
@@ -90,6 +90,7 @@ func init() {
|
||||
getNetworkMetrics(),
|
||||
getMinioVersionMetrics(),
|
||||
getS3TTFBMetric(),
|
||||
getTierMetrics(),
|
||||
getNotificationMetrics(),
|
||||
getDistLockMetrics(),
|
||||
getIAMNodeMetrics(),
|
||||
@@ -155,6 +156,7 @@ const (
|
||||
usageSubsystem MetricSubsystem = "usage"
|
||||
quotaSubsystem MetricSubsystem = "quota"
|
||||
ilmSubsystem MetricSubsystem = "ilm"
|
||||
tierSubsystem MetricSubsystem = "tier"
|
||||
scannerSubsystem MetricSubsystem = "scanner"
|
||||
iamSubsystem MetricSubsystem = "iam"
|
||||
kmsSubsystem MetricSubsystem = "kms"
|
||||
@@ -246,6 +248,7 @@ const (
|
||||
sizeDistribution = "size_distribution"
|
||||
versionDistribution = "version_distribution"
|
||||
ttfbDistribution = "seconds_distribution"
|
||||
ttlbDistribution = "ttlb_seconds_distribution"
|
||||
|
||||
lastActivityTime = "last_activity_nano_seconds"
|
||||
startTime = "starttime_seconds"
|
||||
@@ -262,6 +265,9 @@ const (
|
||||
transitionedObjects MetricName = "transitioned_objects"
|
||||
transitionedVersions MetricName = "transitioned_versions"
|
||||
|
||||
tierRequestsSuccess MetricName = "requests_success"
|
||||
tierRequestsFailure MetricName = "requests_failure"
|
||||
|
||||
kmsOnline = "online"
|
||||
kmsRequestsSuccess = "request_success"
|
||||
kmsRequestsError = "request_error"
|
||||
@@ -1598,47 +1604,52 @@ func getGoMetrics() *MetricsGroup {
|
||||
return mg
|
||||
}
|
||||
|
||||
// getHistogramMetrics fetches histogram metrics and returns it in a []Metric
|
||||
// Note: Typically used in MetricGroup.RegisterRead
|
||||
func getHistogramMetrics(hist *prometheus.HistogramVec, desc MetricDescription) []Metric {
|
||||
ch := make(chan prometheus.Metric)
|
||||
go func() {
|
||||
defer close(ch)
|
||||
// Collects prometheus metrics from hist and sends it over ch
|
||||
hist.Collect(ch)
|
||||
}()
|
||||
|
||||
// Converts metrics received into internal []Metric type
|
||||
var metrics []Metric
|
||||
for promMetric := range ch {
|
||||
dtoMetric := &dto.Metric{}
|
||||
err := promMetric.Write(dtoMetric)
|
||||
if err != nil {
|
||||
// Log error and continue to receive other metric
|
||||
// values
|
||||
logger.LogIf(GlobalContext, err)
|
||||
continue
|
||||
}
|
||||
|
||||
h := dtoMetric.GetHistogram()
|
||||
for _, b := range h.Bucket {
|
||||
labels := make(map[string]string)
|
||||
for _, lp := range dtoMetric.GetLabel() {
|
||||
labels[*lp.Name] = *lp.Value
|
||||
}
|
||||
labels["le"] = fmt.Sprintf("%.3f", *b.UpperBound)
|
||||
metric := Metric{
|
||||
Description: desc,
|
||||
VariableLabels: labels,
|
||||
Value: float64(b.GetCumulativeCount()),
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
func getBucketTTFBMetric() *MetricsGroup {
|
||||
mg := &MetricsGroup{
|
||||
cacheInterval: 10 * time.Second,
|
||||
}
|
||||
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
|
||||
// Read prometheus metric on this channel
|
||||
ch := make(chan prometheus.Metric)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Read prometheus histogram data and convert it to internal metric data
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for promMetric := range ch {
|
||||
dtoMetric := &dto.Metric{}
|
||||
err := promMetric.Write(dtoMetric)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return
|
||||
}
|
||||
h := dtoMetric.GetHistogram()
|
||||
for _, b := range h.Bucket {
|
||||
labels := make(map[string]string)
|
||||
for _, lp := range dtoMetric.GetLabel() {
|
||||
labels[*lp.Name] = *lp.Value
|
||||
}
|
||||
labels["le"] = fmt.Sprintf("%.3f", *b.UpperBound)
|
||||
metric := Metric{
|
||||
Description: getBucketTTFBDistributionMD(),
|
||||
VariableLabels: labels,
|
||||
Value: float64(b.GetCumulativeCount()),
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
bucketHTTPRequestsDuration.Collect(ch)
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
return
|
||||
mg.RegisterRead(func(ctx context.Context) []Metric {
|
||||
return getHistogramMetrics(bucketHTTPRequestsDuration, getBucketTTFBDistributionMD())
|
||||
})
|
||||
return mg
|
||||
}
|
||||
@@ -1647,43 +1658,18 @@ func getS3TTFBMetric() *MetricsGroup {
|
||||
mg := &MetricsGroup{
|
||||
cacheInterval: 10 * time.Second,
|
||||
}
|
||||
mg.RegisterRead(func(ctx context.Context) (metrics []Metric) {
|
||||
// Read prometheus metric on this channel
|
||||
ch := make(chan prometheus.Metric)
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
mg.RegisterRead(func(ctx context.Context) []Metric {
|
||||
return getHistogramMetrics(httpRequestsDuration, getS3TTFBDistributionMD())
|
||||
})
|
||||
return mg
|
||||
}
|
||||
|
||||
// Read prometheus histogram data and convert it to internal metric data
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for promMetric := range ch {
|
||||
dtoMetric := &dto.Metric{}
|
||||
err := promMetric.Write(dtoMetric)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return
|
||||
}
|
||||
h := dtoMetric.GetHistogram()
|
||||
for _, b := range h.Bucket {
|
||||
labels := make(map[string]string)
|
||||
for _, lp := range dtoMetric.GetLabel() {
|
||||
labels[*lp.Name] = *lp.Value
|
||||
}
|
||||
labels["le"] = fmt.Sprintf("%.3f", *b.UpperBound)
|
||||
metric := Metric{
|
||||
Description: getS3TTFBDistributionMD(),
|
||||
VariableLabels: labels,
|
||||
Value: float64(b.GetCumulativeCount()),
|
||||
}
|
||||
metrics = append(metrics, metric)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
httpRequestsDuration.Collect(ch)
|
||||
close(ch)
|
||||
wg.Wait()
|
||||
return
|
||||
func getTierMetrics() *MetricsGroup {
|
||||
mg := &MetricsGroup{
|
||||
cacheInterval: 10 * time.Second,
|
||||
}
|
||||
mg.RegisterRead(func(ctx context.Context) []Metric {
|
||||
return globalTierMetrics.Report()
|
||||
})
|
||||
return mg
|
||||
}
|
||||
@@ -2460,7 +2446,7 @@ func getNotificationMetrics() *MetricsGroup {
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "current_send_in_progress",
|
||||
Help: "Number of concurrent async Send calls active to all targets",
|
||||
Help: "Number of concurrent async Send calls active to all targets (deprecated, please use 'minio_notify_target_current_send_in_progress' instead)",
|
||||
Type: gaugeMetric,
|
||||
},
|
||||
Value: float64(nstats.CurrentSendCalls),
|
||||
@@ -2470,31 +2456,74 @@ func getNotificationMetrics() *MetricsGroup {
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "events_skipped_total",
|
||||
Help: "Events that were skipped due to full queue",
|
||||
Help: "Events that were skipped to be sent to the targets due to the in-memory queue being full",
|
||||
Type: counterMetric,
|
||||
},
|
||||
Value: float64(nstats.EventsSkipped),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: MetricDescription{
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "events_errors_total",
|
||||
Help: "Events that were failed to be sent to the targets (deprecated, please use 'minio_notify_target_failed_events' instead)",
|
||||
Type: counterMetric,
|
||||
},
|
||||
Value: float64(nstats.EventsErrorsTotal),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: MetricDescription{
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "events_sent_total",
|
||||
Help: "Total number of events sent since start",
|
||||
Help: "Total number of events sent to the targets (deprecated, please use 'minio_notify_target_total_events' instead)",
|
||||
Type: counterMetric,
|
||||
},
|
||||
Value: float64(nstats.TotalEvents),
|
||||
})
|
||||
for _, st := range nstats.TargetStats {
|
||||
for id, st := range nstats.TargetStats {
|
||||
metrics = append(metrics, Metric{
|
||||
Description: MetricDescription{
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "target_total_events",
|
||||
Help: "Total number of events sent (or) queued to the target",
|
||||
Type: counterMetric,
|
||||
},
|
||||
VariableLabels: map[string]string{"target_id": id.ID, "target_name": id.Name},
|
||||
Value: float64(st.TotalEvents),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: MetricDescription{
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "target_failed_events",
|
||||
Help: "Number of events failed to be sent (or) queued to the target",
|
||||
Type: counterMetric,
|
||||
},
|
||||
VariableLabels: map[string]string{"target_id": id.ID, "target_name": id.Name},
|
||||
Value: float64(st.FailedEvents),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: MetricDescription{
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "target_current_send_in_progress",
|
||||
Help: "Number of concurrent async Send calls active to the target",
|
||||
Type: gaugeMetric,
|
||||
},
|
||||
VariableLabels: map[string]string{"target_id": id.ID, "target_name": id.Name},
|
||||
Value: float64(st.CurrentSendCalls),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: MetricDescription{
|
||||
Namespace: minioNamespace,
|
||||
Subsystem: notifySubsystem,
|
||||
Name: "target_queue_length",
|
||||
Help: "Number of unsent notifications in queue for target",
|
||||
Help: "Number of events currently staged in the queue_dir configured for the target",
|
||||
Type: gaugeMetric,
|
||||
},
|
||||
VariableLabels: map[string]string{"target_id": st.ID.ID, "target_name": st.ID.Name},
|
||||
VariableLabels: map[string]string{"target_id": id.ID, "target_name": id.Name},
|
||||
Value: float64(st.CurrentQueue),
|
||||
})
|
||||
}
|
||||
@@ -3104,7 +3133,7 @@ func getLocalStorageMetrics() *MetricsGroup {
|
||||
}
|
||||
|
||||
metrics = make([]Metric, 0, 50)
|
||||
storageInfo := objLayer.LocalStorageInfo(ctx)
|
||||
storageInfo := objLayer.LocalStorageInfo(ctx, true)
|
||||
onlineDrives, offlineDrives := getOnlineOfflineDisksStats(storageInfo.Disks)
|
||||
totalDrives := onlineDrives.Merge(offlineDrives)
|
||||
|
||||
@@ -3206,12 +3235,32 @@ func getClusterHealthStatusMD() MetricDescription {
|
||||
}
|
||||
}
|
||||
|
||||
func getClusterErasureSetToleranceMD() MetricDescription {
|
||||
func getClusterErasureSetWriteQuorumMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: clusterMetricNamespace,
|
||||
Subsystem: "health",
|
||||
Name: "erasure_set_tolerance",
|
||||
Help: "Get erasure set tolerance status",
|
||||
Name: "erasure_set_write_quorum",
|
||||
Help: "Get the write quorum for this erasure set",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func getClusterErasureSetOnlineDrivesMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: clusterMetricNamespace,
|
||||
Subsystem: "health",
|
||||
Name: "erasure_set_online_drives",
|
||||
Help: "Get the count of the online drives in this erasure set",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
}
|
||||
|
||||
func getClusterErasureSetHealingDrivesMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: clusterMetricNamespace,
|
||||
Subsystem: "health",
|
||||
Name: "erasure_set_healing_drives",
|
||||
Help: "Get the count of healing drives of this erasure set",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
}
|
||||
@@ -3227,11 +3276,11 @@ func getClusterHealthMetrics() *MetricsGroup {
|
||||
return
|
||||
}
|
||||
|
||||
metrics = make([]Metric, 0, 2)
|
||||
|
||||
opts := HealthOptions{}
|
||||
result := objLayer.Health(ctx, opts)
|
||||
|
||||
metrics = make([]Metric, 0, 2+3*len(result.ESHealth))
|
||||
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getClusterWriteQuorumMD(),
|
||||
Value: float64(result.WriteQuorum),
|
||||
@@ -3253,9 +3302,19 @@ func getClusterHealthMetrics() *MetricsGroup {
|
||||
"set": strconv.Itoa(h.SetID),
|
||||
}
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getClusterErasureSetToleranceMD(),
|
||||
Description: getClusterErasureSetWriteQuorumMD(),
|
||||
VariableLabels: labels,
|
||||
Value: float64(h.HealthyDrives - h.WriteQuorum),
|
||||
Value: float64(h.WriteQuorum),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getClusterErasureSetOnlineDrivesMD(),
|
||||
VariableLabels: labels,
|
||||
Value: float64(h.HealthyDrives),
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: getClusterErasureSetHealingDrivesMD(),
|
||||
VariableLabels: labels,
|
||||
Value: float64(h.HealingDrives),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3349,7 +3408,7 @@ func getClusterStorageMetrics() *MetricsGroup {
|
||||
|
||||
// Fetch disk space info, ignore errors
|
||||
metrics = make([]Metric, 0, 10)
|
||||
storageInfo := objLayer.StorageInfo(ctx)
|
||||
storageInfo := objLayer.StorageInfo(ctx, true)
|
||||
onlineDrives, offlineDrives := getOnlineOfflineDisksStats(storageInfo.Disks)
|
||||
totalDrives := onlineDrives.Merge(offlineDrives)
|
||||
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestGetHistogramMetrics(t *testing.T) {
|
||||
histBuckets := []float64{0.05, 0.1, 0.25, 0.5, 0.75}
|
||||
labels := []string{"GetObject", "PutObject", "CopyObject", "CompleteMultipartUpload"}
|
||||
ttfbHist := prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "s3_ttfb_seconds",
|
||||
Help: "Time taken by requests served by current MinIO server instance",
|
||||
Buckets: histBuckets,
|
||||
},
|
||||
[]string{"api"},
|
||||
)
|
||||
observations := []struct {
|
||||
val float64
|
||||
label string
|
||||
}{
|
||||
{
|
||||
val: 0.02,
|
||||
label: labels[0],
|
||||
},
|
||||
{
|
||||
val: 0.07,
|
||||
label: labels[1],
|
||||
},
|
||||
{
|
||||
val: 0.11,
|
||||
label: labels[1],
|
||||
},
|
||||
{
|
||||
val: 0.19,
|
||||
label: labels[1],
|
||||
},
|
||||
{
|
||||
val: 0.31,
|
||||
label: labels[1],
|
||||
},
|
||||
{
|
||||
val: 0.61,
|
||||
label: labels[3],
|
||||
},
|
||||
{
|
||||
val: 0.79,
|
||||
label: labels[2],
|
||||
},
|
||||
}
|
||||
ticker := time.NewTicker(1 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for _, obs := range observations {
|
||||
// Send observations once every 1ms, to simulate delay between
|
||||
// observations. This is to test the channel based
|
||||
// synchronization used internally.
|
||||
select {
|
||||
case <-ticker.C:
|
||||
ttfbHist.With(prometheus.Labels{"api": obs.label}).Observe(obs.val)
|
||||
}
|
||||
}
|
||||
|
||||
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD())
|
||||
if expPoints := len(labels) * len(histBuckets); expPoints != len(metrics) {
|
||||
t.Fatalf("Expected %v data points but got %v", expPoints, len(metrics))
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -406,7 +406,7 @@ func storageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
float64(GetTotalCapacityFree(server.Disks)),
|
||||
)
|
||||
|
||||
sinfo := objLayer.StorageInfo(GlobalContext)
|
||||
sinfo := objLayer.StorageInfo(GlobalContext, true)
|
||||
|
||||
// Report total usable capacity
|
||||
ch <- prometheus.MustNewConstMetric(
|
||||
|
||||
+8
-3
@@ -38,6 +38,7 @@ type partialOperation struct {
|
||||
allVersions bool
|
||||
setIndex, poolIndex int
|
||||
queued time.Time
|
||||
scanMode madmin.HealScanMode
|
||||
}
|
||||
|
||||
// mrfState sncapsulates all the information
|
||||
@@ -103,13 +104,17 @@ func (m *mrfState) healRoutine() {
|
||||
// wait on timer per heal
|
||||
wait := healSleeper.Timer(context.Background())
|
||||
|
||||
scan := madmin.HealNormalScan
|
||||
if u.scanMode != 0 {
|
||||
scan = u.scanMode
|
||||
}
|
||||
if u.object == "" {
|
||||
healBucket(u.bucket, madmin.HealNormalScan)
|
||||
healBucket(u.bucket, scan)
|
||||
} else {
|
||||
if u.allVersions {
|
||||
m.pools.serverPools[u.poolIndex].sets[u.setIndex].listAndHeal(u.bucket, u.object, healObjectVersionsDisparity)
|
||||
m.pools.serverPools[u.poolIndex].sets[u.setIndex].listAndHeal(u.bucket, u.object, u.scanMode, healObjectVersionsDisparity)
|
||||
} else {
|
||||
healObject(u.bucket, u.object, u.versionID, madmin.HealNormalScan)
|
||||
healObject(u.bucket, u.object, u.versionID, scan)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -975,8 +975,11 @@ func getOfflineDisks(offlineHost string, endpoints EndpointServerPools) []madmin
|
||||
for _, ep := range pool.Endpoints {
|
||||
if offlineHost == "" && ep.IsLocal || offlineHost == ep.Host {
|
||||
offlineDisks = append(offlineDisks, madmin.Disk{
|
||||
Endpoint: ep.String(),
|
||||
State: string(madmin.ItemOffline),
|
||||
Endpoint: ep.String(),
|
||||
State: string(madmin.ItemOffline),
|
||||
PoolIndex: ep.PoolIdx,
|
||||
SetIndex: ep.SetIdx,
|
||||
DiskIndex: ep.DiskIdx,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -985,7 +988,7 @@ func getOfflineDisks(offlineHost string, endpoints EndpointServerPools) []madmin
|
||||
}
|
||||
|
||||
// StorageInfo returns disk information across all peers
|
||||
func (sys *NotificationSys) StorageInfo(objLayer ObjectLayer) StorageInfo {
|
||||
func (sys *NotificationSys) StorageInfo(objLayer ObjectLayer, metrics bool) StorageInfo {
|
||||
var storageInfo StorageInfo
|
||||
replies := make([]StorageInfo, len(sys.peerClients))
|
||||
|
||||
@@ -997,7 +1000,7 @@ func (sys *NotificationSys) StorageInfo(objLayer ObjectLayer) StorageInfo {
|
||||
wg.Add(1)
|
||||
go func(client *peerRESTClient, idx int) {
|
||||
defer wg.Done()
|
||||
info, err := client.LocalStorageInfo()
|
||||
info, err := client.LocalStorageInfo(metrics)
|
||||
if err != nil {
|
||||
info.Disks = getOfflineDisks(client.host.String(), globalEndpoints)
|
||||
}
|
||||
@@ -1007,7 +1010,7 @@ func (sys *NotificationSys) StorageInfo(objLayer ObjectLayer) StorageInfo {
|
||||
wg.Wait()
|
||||
|
||||
// Add local to this server.
|
||||
replies = append(replies, objLayer.LocalStorageInfo(GlobalContext))
|
||||
replies = append(replies, objLayer.LocalStorageInfo(GlobalContext, metrics))
|
||||
|
||||
storageInfo.Backend = objLayer.BackendInfo()
|
||||
for _, sinfo := range replies {
|
||||
|
||||
@@ -228,8 +228,8 @@ type ObjectLayer interface {
|
||||
Shutdown(context.Context) error
|
||||
NSScanner(ctx context.Context, updates chan<- DataUsageInfo, wantCycle uint32, scanMode madmin.HealScanMode) error
|
||||
BackendInfo() madmin.BackendInfo
|
||||
StorageInfo(ctx context.Context) StorageInfo
|
||||
LocalStorageInfo(ctx context.Context) StorageInfo
|
||||
StorageInfo(ctx context.Context, metrics bool) StorageInfo
|
||||
LocalStorageInfo(ctx context.Context, metrics bool) StorageInfo
|
||||
|
||||
// Bucket operations.
|
||||
MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error
|
||||
|
||||
+14
-13
@@ -1982,20 +1982,21 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
md5hex = "" // Do not try to verify the content.
|
||||
sha256hex = ""
|
||||
}
|
||||
var hashReader *hash.Reader
|
||||
// Optimization: If SSE-KMS and SSE-C did not request Content-Md5. Use uuid as etag
|
||||
if !etag.ContentMD5Requested(r.Header) && (crypto.S3KMS.IsRequested(r.Header) || crypto.SSEC.IsRequested(r.Header)) {
|
||||
hashReader, err = hash.NewReaderWithOpts(ctx, reader, hash.Options{
|
||||
Size: size,
|
||||
MD5Hex: md5hex,
|
||||
SHA256Hex: sha256hex,
|
||||
ActualSize: actualSize,
|
||||
DisableMD5: false,
|
||||
ForceMD5: mustGetUUIDBytes(),
|
||||
})
|
||||
} else {
|
||||
hashReader, err = hash.NewReader(ctx, reader, size, md5hex, sha256hex, actualSize)
|
||||
|
||||
var forceMD5 []byte
|
||||
// Optimization: If SSE-KMS and SSE-C did not request Content-Md5. Use uuid as etag. Optionally enable this also
|
||||
// for server that is started with `--no-compat`.
|
||||
if !etag.ContentMD5Requested(r.Header) && (crypto.S3KMS.IsRequested(r.Header) || crypto.SSEC.IsRequested(r.Header) || !globalServerCtxt.StrictS3Compat) {
|
||||
forceMD5 = mustGetUUIDBytes()
|
||||
}
|
||||
hashReader, err := hash.NewReaderWithOpts(ctx, reader, hash.Options{
|
||||
Size: size,
|
||||
MD5Hex: md5hex,
|
||||
SHA256Hex: sha256hex,
|
||||
ActualSize: actualSize,
|
||||
DisableMD5: false,
|
||||
ForceMD5: forceMD5,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
@@ -728,7 +728,14 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
sha256hex = ""
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(ctx, reader, size, md5hex, sha256hex, actualSize)
|
||||
hashReader, err := hash.NewReaderWithOpts(ctx, reader, hash.Options{
|
||||
Size: size,
|
||||
MD5Hex: md5hex,
|
||||
SHA256Hex: sha256hex,
|
||||
ActualSize: actualSize,
|
||||
DisableMD5: false,
|
||||
ForceMD5: nil,
|
||||
})
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||
return
|
||||
|
||||
@@ -102,8 +102,10 @@ func (client *peerRESTClient) GetLocks() (lockMap map[string][]lockRequesterInfo
|
||||
}
|
||||
|
||||
// LocalStorageInfo - fetch server information for a remote node.
|
||||
func (client *peerRESTClient) LocalStorageInfo() (info StorageInfo, err error) {
|
||||
respBody, err := client.call(peerRESTMethodLocalStorageInfo, nil, nil, -1)
|
||||
func (client *peerRESTClient) LocalStorageInfo(metrics bool) (info StorageInfo, err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTMetrics, strconv.FormatBool(metrics))
|
||||
respBody, err := client.call(peerRESTMethodLocalStorageInfo, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
package cmd
|
||||
|
||||
const (
|
||||
peerRESTVersion = "v33" // Add SRMetrics
|
||||
peerRESTVersion = "v34" // Add metrics flag to LocalStorageInfo call
|
||||
|
||||
peerRESTVersionPrefix = SlashSeparator + peerRESTVersion
|
||||
peerRESTPrefix = minioReservedBucketPath + "/peer"
|
||||
@@ -106,6 +106,7 @@ const (
|
||||
peerRESTJobID = "job-id"
|
||||
peerRESTDepID = "depID"
|
||||
peerRESTStartRebalance = "start-rebalance"
|
||||
peerRESTMetrics = "metrics"
|
||||
|
||||
peerRESTListenBucket = "bucket"
|
||||
peerRESTListenPrefix = "prefix"
|
||||
|
||||
@@ -345,7 +345,14 @@ func (s *peerRESTServer) LocalStorageInfoHandler(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(objLayer.LocalStorageInfo(r.Context())))
|
||||
metrics, err := strconv.ParseBool(r.Form.Get(peerRESTMetrics))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(objLayer.LocalStorageInfo(r.Context(), metrics)))
|
||||
}
|
||||
|
||||
// ServerInfoHandler - returns Server Info
|
||||
|
||||
+25
-25
@@ -80,16 +80,16 @@ func (s *peerS3Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []BucketInfo, err error) {
|
||||
globalLocalDrivesMu.RLock()
|
||||
globalLocalDrives := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
quorum := (len(globalLocalDrives) / 2)
|
||||
quorum := (len(localDrives) / 2)
|
||||
|
||||
buckets = make([]BucketInfo, 0, 32)
|
||||
healBuckets := map[string]VolInfo{}
|
||||
|
||||
// lists all unique buckets across drives.
|
||||
if err := listAllBuckets(ctx, globalLocalDrives, healBuckets, quorum); err != nil {
|
||||
if err := listAllBuckets(ctx, localDrives, healBuckets, quorum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []Bucket
|
||||
|
||||
if opts.Deleted {
|
||||
// lists all deleted buckets across drives.
|
||||
if err := listDeletedBuckets(ctx, globalLocalDrives, deletedBuckets, quorum); err != nil {
|
||||
if err := listDeletedBuckets(ctx, localDrives, deletedBuckets, quorum); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -128,23 +128,23 @@ func listBucketsLocal(ctx context.Context, opts BucketOptions) (buckets []Bucket
|
||||
|
||||
func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
||||
globalLocalDrivesMu.RLock()
|
||||
globalLocalDrives := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
|
||||
bucketsInfo := make([]BucketInfo, len(globalLocalDrives))
|
||||
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
||||
bucketsInfo := make([]BucketInfo, len(localDrives))
|
||||
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range globalLocalDrives {
|
||||
for index := range localDrives {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if globalLocalDrives[index] == nil {
|
||||
if localDrives[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
volInfo, err := globalLocalDrives[index].StatVol(ctx, bucket)
|
||||
volInfo, err := localDrives[index].StatVol(ctx, bucket)
|
||||
if err != nil {
|
||||
if opts.Deleted {
|
||||
dvi, derr := globalLocalDrives[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket))
|
||||
dvi, derr := localDrives[index].StatVol(ctx, pathJoin(minioMetaBucket, bucketMetaPrefix, deletedBucketsPrefix, bucket))
|
||||
if derr != nil {
|
||||
return err
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions)
|
||||
}
|
||||
|
||||
errs := g.Wait()
|
||||
if err := reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives) / 2)); err != nil {
|
||||
if err := reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(localDrives) / 2)); err != nil {
|
||||
return BucketInfo{}, err
|
||||
}
|
||||
|
||||
@@ -177,19 +177,19 @@ func getBucketInfoLocal(ctx context.Context, bucket string, opts BucketOptions)
|
||||
|
||||
func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
|
||||
globalLocalDrivesMu.RLock()
|
||||
globalLocalDrives := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
|
||||
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
||||
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range globalLocalDrives {
|
||||
for index := range localDrives {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if globalLocalDrives[index] == nil {
|
||||
if localDrives[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
return globalLocalDrives[index].DeleteVol(ctx, bucket, opts.Force)
|
||||
return localDrives[index].DeleteVol(ctx, bucket, opts.Force)
|
||||
}, index)
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOpti
|
||||
}
|
||||
if err == nil && recreate {
|
||||
// ignore any errors
|
||||
globalLocalDrives[index].MakeVol(ctx, bucket)
|
||||
localDrives[index].MakeVol(ctx, bucket)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,24 +210,24 @@ func deleteBucketLocal(ctx context.Context, bucket string, opts DeleteBucketOpti
|
||||
return errVolumeNotEmpty
|
||||
} // for all other errors reduce by write quorum.
|
||||
|
||||
return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives)/2)+1)
|
||||
return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(localDrives)/2)+1)
|
||||
}
|
||||
|
||||
func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions) error {
|
||||
globalLocalDrivesMu.RLock()
|
||||
globalLocalDrives := globalLocalDrives
|
||||
localDrives := globalLocalDrives
|
||||
globalLocalDrivesMu.RUnlock()
|
||||
|
||||
g := errgroup.WithNErrs(len(globalLocalDrives)).WithConcurrency(32)
|
||||
g := errgroup.WithNErrs(len(localDrives)).WithConcurrency(32)
|
||||
|
||||
// Make a volume entry on all underlying storage disks.
|
||||
for index := range globalLocalDrives {
|
||||
for index := range localDrives {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if globalLocalDrives[index] == nil {
|
||||
if localDrives[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
err := globalLocalDrives[index].MakeVol(ctx, bucket)
|
||||
err := localDrives[index].MakeVol(ctx, bucket)
|
||||
if opts.ForceCreate && errors.Is(err, errVolumeExists) {
|
||||
// No need to return error when force create was
|
||||
// requested.
|
||||
@@ -238,7 +238,7 @@ func makeBucketLocal(ctx context.Context, bucket string, opts MakeBucketOptions)
|
||||
}
|
||||
|
||||
errs := g.Wait()
|
||||
return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(globalLocalDrives)/2)+1)
|
||||
return reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, (len(localDrives)/2)+1)
|
||||
}
|
||||
|
||||
func (s *peerS3Server) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -55,7 +55,7 @@ func rebalanceStatus(ctx context.Context, z *erasureServerPools) (r rebalanceAdm
|
||||
}
|
||||
|
||||
// Compute disk usage percentage
|
||||
si := z.StorageInfo(ctx)
|
||||
si := z.StorageInfo(ctx, true)
|
||||
diskStats := make([]struct {
|
||||
AvailableSpace uint64
|
||||
TotalSpace uint64
|
||||
|
||||
+92
-27
@@ -51,10 +51,16 @@ import (
|
||||
"github.com/minio/pkg/v2/certs"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// ServerFlags - server command specific flags
|
||||
var ServerFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config",
|
||||
Usage: "specify server configuration via YAML configuration",
|
||||
EnvVar: "MINIO_CONFIG",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "address",
|
||||
Value: ":" + GlobalMinioDefaultPort,
|
||||
@@ -68,6 +74,12 @@ var ServerFlags = []cli.Flag{
|
||||
EnvVar: "MINIO_LISTENERS",
|
||||
Hidden: true,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "pre-allocate",
|
||||
Usage: "Number of 1MiB sized buffers to pre-allocate. Default 2048",
|
||||
EnvVar: "MINIO_PRE_ALLOCATE",
|
||||
Hidden: true,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console-address",
|
||||
Usage: "bind to a specific ADDRESS:PORT for embedded Console UI, ADDRESS can be an IP or hostname",
|
||||
@@ -226,9 +238,57 @@ func serverCmdArgs(ctx *cli.Context) []string {
|
||||
return strings.Fields(v)
|
||||
}
|
||||
|
||||
func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
// Handle common command args.
|
||||
handleCommonCmdArgs(ctx)
|
||||
func mergeServerCtxtFromConfigFile(configFile string, ctxt *serverCtxt) error {
|
||||
rd, err := Open(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rd.Close()
|
||||
|
||||
cf := &config.ServerConfig{}
|
||||
dec := yaml.NewDecoder(rd)
|
||||
dec.SetStrict(true)
|
||||
if err = dec.Decode(cf); err != nil {
|
||||
return err
|
||||
}
|
||||
if cf.Version != "v1" {
|
||||
return fmt.Errorf("unexpected version: %s", cf.Version)
|
||||
}
|
||||
|
||||
ctxt.RootUser = cf.RootUser
|
||||
ctxt.RootPwd = cf.RootPwd
|
||||
|
||||
if cf.Addr != "" {
|
||||
ctxt.Addr = cf.Addr
|
||||
}
|
||||
if cf.ConsoleAddr != "" {
|
||||
ctxt.ConsoleAddr = cf.ConsoleAddr
|
||||
}
|
||||
if cf.CertsDir != "" {
|
||||
ctxt.CertsDir = cf.CertsDir
|
||||
ctxt.certsDirSet = true
|
||||
}
|
||||
|
||||
if cf.Options.FTP.Address != "" {
|
||||
ctxt.FTP = append(ctxt.FTP, fmt.Sprintf("address=%s", cf.Options.FTP.Address))
|
||||
}
|
||||
if cf.Options.FTP.PassivePortRange != "" {
|
||||
ctxt.FTP = append(ctxt.FTP, fmt.Sprintf("passive-port-range=%s", cf.Options.FTP.PassivePortRange))
|
||||
}
|
||||
|
||||
if cf.Options.SFTP.Address != "" {
|
||||
ctxt.SFTP = append(ctxt.SFTP, fmt.Sprintf("address=%s", cf.Options.SFTP.Address))
|
||||
}
|
||||
if cf.Options.SFTP.SSHPrivateKey != "" {
|
||||
ctxt.SFTP = append(ctxt.SFTP, fmt.Sprintf("ssh-private-key=%s", cf.Options.SFTP.SSHPrivateKey))
|
||||
}
|
||||
|
||||
ctxt.Layout, err = buildDisksLayoutFromConfFile(cf.Pools)
|
||||
return err
|
||||
}
|
||||
|
||||
func serverHandleCmdArgs(ctxt serverCtxt) {
|
||||
handleCommonArgs(ctxt)
|
||||
|
||||
logger.FatalIf(CheckLocalServerAddr(globalMinioAddr), "Unable to validate passed arguments")
|
||||
|
||||
@@ -251,7 +311,7 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
// Register root CAs for remote ENVs
|
||||
env.RegisterGlobalCAs(globalRootCAs)
|
||||
|
||||
globalEndpoints, setupType, err = createServerEndpoints(globalMinioAddr, serverCmdArgs(ctx)...)
|
||||
globalEndpoints, setupType, err = createServerEndpoints(globalMinioAddr, ctxt.Layout.pools, ctxt.Layout.legacy)
|
||||
logger.FatalIf(err, "Invalid command line arguments")
|
||||
globalNodes = globalEndpoints.GetNodes()
|
||||
|
||||
@@ -262,7 +322,7 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
}
|
||||
globalIsErasureSD = (setupType == ErasureSDSetupType)
|
||||
if globalDynamicAPIPort && globalIsDistErasure {
|
||||
logger.FatalIf(errInvalidArgument, "Invalid --address=\"%s\", port '0' is not allowed in a distributed erasure coded setup", ctx.String("address"))
|
||||
logger.FatalIf(errInvalidArgument, "Invalid --address=\"%s\", port '0' is not allowed in a distributed erasure coded setup", ctxt.Addr)
|
||||
}
|
||||
|
||||
globalLocalNodeName = GetLocalPeer(globalEndpoints, globalMinioHost, globalMinioPort)
|
||||
@@ -270,7 +330,7 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
globalLocalNodeNameHex = hex.EncodeToString(nodeNameSum[:])
|
||||
|
||||
// Initialize, see which NIC the service is running on, and save it as global value
|
||||
setGlobalInternodeInterface(ctx.String("interface"))
|
||||
setGlobalInternodeInterface(ctxt.Interface)
|
||||
|
||||
// allow transport to be HTTP/1.1 for proxying.
|
||||
globalProxyTransport = NewCustomHTTPProxyTransport()()
|
||||
@@ -289,8 +349,8 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
})
|
||||
|
||||
globalTCPOptions = xhttp.TCPOptions{
|
||||
UserTimeout: int(ctx.Duration("conn-user-timeout").Milliseconds()),
|
||||
Interface: ctx.String("interface"),
|
||||
UserTimeout: int(ctxt.UserTimeout.Milliseconds()),
|
||||
Interface: ctxt.Interface,
|
||||
}
|
||||
|
||||
// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back
|
||||
@@ -299,13 +359,8 @@ func serverHandleCmdArgs(ctx *cli.Context) {
|
||||
// To avoid this error situation we check for port availability.
|
||||
logger.FatalIf(xhttp.CheckPortAvailability(globalMinioHost, globalMinioPort, globalTCPOptions), "Unable to start the server")
|
||||
|
||||
globalConnReadDeadline = ctx.Duration("conn-read-deadline")
|
||||
globalConnWriteDeadline = ctx.Duration("conn-write-deadline")
|
||||
}
|
||||
|
||||
func serverHandleEnvVars() {
|
||||
// Handle common environment variables.
|
||||
handleCommonEnvVars()
|
||||
globalConnReadDeadline = ctxt.ConnReadDeadline
|
||||
globalConnWriteDeadline = ctxt.ConnWriteDeadline
|
||||
}
|
||||
|
||||
var globalHealStateLK sync.RWMutex
|
||||
@@ -590,14 +645,24 @@ func serverMain(ctx *cli.Context) {
|
||||
// Always load ENV variables from files first.
|
||||
loadEnvVarsFromFiles()
|
||||
|
||||
// Handle all server command args.
|
||||
// Handle all server command args and build the disks layout
|
||||
bootstrapTrace("serverHandleCmdArgs", func() {
|
||||
serverHandleCmdArgs(ctx)
|
||||
err := buildServerCtxt(ctx, &globalServerCtxt)
|
||||
logger.FatalIf(err, "Unable to prepare the list of endpoints")
|
||||
|
||||
serverHandleCmdArgs(globalServerCtxt)
|
||||
})
|
||||
|
||||
// DNS cache subsystem to reduce outgoing DNS requests
|
||||
runDNSCache(ctx)
|
||||
|
||||
// Handle all server environment vars.
|
||||
serverHandleEnvVars()
|
||||
|
||||
// Load the root credentials from the shell environment or from
|
||||
// the config file if not defined, set the default one.
|
||||
loadRootCredentials()
|
||||
|
||||
// Initialize globalConsoleSys system
|
||||
bootstrapTrace("newConsoleLogger", func() {
|
||||
globalConsoleSys = NewConsoleLogger(GlobalContext)
|
||||
@@ -637,7 +702,7 @@ func serverMain(ctx *cli.Context) {
|
||||
|
||||
// Check for updates in non-blocking manner.
|
||||
go func() {
|
||||
if !globalCLIContext.Quiet && !globalInplaceUpdateDisabled {
|
||||
if !globalServerCtxt.Quiet && !globalInplaceUpdateDisabled {
|
||||
// Check for new updates from dl.min.io.
|
||||
bootstrapTrace("checkUpdate", func() {
|
||||
checkUpdate(getMinioMode())
|
||||
@@ -683,9 +748,9 @@ func serverMain(ctx *cli.Context) {
|
||||
httpServer := xhttp.NewServer(getServerListenAddrs()).
|
||||
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
|
||||
UseTLSConfig(newTLSConfig(getCert)).
|
||||
UseShutdownTimeout(ctx.Duration("shutdown-timeout")).
|
||||
UseIdleTimeout(ctx.Duration("idle-timeout")).
|
||||
UseReadHeaderTimeout(ctx.Duration("read-header-timeout")).
|
||||
UseShutdownTimeout(globalServerCtxt.ShutdownTimeout).
|
||||
UseIdleTimeout(globalServerCtxt.IdleTimeout).
|
||||
UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout).
|
||||
UseBaseContext(GlobalContext).
|
||||
UseCustomLogger(log.New(io.Discard, "", 0)). // Turn-off random logging by Go stdlib
|
||||
UseTCPOptions(globalTCPOptions)
|
||||
@@ -779,7 +844,7 @@ func serverMain(ctx *cli.Context) {
|
||||
logger.LogIf(GlobalContext, err)
|
||||
}
|
||||
|
||||
if !globalCLIContext.StrictS3Compat {
|
||||
if !globalServerCtxt.StrictS3Compat {
|
||||
logger.Info(color.RedBold("WARNING: Strict AWS S3 compatible incoming PUT, POST content payload validation is turned off, caution is advised do not use in production"))
|
||||
}
|
||||
})
|
||||
@@ -813,16 +878,16 @@ func serverMain(ctx *cli.Context) {
|
||||
}
|
||||
|
||||
// if we see FTP args, start FTP if possible
|
||||
if len(ctx.StringSlice("ftp")) > 0 {
|
||||
if len(globalServerCtxt.FTP) > 0 {
|
||||
bootstrapTrace("go startFTPServer", func() {
|
||||
go startFTPServer(ctx)
|
||||
go startFTPServer(globalServerCtxt.FTP)
|
||||
})
|
||||
}
|
||||
|
||||
// If we see SFTP args, start SFTP if possible
|
||||
if len(ctx.StringSlice("sftp")) > 0 {
|
||||
bootstrapTrace("go startFTPServer", func() {
|
||||
go startSFTPServer(ctx)
|
||||
if len(globalServerCtxt.SFTP) > 0 {
|
||||
bootstrapTrace("go startSFTPServer", func() {
|
||||
go startSFTPServer(globalServerCtxt.SFTP)
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -23,6 +23,57 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestServerConfigFile(t *testing.T) {
|
||||
for _, testcase := range []struct {
|
||||
config string
|
||||
expectedErr bool
|
||||
hash string
|
||||
}{
|
||||
{
|
||||
config: "testdata/config/1.yaml",
|
||||
expectedErr: false,
|
||||
hash: "hash:02bf70285dc71f76",
|
||||
},
|
||||
{
|
||||
config: "testdata/config/2.yaml",
|
||||
expectedErr: false,
|
||||
hash: "hash:676d2da00f71f205",
|
||||
},
|
||||
{
|
||||
config: "testdata/config/invalid.yaml",
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
config: "testdata/config/invalid-types.yaml",
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
config: "testdata/config/invalid-disks.yaml",
|
||||
expectedErr: true,
|
||||
},
|
||||
} {
|
||||
testcase := testcase
|
||||
t.Run(testcase.config, func(t *testing.T) {
|
||||
sctx := &serverCtxt{}
|
||||
err := mergeServerCtxtFromConfigFile(testcase.config, sctx)
|
||||
if testcase.expectedErr && err == nil {
|
||||
t.Error("expected failure, got success")
|
||||
}
|
||||
if !testcase.expectedErr && err != nil {
|
||||
t.Error("expected success, got failure", err)
|
||||
}
|
||||
if err == nil {
|
||||
if len(sctx.Layout.pools) != 2 {
|
||||
t.Error("expected parsed pools to be 2, not", len(sctx.Layout.pools))
|
||||
}
|
||||
if sctx.Layout.pools[0].cmdline != testcase.hash {
|
||||
t.Error("expected hash", testcase.hash, "got", sctx.Layout.pools[0].cmdline)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Tests initializing new object layer.
|
||||
func TestNewObjectLayer(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@@ -55,7 +55,7 @@ func printStartupMessage(apiEndpoints []string, err error) {
|
||||
// Object layer is initialized then print StorageInfo.
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI != nil {
|
||||
printStorageInfo(objAPI.StorageInfo(GlobalContext))
|
||||
printStorageInfo(objAPI.StorageInfo(GlobalContext, true))
|
||||
}
|
||||
|
||||
// Prints credential, region and browser access.
|
||||
@@ -121,7 +121,7 @@ func printServerCommonMsg(apiEndpoints []string) {
|
||||
|
||||
// Colorize the message and print.
|
||||
logger.Info(color.Blue("S3-API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
|
||||
if color.IsTerminal() && (!globalCLIContext.Anonymous && !globalCLIContext.JSON) {
|
||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON) {
|
||||
logger.Info(color.Blue("RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||
logger.Info(color.Blue("RootPass: ") + color.Bold("%s \n", cred.SecretKey))
|
||||
if region != "" {
|
||||
@@ -132,7 +132,7 @@ func printServerCommonMsg(apiEndpoints []string) {
|
||||
if globalBrowserEnabled {
|
||||
consoleEndpointStr := strings.Join(stripStandardPorts(getConsoleEndpoints(), globalMinioConsoleHost), " ")
|
||||
logger.Info(color.Blue("Console: ") + color.Bold(fmt.Sprintf("%s ", consoleEndpointStr)))
|
||||
if color.IsTerminal() && (!globalCLIContext.Anonymous && !globalCLIContext.JSON) {
|
||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON) {
|
||||
logger.Info(color.Blue("RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||
logger.Info(color.Blue("RootPass: ") + color.Bold("%s ", cred.SecretKey))
|
||||
}
|
||||
@@ -187,7 +187,7 @@ func printCLIAccessMsg(endPoint string, alias string) {
|
||||
const mcQuickStartGuide = "https://min.io/docs/minio/linux/reference/minio-mc.html#quickstart"
|
||||
|
||||
// Configure 'mc', following block prints platform specific information for minio client.
|
||||
if color.IsTerminal() && !globalCLIContext.Anonymous {
|
||||
if color.IsTerminal() && !globalServerCtxt.Anonymous {
|
||||
logger.Info(color.Blue("\nCommand-line: ") + mcQuickStartGuide)
|
||||
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
|
||||
endPoint, cred.AccessKey, cred.SecretKey)
|
||||
|
||||
+1
-1
@@ -137,7 +137,7 @@ func TestServerSuite(t *testing.T) {
|
||||
// Init and run test on ErasureSet backend.
|
||||
{serverType: "ErasureSet", signer: signerV4},
|
||||
}
|
||||
globalCLIContext.StrictS3Compat = true
|
||||
globalServerCtxt.StrictS3Compat = true
|
||||
for i, testCase := range testCases {
|
||||
t.Run(fmt.Sprintf("Test: %d, ServerType: %s", i+1, testCase.serverType), func(t *testing.T) {
|
||||
runAllTests(testCase, &check{t, testCase.serverType})
|
||||
|
||||
+55
-44
@@ -93,55 +93,66 @@ func (f *sftpDriver) getMinIOClient() (*minio.Client, error) {
|
||||
return nil, errNoSuchUser
|
||||
}
|
||||
if !ok && globalIAMSys.LDAPConfig.Enabled() {
|
||||
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(f.AccessKey())
|
||||
if err != nil {
|
||||
sa, _, err := globalIAMSys.getServiceAccount(context.Background(), f.AccessKey())
|
||||
if err != nil && !errors.Is(err, errNoSuchServiceAccount) {
|
||||
return nil, err
|
||||
}
|
||||
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var mcreds *credentials.Credentials
|
||||
if errors.Is(err, errNoSuchServiceAccount) {
|
||||
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.LookupUserDN(f.AccessKey())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expiryDur, err := globalIAMSys.LDAPConfig.GetExpiryDuration("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claims := make(map[string]interface{})
|
||||
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
|
||||
for k, v := range f.permissions.CriticalOptions {
|
||||
claims[k] = v
|
||||
}
|
||||
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the parent of the temporary access key, this is useful
|
||||
// in obtaining service accounts by this cred.
|
||||
cred.ParentUser = targetUser
|
||||
|
||||
// Set this value to LDAP groups, LDAP user can be part
|
||||
// of large number of groups
|
||||
cred.Groups = targetGroups
|
||||
|
||||
// Set the newly generated credentials, policyName is empty on purpose
|
||||
// LDAP policies are applied automatically using their ldapUser, ldapGroups
|
||||
// mapping.
|
||||
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
SecretKey: cred.SecretKey,
|
||||
SessionToken: cred.SessionToken,
|
||||
ParentUser: cred.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
|
||||
mcreds = credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken)
|
||||
} else {
|
||||
mcreds = credentials.NewStaticV4(sa.Credentials.AccessKey, sa.Credentials.SecretKey, "")
|
||||
}
|
||||
claims := make(map[string]interface{})
|
||||
claims[expClaim] = UTCNow().Add(expiryDur).Unix()
|
||||
for k, v := range f.permissions.CriticalOptions {
|
||||
claims[k] = v
|
||||
}
|
||||
|
||||
cred, err := auth.GetNewCredentialsWithMetadata(claims, globalActiveCred.SecretKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the parent of the temporary access key, this is useful
|
||||
// in obtaining service accounts by this cred.
|
||||
cred.ParentUser = targetUser
|
||||
|
||||
// Set this value to LDAP groups, LDAP user can be part
|
||||
// of large number of groups
|
||||
cred.Groups = targetGroups
|
||||
|
||||
// Set the newly generated credentials, policyName is empty on purpose
|
||||
// LDAP policies are applied automatically using their ldapUser, ldapGroups
|
||||
// mapping.
|
||||
updatedAt, err := globalIAMSys.SetTempUser(context.Background(), cred.AccessKey, cred, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Call hook for site replication.
|
||||
logger.LogIf(context.Background(), globalSiteReplicationSys.IAMChangeHook(context.Background(), madmin.SRIAMItem{
|
||||
Type: madmin.SRIAMItemSTSAcc,
|
||||
STSCredential: &madmin.SRSTSCredential{
|
||||
AccessKey: cred.AccessKey,
|
||||
SecretKey: cred.SecretKey,
|
||||
SessionToken: cred.SessionToken,
|
||||
ParentUser: cred.ParentUser,
|
||||
},
|
||||
UpdatedAt: updatedAt,
|
||||
}))
|
||||
|
||||
return minio.New(f.endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cred.AccessKey, cred.SecretKey, cred.SessionToken),
|
||||
Creds: mcreds,
|
||||
Secure: globalIsTLS,
|
||||
Transport: globalRemoteFTPClientTransport,
|
||||
})
|
||||
|
||||
+29
-16
@@ -20,6 +20,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -27,7 +28,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/cli"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
xsftp "github.com/minio/pkg/v2/sftp"
|
||||
"github.com/pkg/sftp"
|
||||
@@ -53,9 +53,7 @@ func (s *sftpLogger) Error(tag xsftp.LogType, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func startSFTPServer(c *cli.Context) {
|
||||
args := c.StringSlice("sftp")
|
||||
|
||||
func startSFTPServer(args []string) {
|
||||
var (
|
||||
port int
|
||||
publicIP string
|
||||
@@ -110,21 +108,36 @@ func startSFTPServer(c *cli.Context) {
|
||||
sshConfig := &ssh.ServerConfig{
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
if globalIAMSys.LDAPConfig.Enabled() {
|
||||
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.Bind(c.User(), string(pass))
|
||||
if err != nil {
|
||||
sa, _, err := globalIAMSys.getServiceAccount(context.Background(), c.User())
|
||||
if err != nil && !errors.Is(err, errNoSuchServiceAccount) {
|
||||
return nil, err
|
||||
}
|
||||
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...)
|
||||
if len(ldapPolicies) == 0 {
|
||||
return nil, errAuthentication
|
||||
if errors.Is(err, errNoSuchServiceAccount) {
|
||||
targetUser, targetGroups, err := globalIAMSys.LDAPConfig.Bind(c.User(), string(pass))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ldapPolicies, _ := globalIAMSys.PolicyDBGet(targetUser, targetGroups...)
|
||||
if len(ldapPolicies) == 0 {
|
||||
return nil, errAuthentication
|
||||
}
|
||||
return &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{
|
||||
ldapUser: targetUser,
|
||||
ldapUserN: c.User(),
|
||||
},
|
||||
Extensions: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
return &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{
|
||||
ldapUser: targetUser,
|
||||
ldapUserN: c.User(),
|
||||
},
|
||||
Extensions: make(map[string]string),
|
||||
}, nil
|
||||
if subtle.ConstantTimeCompare([]byte(sa.Credentials.SecretKey), pass) == 1 {
|
||||
return &ssh.Permissions{
|
||||
CriticalOptions: map[string]string{
|
||||
"accessKey": c.User(),
|
||||
},
|
||||
Extensions: make(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
return nil, errAuthentication
|
||||
}
|
||||
|
||||
ui, ok := globalIAMSys.GetUser(context.Background(), c.User())
|
||||
|
||||
@@ -79,7 +79,7 @@ func skipContentSha256Cksum(r *http.Request) bool {
|
||||
// We return true only in situations when
|
||||
// deployment has asked MinIO to allow for
|
||||
// such broken clients and content-length > 0.
|
||||
return r.ContentLength > 0 && !globalCLIContext.StrictS3Compat
|
||||
return r.ContentLength > 0 && !globalServerCtxt.StrictS3Compat
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+41
-32
@@ -34,7 +34,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/grid"
|
||||
@@ -55,18 +54,13 @@ var errDiskStale = errors.New("drive stale")
|
||||
|
||||
// To abstract a disk over network.
|
||||
type storageRESTServer struct {
|
||||
storage atomic.Value
|
||||
poolIndex, setIndex, diskIndex int
|
||||
}
|
||||
|
||||
func (s *storageRESTServer) getStorage() *xlStorageDiskIDCheck {
|
||||
if s, ok := s.storage.Load().(*xlStorageDiskIDCheck); ok {
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *storageRESTServer) setStorage(xl *xlStorageDiskIDCheck) {
|
||||
s.storage.Store(xl)
|
||||
func (s *storageRESTServer) getStorage() StorageAPI {
|
||||
globalLocalDrivesMu.RLock()
|
||||
defer globalLocalDrivesMu.RUnlock()
|
||||
return globalLocalSetDrives[s.poolIndex][s.setIndex][s.diskIndex]
|
||||
}
|
||||
|
||||
func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error) {
|
||||
@@ -218,7 +212,6 @@ func (s *storageRESTServer) DiskInfoHandler(params *grid.MSS) (*DiskInfo, *grid.
|
||||
if err != nil {
|
||||
info.Error = err.Error()
|
||||
}
|
||||
info.Scanning = s.getStorage().storage != nil && atomic.LoadInt32(&s.getStorage().storage.scanning) > 0
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
@@ -646,17 +639,14 @@ func (s *storageRESTServer) ReadFileStreamHandler(w http.ResponseWriter, r *http
|
||||
// Attempt to use splice/sendfile() optimization, A very specific behavior mentioned below is necessary.
|
||||
// See https://github.com/golang/go/blob/f7c5cbb82087c55aa82081e931e0142783700ce8/src/net/sendfile_linux.go#L20
|
||||
// Windows can lock up with this optimization, so we fall back to regular copy.
|
||||
dr, ok := rc.(*xioutil.DeadlineReader)
|
||||
sr, ok := rc.(*sendFileReader)
|
||||
if ok {
|
||||
sr, ok := dr.ReadCloser.(*sendFileReader)
|
||||
if ok {
|
||||
_, err = rf.ReadFrom(sr.Reader)
|
||||
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
|
||||
logger.LogIf(r.Context(), err)
|
||||
}
|
||||
if err == nil || !errors.Is(err, xhttp.ErrNotImplemented) {
|
||||
return
|
||||
}
|
||||
_, err = rf.ReadFrom(sr.Reader)
|
||||
if !xnet.IsNetworkOrHostDown(err, true) { // do not need to log disconnected clients
|
||||
logger.LogIf(r.Context(), err)
|
||||
}
|
||||
if err == nil || !errors.Is(err, xhttp.ErrNotImplemented) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} // Fallback to regular copy
|
||||
@@ -735,6 +725,7 @@ func (s *storageRESTServer) DeleteVersionsHandler(w http.ResponseWriter, r *http
|
||||
setEventStreamHeaders(w)
|
||||
encoder := gob.NewEncoder(w)
|
||||
done := keepHTTPResponseAlive(w)
|
||||
|
||||
errs := s.getStorage().DeleteVersions(r.Context(), volume, versions)
|
||||
done(nil)
|
||||
for idx := range versions {
|
||||
@@ -1332,25 +1323,36 @@ func (s *storageRESTServer) ReadMultiple(w http.ResponseWriter, r *http.Request)
|
||||
rw.CloseWithError(err)
|
||||
}
|
||||
|
||||
// globalLocalSetDrives is used for local drive as well as remote REST
|
||||
// API caller for other nodes to talk to this node.
|
||||
//
|
||||
// Any updates to this must be serialized via globalLocalDrivesMu (locker)
|
||||
var globalLocalSetDrives [][][]StorageAPI
|
||||
|
||||
// registerStorageRESTHandlers - register storage rpc router.
|
||||
func registerStorageRESTHandlers(router *mux.Router, endpointServerPools EndpointServerPools, gm *grid.Manager) {
|
||||
h := func(f http.HandlerFunc) http.HandlerFunc {
|
||||
return collectInternodeStats(httpTraceHdrs(f))
|
||||
}
|
||||
|
||||
driveHandlers := make([][]*storageRESTServer, len(endpointServerPools))
|
||||
for pool, serverPool := range endpointServerPools {
|
||||
driveHandlers[pool] = make([]*storageRESTServer, len(serverPool.Endpoints))
|
||||
globalLocalSetDrives = make([][][]StorageAPI, len(endpointServerPools))
|
||||
for pool := range globalLocalSetDrives {
|
||||
globalLocalSetDrives[pool] = make([][]StorageAPI, endpointServerPools[pool].SetCount)
|
||||
for set := range globalLocalSetDrives[pool] {
|
||||
globalLocalSetDrives[pool][set] = make([]StorageAPI, endpointServerPools[pool].DrivesPerSet)
|
||||
}
|
||||
}
|
||||
|
||||
for pool, serverPool := range endpointServerPools {
|
||||
for set, endpoint := range serverPool.Endpoints {
|
||||
for _, serverPool := range endpointServerPools {
|
||||
for _, endpoint := range serverPool.Endpoints {
|
||||
if !endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
|
||||
driveHandlers[pool][set] = &storageRESTServer{}
|
||||
server := driveHandlers[pool][set]
|
||||
server := &storageRESTServer{
|
||||
poolIndex: endpoint.PoolIdx,
|
||||
setIndex: endpoint.SetIdx,
|
||||
diskIndex: endpoint.DiskIdx,
|
||||
}
|
||||
|
||||
subrouter := router.PathPrefix(path.Join(storageRESTPrefix, endpoint.Path)).Subrouter()
|
||||
|
||||
@@ -1404,16 +1406,23 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
|
||||
}
|
||||
storage := newXLStorageDiskIDCheck(xl, true)
|
||||
storage.SetDiskID(xl.diskID)
|
||||
server.setStorage(storage)
|
||||
|
||||
globalLocalDrivesMu.Lock()
|
||||
defer globalLocalDrivesMu.Unlock()
|
||||
|
||||
globalLocalDrives = append(globalLocalDrives, storage)
|
||||
globalLocalSetDrives[endpoint.PoolIdx][endpoint.SetIdx][endpoint.DiskIdx] = storage
|
||||
return true
|
||||
}
|
||||
|
||||
if createStorage(server) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Start async goroutine to create storage.
|
||||
go func(server *storageRESTServer) {
|
||||
for {
|
||||
time.Sleep(5 * time.Second)
|
||||
time.Sleep(3 * time.Second)
|
||||
if createStorage(server) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -470,13 +470,19 @@ func newStorageRESTHTTPServerClient(t testing.TB) *storageRESTClient {
|
||||
t.Fatalf("UpdateIsLocal failed %v", err)
|
||||
}
|
||||
|
||||
endpoint.PoolIdx = 0
|
||||
endpoint.SetIdx = 0
|
||||
endpoint.DiskIdx = 0
|
||||
|
||||
poolEps := []PoolEndpoints{{
|
||||
Endpoints: Endpoints{endpoint},
|
||||
}}
|
||||
poolEps[0].SetCount = 1
|
||||
poolEps[0].DrivesPerSet = 1
|
||||
|
||||
// Register handlers on newly created servers
|
||||
registerStorageRESTHandlers(tg.Mux[0], []PoolEndpoints{{
|
||||
Endpoints: Endpoints{endpoint},
|
||||
}}, tg.Managers[0])
|
||||
registerStorageRESTHandlers(tg.Mux[1], []PoolEndpoints{{
|
||||
Endpoints: Endpoints{endpoint},
|
||||
}}, tg.Managers[1])
|
||||
registerStorageRESTHandlers(tg.Mux[0], poolEps, tg.Managers[0])
|
||||
registerStorageRESTHandlers(tg.Mux[1], poolEps, tg.Managers[1])
|
||||
|
||||
restClient, err := newStorageRESTClient(endpoint, false, tg.Managers[0])
|
||||
if err != nil {
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
version: v1
|
||||
address: ':9000'
|
||||
console-address: ':9001'
|
||||
certs-dir: '/home/user/.minio/certs/'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- 'https://server-example-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server{1...2}-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool1:9000/mnt/disk{1...4}/'
|
||||
-
|
||||
- 'https://server-example-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server{1...2}-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool2:9000/mnt/disk{1...4}/'
|
||||
|
||||
options:
|
||||
ftp: # settings for MinIO to act as an ftp server
|
||||
address: ':8021'
|
||||
passive-port-range: '30000-40000'
|
||||
sftp: # settings for MinIO to act as an sftp server
|
||||
address: ':8022'
|
||||
ssh-private-key: '/home/user/.ssh/id_rsa'
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
version: v1
|
||||
address: ':9000'
|
||||
console-address: ':9001'
|
||||
certs-dir: '/home/user/.minio/certs/'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- 'https://server-example-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool1:9000/mnt/disk{1...4}/'
|
||||
-
|
||||
- 'https://server-example-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool2:9000/mnt/disk{1...4}/'
|
||||
|
||||
options:
|
||||
ftp: # settings for MinIO to act as an ftp server
|
||||
address: ':8021'
|
||||
passive-port-range: '30000-40000'
|
||||
sftp: # settings for MinIO to act as an sftp server
|
||||
address: ':8022'
|
||||
ssh-private-key: '/home/user/.ssh/id_rsa'
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
version: v1
|
||||
address: ':9000'
|
||||
console-address: ':9001'
|
||||
certs-dir: '/home/user/.minio/certs/'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- 'https://server-example-pool1:9000/mnt/disk1/'
|
||||
- 'https://server1-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool1:9000/mnt/disk{1...4}/'
|
||||
-
|
||||
- 'https://server-example-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool2:9000/mnt/disk{1...4}/'
|
||||
|
||||
options:
|
||||
ftp: # settings for MinIO to act as an ftp server
|
||||
address: ':8021'
|
||||
passive-port-range: '30000-40000'
|
||||
sftp: # settings for MinIO to act as an sftp server
|
||||
address: ':8022'
|
||||
ssh-private-key: '/home/user/.ssh/id_rsa'
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
version: v1
|
||||
address: ':9000'
|
||||
console-address: ':9001'
|
||||
certs-dir: '/home/user/.minio/certs/'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- '/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool1:9000/mnt/disk{1...4}/'
|
||||
-
|
||||
- 'https://server-example-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool2:9000/mnt/disk{1...4}/'
|
||||
|
||||
options:
|
||||
ftp: # settings for MinIO to act as an ftp server
|
||||
address: ':8021'
|
||||
passive-port-range: '30000-40000'
|
||||
sftp: # settings for MinIO to act as an sftp server
|
||||
address: ':8022'
|
||||
ssh-private-key: '/home/user/.ssh/id_rsa'
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
version:
|
||||
address: ':9000'
|
||||
console-address: ':9001'
|
||||
certs-dir: '/home/user/.minio/certs/'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- 'https://server-example-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool1:9000/mnt/disk{1...4}/'
|
||||
-
|
||||
- 'https://server-example-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server1-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool2:9000/mnt/disk{1...4}/'
|
||||
|
||||
options:
|
||||
ftp: # settings for MinIO to act as an ftp server
|
||||
address: ':8021'
|
||||
passive-port-range: '30000-40000'
|
||||
sftp: # settings for MinIO to act as an sftp server
|
||||
address: ':8022'
|
||||
ssh-private-key: '/home/user/.ssh/id_rsa'
|
||||
+115
-16
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -27,11 +27,13 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/crypto"
|
||||
"github.com/minio/minio/internal/hash"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
//go:generate msgp -file $GOFILE
|
||||
@@ -80,6 +82,96 @@ type TierConfigMgr struct {
|
||||
Tiers map[string]madmin.TierConfig `json:"tiers"`
|
||||
}
|
||||
|
||||
type tierMetrics struct {
|
||||
sync.RWMutex // protects requestsCount only
|
||||
requestsCount map[string]struct {
|
||||
success int64
|
||||
failure int64
|
||||
}
|
||||
histogram *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
var globalTierMetrics = tierMetrics{
|
||||
requestsCount: make(map[string]struct {
|
||||
success int64
|
||||
failure int64
|
||||
}),
|
||||
histogram: prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "tier_ttlb_seconds",
|
||||
Help: "Time taken by requests served by warm tier",
|
||||
Buckets: []float64{0.01, 0.1, 1, 2, 5, 10, 60, 5 * 60, 15 * 60, 30 * 60},
|
||||
}, []string{"tier"}),
|
||||
}
|
||||
|
||||
func (t *tierMetrics) Observe(tier string, dur time.Duration) {
|
||||
t.histogram.With(prometheus.Labels{"tier": tier}).Observe(dur.Seconds())
|
||||
}
|
||||
|
||||
func (t *tierMetrics) logSuccess(tier string) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
stat := t.requestsCount[tier]
|
||||
stat.success++
|
||||
t.requestsCount[tier] = stat
|
||||
}
|
||||
|
||||
func (t *tierMetrics) logFailure(tier string) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
stat := t.requestsCount[tier]
|
||||
stat.failure++
|
||||
t.requestsCount[tier] = stat
|
||||
}
|
||||
|
||||
var (
|
||||
// {minio_node}_{tier}_{ttlb_seconds_distribution}
|
||||
tierTTLBMD = MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: tierSubsystem,
|
||||
Name: ttlbDistribution,
|
||||
Help: "Distribution of time to last byte for objects downloaded from warm tier",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
|
||||
// {minio_node}_{tier}_{requests_success}
|
||||
tierRequestsSuccessMD = MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: tierSubsystem,
|
||||
Name: tierRequestsSuccess,
|
||||
Help: "Number of requests to download object from warm tier that were successful",
|
||||
Type: counterMetric,
|
||||
}
|
||||
// {minio_node}_{tier}_{requests_failure}
|
||||
tierRequestsFailureMD = MetricDescription{
|
||||
Namespace: nodeMetricNamespace,
|
||||
Subsystem: tierSubsystem,
|
||||
Name: tierRequestsFailure,
|
||||
Help: "Number of requests to download object from warm tier that failed",
|
||||
Type: counterMetric,
|
||||
}
|
||||
)
|
||||
|
||||
func (t *tierMetrics) Report() []Metric {
|
||||
metrics := getHistogramMetrics(t.histogram, tierTTLBMD)
|
||||
t.RLock()
|
||||
defer t.RUnlock()
|
||||
for tier, stat := range t.requestsCount {
|
||||
metrics = append(metrics, Metric{
|
||||
Description: tierRequestsSuccessMD,
|
||||
Value: float64(stat.success),
|
||||
VariableLabels: map[string]string{"tier": tier},
|
||||
})
|
||||
metrics = append(metrics, Metric{
|
||||
Description: tierRequestsFailureMD,
|
||||
Value: float64(stat.failure),
|
||||
VariableLabels: map[string]string{"tier": tier},
|
||||
})
|
||||
}
|
||||
return metrics
|
||||
}
|
||||
|
||||
// IsTierValid returns true if there exists a remote tier by name tierName,
|
||||
// otherwise returns false.
|
||||
func (config *TierConfigMgr) IsTierValid(tierName string) bool {
|
||||
@@ -148,12 +240,11 @@ func (config *TierConfigMgr) Remove(ctx context.Context, tier string) error {
|
||||
return err
|
||||
} else if inuse {
|
||||
return errTierBackendNotEmpty
|
||||
} else {
|
||||
config.Lock()
|
||||
delete(config.Tiers, tier)
|
||||
delete(config.drivercache, tier)
|
||||
config.Unlock()
|
||||
}
|
||||
config.Lock()
|
||||
delete(config.Tiers, tier)
|
||||
delete(config.drivercache, tier)
|
||||
config.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -220,22 +311,30 @@ func (config *TierConfigMgr) Edit(ctx context.Context, tierName string, creds ma
|
||||
cfg := config.Tiers[tierName]
|
||||
switch tierType {
|
||||
case madmin.S3:
|
||||
if (creds.AccessKey == "" || creds.SecretKey == "") && !creds.AWSRole {
|
||||
return errTierMissingCredentials
|
||||
}
|
||||
switch {
|
||||
case creds.AWSRole:
|
||||
if creds.AWSRole {
|
||||
cfg.S3.AWSRole = true
|
||||
default:
|
||||
}
|
||||
if creds.AWSRoleWebIdentityTokenFile != "" && creds.AWSRoleARN != "" {
|
||||
cfg.S3.AWSRoleARN = creds.AWSRoleARN
|
||||
cfg.S3.AWSRoleWebIdentityTokenFile = creds.AWSRoleWebIdentityTokenFile
|
||||
}
|
||||
if creds.AccessKey != "" && creds.SecretKey != "" {
|
||||
cfg.S3.AccessKey = creds.AccessKey
|
||||
cfg.S3.SecretKey = creds.SecretKey
|
||||
}
|
||||
case madmin.Azure:
|
||||
if creds.SecretKey == "" {
|
||||
return errTierMissingCredentials
|
||||
if creds.SecretKey != "" {
|
||||
cfg.Azure.AccountKey = creds.SecretKey
|
||||
}
|
||||
if creds.AzSP.TenantID != "" {
|
||||
cfg.Azure.SPAuth.TenantID = creds.AzSP.TenantID
|
||||
}
|
||||
if creds.AzSP.ClientID != "" {
|
||||
cfg.Azure.SPAuth.ClientID = creds.AzSP.ClientID
|
||||
}
|
||||
if creds.AzSP.ClientSecret != "" {
|
||||
cfg.Azure.SPAuth.ClientSecret = creds.AzSP.ClientSecret
|
||||
}
|
||||
cfg.Azure.AccountKey = creds.SecretKey
|
||||
|
||||
case madmin.GCS:
|
||||
if creds.CredsJSON == nil {
|
||||
return errTierMissingCredentials
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTierMetrics(t *testing.T) {
|
||||
tier := "WARM-1"
|
||||
globalTierMetrics.Observe(tier, 200*time.Millisecond)
|
||||
expSuccess := 10
|
||||
expFailure := 5
|
||||
for i := 0; i < expSuccess; i++ {
|
||||
globalTierMetrics.logSuccess(tier)
|
||||
}
|
||||
for i := 0; i < expFailure; i++ {
|
||||
globalTierMetrics.logFailure(tier)
|
||||
}
|
||||
metrics := globalTierMetrics.Report()
|
||||
var succ, fail float64
|
||||
for _, metric := range metrics {
|
||||
switch metric.Description.Name {
|
||||
case tierRequestsSuccess:
|
||||
succ += metric.Value
|
||||
case tierRequestsFailure:
|
||||
fail += metric.Value
|
||||
}
|
||||
}
|
||||
if int(succ) != expSuccess {
|
||||
t.Fatalf("Expected %d successes but got %f", expSuccess, succ)
|
||||
}
|
||||
if int(fail) != expFailure {
|
||||
t.Fatalf("Expected %d failures but got %f", expFailure, fail)
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ func prepareUpdateMessage(downloadURL string, older time.Duration) string {
|
||||
t := time.Time{}
|
||||
newerThan := humanize.RelTime(t, t.Add(older), "before the latest release", "")
|
||||
|
||||
if globalCLIContext.JSON {
|
||||
if globalServerCtxt.JSON {
|
||||
return fmt.Sprintf("You are running an older version of MinIO released %s, update: %s", newerThan, downloadURL)
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ func veeamSOSAPIGetObject(ctx context.Context, bucket, object string, rs *HTTPRa
|
||||
}
|
||||
|
||||
if quotaSize == 0 {
|
||||
info := objAPI.StorageInfo(ctx)
|
||||
info := objAPI.StorageInfo(ctx, true)
|
||||
info.Backend = objAPI.BackendInfo()
|
||||
|
||||
ci.Capacity = int64(GetTotalUsableCapacity(info.Disks, info))
|
||||
|
||||
@@ -26,8 +26,11 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-storage-blob-go/azblob"
|
||||
"github.com/Azure/go-autorest/autorest/adal"
|
||||
"github.com/Azure/go-autorest/autorest/azure"
|
||||
"github.com/minio/madmin-go/v3"
|
||||
)
|
||||
|
||||
@@ -108,8 +111,66 @@ func (az *warmBackendAzure) InUse(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func newCredentialFromSP(conf madmin.TierAzure) (azblob.Credential, error) {
|
||||
oauthConfig, err := adal.NewOAuthConfig(azure.PublicCloud.ActiveDirectoryEndpoint, conf.SPAuth.TenantID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spt, err := adal.NewServicePrincipalToken(*oauthConfig, conf.SPAuth.ClientID, conf.SPAuth.ClientSecret, azure.PublicCloud.ResourceIdentifiers.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Refresh obtains a fresh token
|
||||
err = spt.Refresh()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tc := azblob.NewTokenCredential(spt.Token().AccessToken, func(tc azblob.TokenCredential) time.Duration {
|
||||
err := spt.Refresh()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
// set the new token value
|
||||
tc.SetToken(spt.Token().AccessToken)
|
||||
|
||||
// get the next token before the current one expires
|
||||
nextRenewal := float64(time.Until(spt.Token().Expires())) * 0.8
|
||||
if nextRenewal <= 0 {
|
||||
nextRenewal = float64(time.Second)
|
||||
}
|
||||
|
||||
return time.Duration(nextRenewal)
|
||||
})
|
||||
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func newWarmBackendAzure(conf madmin.TierAzure, _ string) (*warmBackendAzure, error) {
|
||||
credential, err := azblob.NewSharedKeyCredential(conf.AccountName, conf.AccountKey)
|
||||
var (
|
||||
credential azblob.Credential
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case conf.AccountName == "":
|
||||
return nil, errors.New("the account name is required")
|
||||
case conf.AccountKey != "" && (conf.SPAuth.TenantID != "" || conf.SPAuth.ClientID != "" || conf.SPAuth.ClientSecret != ""):
|
||||
return nil, errors.New("multiple authentication mechanisms are provided")
|
||||
case conf.AccountKey == "" && (conf.SPAuth.TenantID == "" || conf.SPAuth.ClientID == "" || conf.SPAuth.ClientSecret == ""):
|
||||
return nil, errors.New("no authentication mechanism was provided")
|
||||
}
|
||||
|
||||
if conf.Bucket == "" {
|
||||
return nil, errors.New("no bucket name was provided")
|
||||
}
|
||||
|
||||
if conf.IsSPEnabled() {
|
||||
credential, err = newCredentialFromSP(conf)
|
||||
} else {
|
||||
credential, err = azblob.NewSharedKeyCredential(conf.AccountName, conf.AccountKey)
|
||||
}
|
||||
if err != nil {
|
||||
if _, ok := err.(base64.CorruptInputError); ok {
|
||||
return nil, errors.New("invalid Azure credentials")
|
||||
|
||||
@@ -19,6 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
@@ -102,6 +103,15 @@ func (gcs *warmBackendGCS) InUse(ctx context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
func newWarmBackendGCS(conf madmin.TierGCS, _ string) (*warmBackendGCS, error) {
|
||||
// Validation code
|
||||
if conf.Creds == "" {
|
||||
return nil, errors.New("empty credentials unsupported")
|
||||
}
|
||||
|
||||
if conf.Bucket == "" {
|
||||
return nil, errors.New("no bucket name was provided")
|
||||
}
|
||||
|
||||
credsJSON, err := conf.GetCredentialJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
@@ -35,6 +36,15 @@ type warmBackendMinIO struct {
|
||||
var _ WarmBackend = (*warmBackendMinIO)(nil)
|
||||
|
||||
func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO, error) {
|
||||
// Validation of credentials
|
||||
if conf.AccessKey == "" || conf.SecretKey == "" {
|
||||
return nil, errors.New("both access and secret keys are requied")
|
||||
}
|
||||
|
||||
if conf.Bucket == "" {
|
||||
return nil, errors.New("no bucket name was provided")
|
||||
}
|
||||
|
||||
u, err := url.Parse(conf.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+41
-2
@@ -19,6 +19,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -113,15 +114,53 @@ func newWarmBackendS3(conf madmin.TierS3, tier string) (*warmBackendS3, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validation code
|
||||
switch {
|
||||
case conf.AWSRoleWebIdentityTokenFile == "" && conf.AWSRoleARN != "" || conf.AWSRoleWebIdentityTokenFile != "" && conf.AWSRoleARN == "":
|
||||
return nil, errors.New("both the token file and the role ARN are required")
|
||||
case conf.AccessKey == "" && conf.SecretKey != "" || conf.AccessKey != "" && conf.SecretKey == "":
|
||||
return nil, errors.New("both the access and secret keys are required")
|
||||
case conf.AWSRole && (conf.AWSRoleWebIdentityTokenFile != "" || conf.AWSRoleARN != "" || conf.AccessKey != "" || conf.SecretKey != ""):
|
||||
return nil, errors.New("AWS Role cannot be activated with static credentials or the web identity token file")
|
||||
case conf.Bucket == "":
|
||||
return nil, errors.New("no bucket name was provided")
|
||||
}
|
||||
|
||||
// Credentials initialization
|
||||
var creds *credentials.Credentials
|
||||
if conf.AWSRole {
|
||||
switch {
|
||||
case conf.AWSRole:
|
||||
creds = credentials.New(&credentials.IAM{
|
||||
Client: &http.Client{
|
||||
Transport: NewHTTPTransport(),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
case conf.AWSRoleWebIdentityTokenFile != "" && conf.AWSRoleARN != "":
|
||||
sessionName := conf.AWSRoleSessionName
|
||||
if sessionName == "" {
|
||||
// RoleSessionName has a limited set of characters (https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html)
|
||||
sessionName = "minio-tier-" + mustGetUUID()
|
||||
}
|
||||
s3WebIdentityIAM := credentials.IAM{
|
||||
Client: &http.Client{
|
||||
Transport: NewHTTPTransport(),
|
||||
},
|
||||
EKSIdentity: struct {
|
||||
TokenFile string
|
||||
RoleARN string
|
||||
RoleSessionName string
|
||||
}{
|
||||
conf.AWSRoleWebIdentityTokenFile,
|
||||
conf.AWSRoleARN,
|
||||
sessionName,
|
||||
},
|
||||
}
|
||||
creds = credentials.New(&s3WebIdentityIAM)
|
||||
case conf.AccessKey != "" && conf.SecretKey != "":
|
||||
creds = credentials.NewStaticV4(conf.AccessKey, conf.SecretKey, "")
|
||||
default:
|
||||
return nil, errors.New("insufficient parameters for S3 backend authentication")
|
||||
}
|
||||
getRemoteTierTargetInstanceTransportOnce.Do(func() {
|
||||
getRemoteTierTargetInstanceTransport = NewHTTPTransportWithTimeout(10 * time.Minute)
|
||||
|
||||
@@ -458,7 +458,7 @@ func (p *xlStorageDiskIDCheck) CreateFile(ctx context.Context, volume, path stri
|
||||
}
|
||||
defer done(&err)
|
||||
|
||||
return p.storage.CreateFile(ctx, volume, path, size, xioutil.NewDeadlineReader(io.NopCloser(reader), globalDriveConfig.GetMaxTimeout()))
|
||||
return p.storage.CreateFile(ctx, volume, path, size, io.NopCloser(reader))
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) {
|
||||
@@ -480,7 +480,7 @@ func (p *xlStorageDiskIDCheck) ReadFileStream(ctx context.Context, volume, path
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return xioutil.NewDeadlineReader(rc, globalDriveConfig.GetMaxTimeout()), nil
|
||||
return rc, nil
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) RenameFile(ctx context.Context, srcVolume, srcPath, dstVolume, dstPath string) (err error) {
|
||||
|
||||
+26
-34
@@ -1005,8 +1005,10 @@ func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis
|
||||
return err
|
||||
}
|
||||
|
||||
discard := true
|
||||
|
||||
var legacyJSON bool
|
||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFile), false)
|
||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFile), discard)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errFileNotFound) {
|
||||
return err
|
||||
@@ -1016,7 +1018,7 @@ func (s *xlStorage) deleteVersions(ctx context.Context, volume, path string, fis
|
||||
legacy := s.formatLegacy
|
||||
s.RUnlock()
|
||||
if legacy {
|
||||
buf, _, err = s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFileV1), false)
|
||||
buf, _, err = s.readAllData(ctx, volume, volumeDir, pathJoin(volumeDir, path, xlStorageFormatFileV1), discard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1163,7 +1165,7 @@ func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi F
|
||||
}
|
||||
|
||||
var legacyJSON bool
|
||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFile), false)
|
||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFile), true)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errFileNotFound) {
|
||||
return err
|
||||
@@ -1401,7 +1403,7 @@ func (s *xlStorage) readRaw(ctx context.Context, volume, volumeDir, filePath str
|
||||
|
||||
xlPath := pathJoin(filePath, xlStorageFormatFile)
|
||||
if readData {
|
||||
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, xlPath, true)
|
||||
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, xlPath, false)
|
||||
} else {
|
||||
buf, dmTime, err = s.readMetadataWithDMTime(ctx, xlPath)
|
||||
if err != nil {
|
||||
@@ -1421,7 +1423,7 @@ func (s *xlStorage) readRaw(ctx context.Context, volume, volumeDir, filePath str
|
||||
s.RUnlock()
|
||||
|
||||
if err != nil && errors.Is(err, errFileNotFound) && legacy {
|
||||
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFileV1), true)
|
||||
buf, dmTime, err = s.readAllData(ctx, volume, volumeDir, pathJoin(filePath, xlStorageFormatFileV1), false)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
@@ -1538,14 +1540,14 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
||||
len(fi.Parts) == 1 {
|
||||
partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number)
|
||||
dataPath := pathJoin(volumeDir, path, fi.DataDir, partPath)
|
||||
fi.Data, _, err = s.readAllData(ctx, volume, volumeDir, dataPath, true)
|
||||
fi.Data, _, err = s.readAllData(ctx, volume, volumeDir, dataPath, false)
|
||||
if err != nil {
|
||||
return FileInfo{}, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !skipAccessChecks(volume) && !opts.Healing && fi.TransitionStatus == "" && !fi.InlineData() && len(fi.Data) == 0 && fi.DataDir != "" && fi.DataDir != emptyUUID {
|
||||
if !skipAccessChecks(volume) && !opts.Healing && fi.TransitionStatus == "" && !fi.InlineData() && len(fi.Data) == 0 && fi.DataDir != "" && fi.DataDir != emptyUUID && fi.VersionPurgeStatus().Empty() {
|
||||
// Verify if the dataDir is present or not when the data
|
||||
// is not inlined to make sure we return correct errors
|
||||
// during HeadObject().
|
||||
@@ -1564,7 +1566,7 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, filePath string, sync bool) (buf []byte, dmTime time.Time, err error) {
|
||||
func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, filePath string, discard bool) (buf []byte, dmTime time.Time, err error) {
|
||||
if filePath == "" {
|
||||
return nil, dmTime, errFileNotFound
|
||||
}
|
||||
@@ -1573,13 +1575,7 @@ func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, f
|
||||
return nil, time.Time{}, ctx.Err()
|
||||
}
|
||||
|
||||
odirectEnabled := globalAPIConfig.odirectEnabled() && s.oDirect
|
||||
var f *os.File
|
||||
if odirectEnabled && sync {
|
||||
f, err = OpenFileDirectIO(filePath, readMode, 0o666)
|
||||
} else {
|
||||
f, err = OpenFile(filePath, readMode, 0o666)
|
||||
}
|
||||
f, err := OpenFile(filePath, readMode, 0o666)
|
||||
if err != nil {
|
||||
switch {
|
||||
case osIsNotExist(err):
|
||||
@@ -1614,23 +1610,20 @@ func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, f
|
||||
}
|
||||
return nil, dmTime, err
|
||||
}
|
||||
r := io.Reader(f)
|
||||
var dr *xioutil.ODirectReader
|
||||
if odirectEnabled {
|
||||
dr = &xioutil.ODirectReader{
|
||||
File: f,
|
||||
SmallFile: true,
|
||||
}
|
||||
defer dr.Close()
|
||||
r = dr
|
||||
} else {
|
||||
defer f.Close()
|
||||
|
||||
if discard {
|
||||
// This discard is mostly true for DELETEs
|
||||
// so we need to make sure we do not keep
|
||||
// page-cache references after.
|
||||
defer disk.Fdatasync(f)
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
// Get size for precise allocation.
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
buf, err = io.ReadAll(r)
|
||||
buf, err = io.ReadAll(f)
|
||||
return buf, dmTime, osErrToFileErr(err)
|
||||
}
|
||||
if stat.IsDir() {
|
||||
@@ -1645,11 +1638,9 @@ func (s *xlStorage) readAllData(ctx context.Context, volume, volumeDir string, f
|
||||
} else {
|
||||
buf = make([]byte, sz)
|
||||
}
|
||||
if dr != nil {
|
||||
dr.SmallFile = sz <= xioutil.BlockSizeSmall*2
|
||||
}
|
||||
|
||||
// Read file...
|
||||
_, err = io.ReadFull(r, buf)
|
||||
_, err = io.ReadFull(f, buf)
|
||||
|
||||
return buf, stat.ModTime().UTC(), osErrToFileErr(err)
|
||||
}
|
||||
@@ -2284,6 +2275,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
||||
errDiskNotFound,
|
||||
errUnformattedDisk,
|
||||
errMaxVersionsExceeded,
|
||||
errFileAccessDenied,
|
||||
}
|
||||
if err != nil && !IsErr(err, ignoredErrs...) && !contextCanceled(ctx) {
|
||||
// Only log these errors if context is not yet canceled.
|
||||
@@ -2291,7 +2283,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
||||
s.drivePath,
|
||||
srcVolume, srcPath,
|
||||
dstVolume, dstPath,
|
||||
err), "xl-storage-rename-data-"+srcVolume+"-"+dstVolume)
|
||||
err), "xl-storage-rename-data-"+dstVolume)
|
||||
}
|
||||
if err == nil && s.globalSync {
|
||||
globalSync()
|
||||
@@ -2780,7 +2772,7 @@ func (s *xlStorage) ReadMultiple(ctx context.Context, req ReadMultipleReq, resp
|
||||
if req.MetadataOnly {
|
||||
data, mt, err = s.readMetadataWithDMTime(ctx, fullPath)
|
||||
} else {
|
||||
data, mt, err = s.readAllData(ctx, req.Bucket, volumeDir, fullPath, false)
|
||||
data, mt, err = s.readAllData(ctx, req.Bucket, volumeDir, fullPath, true)
|
||||
}
|
||||
return err
|
||||
}); err != nil {
|
||||
@@ -2875,7 +2867,7 @@ func (s *xlStorage) CleanAbandonedData(ctx context.Context, volume string, path
|
||||
}
|
||||
baseDir := pathJoin(volumeDir, path+slashSeparator)
|
||||
metaPath := pathutil.Join(baseDir, xlStorageFormatFile)
|
||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, metaPath, false)
|
||||
buf, _, err := s.readAllData(ctx, volume, volumeDir, metaPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,20 +3,20 @@ module github.com/minio/minio/docs/debugging/inspect
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/klauspost/compress v1.15.9
|
||||
github.com/minio/colorjson v1.0.2
|
||||
github.com/minio/madmin-go/v3 v3.0.2
|
||||
github.com/klauspost/compress v1.17.4
|
||||
github.com/minio/colorjson v1.0.6
|
||||
github.com/minio/madmin-go/v3 v3.0.36
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
github.com/tinylib/msgp v1.1.8
|
||||
github.com/tinylib/msgp v1.1.9
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/fatih/color v1.13.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/minio/pkg v1.1.20 // indirect
|
||||
github.com/fatih/color v1.16.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/minio/pkg/v2 v2.0.6 // indirect
|
||||
github.com/philhofer/fwd v1.1.2 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
)
|
||||
|
||||
+23
-746
@@ -1,759 +1,36 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/decred/dcrd/chaincfg/chainhash v1.0.2/go.mod h1:BpbrGgrPTr3YJYRN3Bm+D9NuaFd+zGyNeIKgrhCXK60=
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v3 v3.0.0/go.mod h1:J70FGZSbzsjecRTiTzER+3f1KZLNaXkuv+yeFTKoxM8=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20220104163920-15ed2e8cf2bd/go.mod h1:cz9oNYuRUWGdHmLF2IodMLkAhcPtXeULvcBNagUrxTI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
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.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.7/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.2.0/go.mod h1:Tg2uP7bpxEHUDtuWjap/PxroJ4okxGzkQznXiG+a5Dc=
|
||||
github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/minio/argon2 v1.0.0/go.mod h1:XtOGJ7MjwUJDPtCqqrisx5QwVB/jDx+adQHigJVsQHQ=
|
||||
github.com/minio/colorjson v1.0.2 h1:Em3IM68MTm3h+Oxa0nxrV9VQqDgbxvC5iq5A+pqzDeI=
|
||||
github.com/minio/colorjson v1.0.2/go.mod h1:JWxcL2n8T8JVf+NY6awl6kn5nK49aAzHOeQEM33dL0k=
|
||||
github.com/minio/madmin-go v1.3.5/go.mod h1:vGKGboQgGIWx4DuDUaXixjlIEZOCIp6ivJkQoiVaACc=
|
||||
github.com/minio/madmin-go/v3 v3.0.2 h1:1KnSG90pAGUxfLseN+XAyA+P0+3ZFZfhBmgQqGpCmic=
|
||||
github.com/minio/madmin-go/v3 v3.0.2/go.mod h1:lPrMoc1aeiIWmmrxBthkDqzMPQwC/Lu9ByuyM2wenJk=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v7 v7.0.23/go.mod h1:ei5JjmxwHaMrgsMrn4U/+Nmg+d8MKS1U2DAn1ou4+Do=
|
||||
github.com/minio/pkg v1.1.20 h1:NhYtoQHw/Sl1ib/lroANFwkQspE0YyTeVR1CMPEff/A=
|
||||
github.com/minio/pkg v1.1.20/go.mod h1:Xo7LQshlxGa9shKwJ7NzQbgW4s8T/Wc1cOStR/eUiMY=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/minio/colorjson v1.0.6 h1:m7TUvpvt0u7FBmVIEQNIa0T4NBQlxrcMBp4wJKsg2Ik=
|
||||
github.com/minio/colorjson v1.0.6/go.mod h1:LUXwS5ZGNb6Eh9f+t+3uJiowD3XsIWtsvTriUBeqgYs=
|
||||
github.com/minio/madmin-go/v3 v3.0.36 h1:Ewu/Rt7WVSs9slWW+SZHRc5RPQdYAGIdNZnRr+gyN4k=
|
||||
github.com/minio/madmin-go/v3 v3.0.36/go.mod h1:4QN2NftLSV7MdlT50dkrenOMmNVHluxTvlqJou3hte8=
|
||||
github.com/minio/pkg/v2 v2.0.6 h1:n+PpbSMaJK1FfQkP55l1y0wj5Hi9R5w2DtGhxiGdP9I=
|
||||
github.com/minio/pkg/v2 v2.0.6/go.mod h1:Z9Z/LzhTIxZ6zhPeW658vmLRilRek3zBOqNB9j+lxSY=
|
||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rjeczalik/notify v0.9.2/go.mod h1:aErll2f0sUX9PXZnVNyeiObbmTlk5jnMoCa4QEjJeqM=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
||||
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
||||
github.com/shirou/gopsutil/v3 v3.21.6/go.mod h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+LwHTKj0ST88=
|
||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
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/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
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=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||
github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
|
||||
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
|
||||
github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
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=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
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=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
|
||||
@@ -2,4 +2,4 @@ module github.com/minio/minio/docs/debugging/reorder-disks
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/minio/pkg/v2 v2.0.1
|
||||
require github.com/minio/pkg/v2 v2.0.6
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
github.com/minio/pkg/v2 v2.0.1 h1:MI3xMGCxoN5EEBRp98uEU5J0LlaF+8fLPtL8oHTHLX0=
|
||||
github.com/minio/pkg/v2 v2.0.1/go.mod h1:6xTAr5M9yobpUroXAAaTrGJ9fhOZIqKYOT0I87u2yZ4=
|
||||
github.com/minio/pkg/v2 v2.0.6 h1:n+PpbSMaJK1FfQkP55l1y0wj5Hi9R5w2DtGhxiGdP9I=
|
||||
github.com/minio/pkg/v2 v2.0.6/go.mod h1:Z9Z/LzhTIxZ6zhPeW658vmLRilRek3zBOqNB9j+lxSY=
|
||||
|
||||
@@ -2,23 +2,23 @@ module github.com/minio/minio/docs/debugging/s3-verify
|
||||
|
||||
go 1.21.3
|
||||
|
||||
require github.com/minio/minio-go/v7 v7.0.63
|
||||
require github.com/minio/minio-go/v7 v7.0.66
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/sha256-simd v1.0.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
@@ -4,19 +4,19 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
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.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=
|
||||
github.com/minio/minio-go/v7 v7.0.63/go.mod h1:Q6X7Qjb7WMhvG65qKf4gUgA5XaiSox74kR1uAEjxRS4=
|
||||
github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0Dzw=
|
||||
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -34,16 +34,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
## MinIO configuration YAML
|
||||
|
||||
MinIO now supports starting the server arguments and configuration via a YAML configuration file. This YAML configuration describes everything that can be configured in a MinIO setup, such as '--address', '--console-address' and command line arguments for the MinIO server.
|
||||
|
||||
Historically everything to MinIO was provided via command arguments for the hostnames and the drives via an ellipses syntax such as `minio server http://host{1...4}/disk{1...4}` this requirement added an additional burden to have sequential hostnames for us to make sure that we can provide horizontal distribution, however we have come across situations where sometimes this is not feasible and there are no easier alternatives without modifying /etc/hosts on the host system as root user. Many times in airgapped deployments this is not allowed or requires audits and approvals.
|
||||
|
||||
MinIO server configuration file allows users to provide topology that allows for heterogenous hostnames, allowing MinIO to deployed in pre-existing environments without any further OS level configurations.
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
minio server --config config.yaml
|
||||
```
|
||||
|
||||
Lets you start MinIO server with all inputs to start MinIO server provided via this configuration file, once the configuration file is provided all other pre-existing values on disk for configuration are overriden by the new values set in this configuration file.
|
||||
|
||||
Following is an example YAML configuration structure.
|
||||
```
|
||||
version: v1
|
||||
address: ':9000'
|
||||
rootUser: 'minioadmin'
|
||||
rootPassword: 'pBU94AGAY85e'
|
||||
console-address: ':9001'
|
||||
certs-dir: '/home/user/.minio/certs/'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- 'https://server-example-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server{1...2}-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool1:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool1:9000/mnt/disk{1...4}/'
|
||||
-
|
||||
- 'https://server-example-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server{1...2}-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server3-pool2:9000/mnt/disk{1...4}/'
|
||||
- 'https://server4-pool2:9000/mnt/disk{1...4}/'
|
||||
|
||||
...
|
||||
|
||||
options:
|
||||
ftp: # settings for MinIO to act as an ftp server
|
||||
address: ':8021'
|
||||
passive-port-range: '30000-40000'
|
||||
sftp: # settings for MinIO to act as an sftp server
|
||||
address: ':8022'
|
||||
ssh-private-key: '/home/user/.ssh/id_rsa'
|
||||
```
|
||||
|
||||
### Things to know
|
||||
|
||||
- Fields such as `version` and `pools` are mandatory, however all other fields are optional.
|
||||
- Each pool expects a minimum of 2 nodes per pool, and unique non-repeating hosts for each argument.
|
||||
- Each pool expects each host in this pool has the same number of drives specified as any other host.
|
||||
- Mixing `local-path` and `distributed-path` is not allowed, doing so would cause MinIO to refuse starting the server.
|
||||
- Ellipses notation (e.g. `{1...10}`) or bracket notations are fully allowed (e.g. `{a,c,f}`) to have multiple entries in one line.
|
||||
|
||||
> NOTE: MinIO environmental variables still take precedence over the `config.yaml` file, however `config.yaml` is preffered over MinIO internal config KV settings via `mc admin config set alias/ <sub-system>`.
|
||||
|
||||
### TODO
|
||||
|
||||
In subsequent releases we are planning to extend this to provide things like
|
||||
|
||||
- Reload() of MinIO server arguments without fully restarting the process.
|
||||
|
||||
- Expanding 1 node at a time by automating the process of creating a new pool
|
||||
and decommissioning to provide a functionality that smaller deployments
|
||||
care about.
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
cleanup() {
|
||||
echo "Cleaning up instances of MinIO"
|
||||
pkill minio || true
|
||||
pkill -9 minio || true
|
||||
rm -rf /tmp/xl/ || true
|
||||
rm -rf /tmp/minio.configfile.{1,2,3,4} || true
|
||||
}
|
||||
|
||||
cleanup
|
||||
|
||||
unset MINIO_KMS_KES_CERT_FILE
|
||||
unset MINIO_KMS_KES_KEY_FILE
|
||||
unset MINIO_KMS_KES_ENDPOINT
|
||||
unset MINIO_KMS_KES_KEY_NAME
|
||||
|
||||
export MINIO_CI_CD=1
|
||||
|
||||
if [ ! -f ./mc ]; then
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
wget -O mc https://dl.minio.io/client/mc/release/${os,,}-${arch,,}/mc &&
|
||||
chmod +x mc
|
||||
fi
|
||||
|
||||
for i in $(seq 1 4); do
|
||||
s3Port="$((9000 + i))"
|
||||
consolePort="$((s3Port + 1000))"
|
||||
|
||||
cat <<EOF >/tmp/minio.configfile.$i
|
||||
version: v1
|
||||
address: ':${s3Port}'
|
||||
console-address: ':${consolePort}'
|
||||
rootUser: 'minr0otUS2r'
|
||||
rootPassword: 'pBU94AGAY85e'
|
||||
pools: # Specify the nodes and drives with pools
|
||||
-
|
||||
- 'http://localhost:9001/tmp/xl/node9001/mnt/disk{1...4}/'
|
||||
- 'http://localhost:9002/tmp/xl/node9002/mnt/disk{1,2,3,4}/'
|
||||
-
|
||||
- 'http://localhost:9003/tmp/xl/node9003/mnt/disk{1...4}/'
|
||||
- 'http://localhost:9004/tmp/xl/node9004/mnt/disk1/'
|
||||
- 'http://localhost:9004/tmp/xl/node9004/mnt/disk2/'
|
||||
- 'http://localhost:9004/tmp/xl/node9004/mnt/disk3/'
|
||||
- 'http://localhost:9004/tmp/xl/node9004/mnt/disk4/'
|
||||
EOF
|
||||
done
|
||||
|
||||
minio server --config /tmp/minio.configfile.1 >/tmp/minio1_1.log 2>&1 &
|
||||
site1_pid=$!
|
||||
minio server --config /tmp/minio.configfile.2 >/tmp/minio2_1.log 2>&1 &
|
||||
site2_pid=$!
|
||||
minio server --config /tmp/minio.configfile.3 >/tmp/minio3_1.log 2>&1 &
|
||||
site3_pid=$!
|
||||
minio server --config /tmp/minio.configfile.4 >/tmp/minio4_1.log 2>&1 &
|
||||
site4_pid=$!
|
||||
|
||||
sleep 5
|
||||
|
||||
export MC_HOST_minio1=http://minr0otUS2r:pBU94AGAY85e@localhost:9001
|
||||
export MC_HOST_minio3=http://minr0otUS2r:pBU94AGAY85e@localhost:9003
|
||||
|
||||
./mc ready minio1
|
||||
./mc ready minio3
|
||||
|
||||
./mc mb minio1/testbucket
|
||||
# copy large upload to newbucket on minio1
|
||||
truncate -s 17M lrgfile
|
||||
expected_checksum=$(cat ./lrgfile | md5sum)
|
||||
|
||||
./mc cp ./lrgfile minio1/testbucket
|
||||
|
||||
actual_checksum=$(./mc cat minio3/testbucket/lrgfile | md5sum)
|
||||
|
||||
if [ "${expected_checksum}" != "${actual_checksum}" ]; then
|
||||
echo "unexpected object checksum, expected: ${expected_checksum} got: ${actual_checksum}"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Compare the difference of the list of disks and their location, with the below exected output
|
||||
diff <(./mc admin info minio1 --json | jq -r '.info.servers[].drives[] | "\(.pool_index),\(.set_index),\(.disk_index) \(.endpoint)"' | sort) <(
|
||||
cat <<EOF
|
||||
0,0,0 http://localhost:9001/tmp/xl/node9001/mnt/disk1
|
||||
0,0,1 http://localhost:9002/tmp/xl/node9002/mnt/disk1
|
||||
0,0,2 http://localhost:9001/tmp/xl/node9001/mnt/disk2
|
||||
0,0,3 http://localhost:9002/tmp/xl/node9002/mnt/disk2
|
||||
0,0,4 http://localhost:9001/tmp/xl/node9001/mnt/disk3
|
||||
0,0,5 http://localhost:9002/tmp/xl/node9002/mnt/disk3
|
||||
0,0,6 http://localhost:9001/tmp/xl/node9001/mnt/disk4
|
||||
0,0,7 http://localhost:9002/tmp/xl/node9002/mnt/disk4
|
||||
1,0,0 http://localhost:9003/tmp/xl/node9003/mnt/disk1
|
||||
1,0,1 http://localhost:9004/tmp/xl/node9004/mnt/disk1
|
||||
1,0,2 http://localhost:9003/tmp/xl/node9003/mnt/disk2
|
||||
1,0,3 http://localhost:9004/tmp/xl/node9004/mnt/disk2
|
||||
1,0,4 http://localhost:9003/tmp/xl/node9003/mnt/disk3
|
||||
1,0,5 http://localhost:9004/tmp/xl/node9004/mnt/disk3
|
||||
1,0,6 http://localhost:9003/tmp/xl/node9003/mnt/disk4
|
||||
1,0,7 http://localhost:9004/tmp/xl/node9004/mnt/disk4
|
||||
EOF
|
||||
)
|
||||
|
||||
cleanup
|
||||
@@ -364,7 +364,7 @@
|
||||
"title": "S3 API Request 4xx Error Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -456,7 +456,7 @@
|
||||
"title": "Inflight Requests Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -548,7 +548,7 @@
|
||||
"title": "Total Requests Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -640,7 +640,7 @@
|
||||
"title": "Total Data Sent Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -732,7 +732,7 @@
|
||||
"title": "Usage Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -824,7 +824,7 @@
|
||||
"title": "Objects Increase Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -916,7 +916,7 @@
|
||||
"title": "Versions Increase Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1008,7 +1008,7 @@
|
||||
"title": "Delete Markers Increase Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1189,7 +1189,7 @@
|
||||
"title": "Total Data Received Rate",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1269,7 +1269,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum by (bucket) (rate(minio_bucket_replication_received_bytes{job=\"$scrape_jobs\"}[$__rate_interval]))",
|
||||
"expr": "sum by (bucket) (minio_bucket_replication_received_bytes{job=\"$scrape_jobs\"})",
|
||||
"interval": "1m",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "Data Received [{{bucket}}]",
|
||||
@@ -1278,10 +1278,10 @@
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeRegions": [],
|
||||
"title": "Replication Data Received Rate ",
|
||||
"title": "Replication Data Received",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1361,7 +1361,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "sum by (bucket) (rate(minio_bucket_replication_sent_bytes{job=\"$scrape_jobs\"}[$__rate_interval]))",
|
||||
"expr": "sum by (bucket) (minio_bucket_replication_sent_bytes{job=\"$scrape_jobs\"})",
|
||||
"interval": "1m",
|
||||
"intervalFactor": 2,
|
||||
"legendFormat": "Replication Data Sent [{{bucket}}]",
|
||||
@@ -1370,10 +1370,10 @@
|
||||
],
|
||||
"thresholds": [],
|
||||
"timeRegions": [],
|
||||
"title": "Replication Data Sent Rate ",
|
||||
"title": "Replication Data Sent",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1557,7 +1557,7 @@
|
||||
"title": "Replication Failed Objects",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1649,7 +1649,7 @@
|
||||
"title": "Replicated In Objects",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1741,7 +1741,7 @@
|
||||
"title": "Replicated Out Objects",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1833,7 +1833,7 @@
|
||||
"title": "Last Hour Failed Size",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -1925,7 +1925,7 @@
|
||||
"title": "Last Hour Failed Objects",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -2017,7 +2017,7 @@
|
||||
"title": "Last Minute Failed Size",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -2109,7 +2109,7 @@
|
||||
"title": "Last Minute Failed Objects",
|
||||
"tooltip": {
|
||||
"shared": true,
|
||||
"sort": 0,
|
||||
"sort": 2,
|
||||
"value_type": "individual"
|
||||
},
|
||||
"type": "graph",
|
||||
@@ -2272,4 +2272,4 @@
|
||||
"uid": "TgmJnqnnk2",
|
||||
"version": 3,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "minio_cluster_usage_total_bytes{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_cluster_usage_total_bytes{job=\"$scrape_jobs\"})",
|
||||
"interval": "",
|
||||
"legendFormat": "Usage",
|
||||
"range": true,
|
||||
@@ -575,7 +575,7 @@
|
||||
},
|
||||
"editorMode": "code",
|
||||
"exemplar": true,
|
||||
"expr": "minio_cluster_objects_size_distribution{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_cluster_objects_size_distribution{job=\"$scrape_jobs\"})",
|
||||
"format": "time_series",
|
||||
"instant": false,
|
||||
"interval": "",
|
||||
@@ -901,7 +901,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_cluster_nodes_online_total{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_cluster_nodes_online_total{job=\"$scrape_jobs\"})",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
@@ -984,7 +984,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_cluster_drive_online_total{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_cluster_drive_online_total{job=\"$scrape_jobs\"})",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
@@ -1335,7 +1335,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_cluster_nodes_offline_total{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_cluster_nodes_offline_total{job=\"$scrape_jobs\"})",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
@@ -1418,7 +1418,7 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_cluster_drive_offline_total{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_cluster_drive_offline_total{job=\"$scrape_jobs\"})",
|
||||
"format": "table",
|
||||
"hide": false,
|
||||
"instant": true,
|
||||
@@ -1574,12 +1574,12 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_heal_time_last_activity_nano_seconds{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_heal_time_last_activity_nano_seconds{job=\"$scrape_jobs\"})",
|
||||
"format": "time_series",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "{{server}}",
|
||||
"legendFormat": "",
|
||||
"metric": "process_start_time_seconds",
|
||||
"refId": "A",
|
||||
"step": 60
|
||||
@@ -1645,12 +1645,12 @@
|
||||
"uid": "${DS_PROMETHEUS}"
|
||||
},
|
||||
"exemplar": true,
|
||||
"expr": "minio_usage_last_activity_nano_seconds{job=\"$scrape_jobs\"}",
|
||||
"expr": "max(minio_usage_last_activity_nano_seconds{job=\"$scrape_jobs\"})",
|
||||
"format": "time_series",
|
||||
"instant": true,
|
||||
"interval": "",
|
||||
"intervalFactor": 1,
|
||||
"legendFormat": "{{server}}",
|
||||
"legendFormat": "",
|
||||
"metric": "process_start_time_seconds",
|
||||
"refId": "A",
|
||||
"step": 60
|
||||
@@ -4771,4 +4771,4 @@
|
||||
"uid": "TgmJnqnnk",
|
||||
"version": 1,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user