mirror of
https://github.com/pgsty/minio.git
synced 2026-11-09 09:11:51 +03:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4567160e4d | |||
| bc6a2b579b | |||
| 3c34e18a4e | |||
| b6cd54779c | |||
| bfbdb8f0a8 | |||
| 397637a042 | |||
| 20cacc3b04 | |||
| a78bc7bfdb | |||
| 3735450e7e | |||
| aa0c28809b | |||
| 471b4fd0c9 | |||
| ddcd419b4c | |||
| a9d9b520ec | |||
| 0c666379fe | |||
| a3f7d575e0 | |||
| f175ff8f66 | |||
| 39874b77ed | |||
| 7106e9394c | |||
| e124d88788 | |||
| de00b641da | |||
| e936871b83 | |||
| 922f5da8dc | |||
| 294bafd58b | |||
| 23ef25b57a | |||
| 9a31030e74 | |||
| df2871de53 | |||
| a3079a7de2 | |||
| 17a37241f0 | |||
| 026a005b64 | |||
| b865b9f43d | |||
| 209e6d00c6 | |||
| dbd7f74bb9 | |||
| 4a2cb23690 | |||
| 320e1533c4 | |||
| 7f45e80755 | |||
| 0db1c94e7d | |||
| e7a4967726 | |||
| d0963974a5 | |||
| 6ea083d197 | |||
| ab7584eeac | |||
| 69e0faa278 | |||
| fa98014bbe | |||
| c25ad94b7e | |||
| 27e07e80bc | |||
| b13bca85d0 | |||
| 29eea52e14 | |||
| b0b4696a64 | |||
| ead8778305 | |||
| eae9c2f65b | |||
| a6ec405443 | |||
| 9516587a6c | |||
| ce800ed347 | |||
| 017722cf85 | |||
| 10fc30a989 | |||
| 92ffe5e5ef | |||
| e8cbfa7af2 | |||
| b8f95fb3d4 | |||
| a4b8928660 | |||
| 4d6d4244f1 | |||
| 8d19efe7e0 | |||
| 83c37a44b6 | |||
| 9dae5a7c85 | |||
| aa78505181 | |||
| e316873f84 | |||
| 559d075627 | |||
| e25ae1c01f | |||
| a655e7f820 | |||
| c438eda624 | |||
| 2b6d0a1d84 | |||
| 3201bae773 | |||
| 8625cfdfae | |||
| bc8962102f |
@@ -32,10 +32,11 @@ jobs:
|
|||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GO111MODULE: on
|
GO111MODULE: on
|
||||||
run: |
|
run: |
|
||||||
|
sudo apt install jq -y
|
||||||
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
|
||||||
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
|
||||||
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
nancy_version=$(curl --retry 10 -Ls -o /dev/null -w "%{url_effective}" https://github.com/sonatype-nexus-community/nancy/releases/latest | sed "s/https:\/\/github.com\/sonatype-nexus-community\/nancy\/releases\/tag\///")
|
||||||
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
curl -L -o nancy https://github.com/sonatype-nexus-community/nancy/releases/download/${nancy_version}/nancy-${nancy_version}-linux-amd64 && chmod +x nancy
|
||||||
go list -m all | ./nancy sleuth
|
go list -deps -json ./... | jq -s 'unique_by(.Module.Path)|.[]|select(has("Module"))|.Module' | ./nancy sleuth
|
||||||
make
|
make
|
||||||
make test-race
|
make test-race
|
||||||
|
|||||||
+2
-1
@@ -17,7 +17,8 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_ROOT_USER_FILE=access_key \
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
|
MINIO_CONFIG_ENV_FILE=config.env
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ ENV MINIO_ACCESS_KEY_FILE=access_key \
|
|||||||
MINIO_ROOT_USER_FILE=access_key \
|
MINIO_ROOT_USER_FILE=access_key \
|
||||||
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
MINIO_ROOT_PASSWORD_FILE=secret_key \
|
||||||
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
MINIO_KMS_SECRET_KEY_FILE=kms_master_key \
|
||||||
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav"
|
MINIO_UPDATE_MINISIGN_PUBKEY="RWTx5Zr1tiHQLwG9keckT0c45M3AGeHD6IvimQHpyRywVWGbP1aVSGav" \
|
||||||
|
MINIO_CONFIG_ENV_FILE=config.env
|
||||||
|
|
||||||
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
COPY dockerscripts/verify-minio.sh /usr/bin/verify-minio.sh
|
||||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
COPY dockerscripts/docker-entrypoint.sh /usr/bin/docker-entrypoint.sh
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ for more complete documentation.
|
|||||||
Run the following command to run the latest stable image of MinIO as a container using an ephemeral data volume:
|
Run the following command to run the latest stable image of MinIO as a container using an ephemeral data volume:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
podman run -p 9000:9000 minio/minio server /data
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
|
minio/minio server /data --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded
|
The MinIO deployment starts using default root credentials `minioadmin:minioadmin`. You can test the deployment using the MinIO Console, an embedded
|
||||||
@@ -207,12 +210,17 @@ MinIO Server comes with an embedded web based object browser. Point your web bro
|
|||||||
> NOTE: MinIO runs console on random port by default if you wish choose a specific port use `--console-address` to pick a specific interface and port.
|
> NOTE: MinIO runs console on random port by default if you wish choose a specific port use `--console-address` to pick a specific interface and port.
|
||||||
|
|
||||||
### Things to consider
|
### Things to consider
|
||||||
MinIO redirects browser access requests to the configured server port (i.e. `127.0.0.1:9000`) to the configured Console port. MinIO uses the hostname or IP address specified in the request when building the redirect URI. The URL and port *must* be accessible by the client for the redirection to work
|
MinIO redirects browser access requests to the configured server port (i.e. `127.0.0.1:9000`) to the configured Console port. MinIO uses the hostname or IP address specified in the request when building the redirect URL. The URL and port *must* be accessible by the client for the redirection to work.
|
||||||
|
|
||||||
For deployments behind a load balancer, proxy, or ingress rule where the MinIO host IP address or port is not public, use the `MINIO_BROWSER_REDIRECT_URI` environment variable to specify the external hostname for the redirect. The LB/Proxy must have rules for directing traffic to the Console port specifically.
|
For deployments behind a load balancer, proxy, or ingress rule where the MinIO host IP address or port is not public, use the `MINIO_BROWSER_REDIRECT_URL` environment variable to specify the external hostname for the redirect. The LB/Proxy must have rules for directing traffic to the Console port specifically.
|
||||||
|
|
||||||
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
For example, consider a MinIO deployment behind a proxy `https://minio.example.net`, `https://console.minio.example.net` with rules for forwarding traffic on port :9000 and :9001 to MinIO and the MinIO Console respectively on the internal network. Set `MINIO_BROWSER_REDIRECT_URL` to `https://console.minio.example.net` to ensure the browser receives a valid reachable URL.
|
||||||
|
|
||||||
|
Similarly, if your TLS certificates do not have the IP SAN for the MinIO server host, the MinIO Console may fail to validate the connection to the server. Use the `MINIO_SERVER_URL` environment variable and specify the proxy-accessible hostname of the MinIO server to allow the Console to use the MinIO server API using the TLS certificate.
|
||||||
|
|
||||||
|
For example: `export MINIO_SERVER_URL="https://minio.example.net"`
|
||||||
|
|
||||||
|
|
||||||
| Dashboard | Creating a bucket |
|
| Dashboard | Creating a bucket |
|
||||||
| ------------- | ------------- |
|
| ------------- | ------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|||||||
@@ -562,10 +562,12 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ldapUsername string
|
||||||
if globalLDAPConfig.Enabled && targetUser != "" {
|
if globalLDAPConfig.Enabled && targetUser != "" {
|
||||||
// If LDAP enabled, service accounts need
|
// If LDAP enabled, service accounts need
|
||||||
// to be created only for LDAP users.
|
// to be created only for LDAP users.
|
||||||
var err error
|
var err error
|
||||||
|
ldapUsername = targetUser
|
||||||
targetUser, targetGroups, err = globalLDAPConfig.LookupUserDN(targetUser)
|
targetUser, targetGroups, err = globalLDAPConfig.LookupUserDN(targetUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -604,6 +606,9 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
|
|||||||
secretKey: createReq.SecretKey,
|
secretKey: createReq.SecretKey,
|
||||||
sessionPolicy: sp,
|
sessionPolicy: sp,
|
||||||
}
|
}
|
||||||
|
if ldapUsername != "" {
|
||||||
|
opts.ldapUsername = ldapUsername
|
||||||
|
}
|
||||||
newCred, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
newCred, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
@@ -956,6 +961,13 @@ func (a adminAPIHandlers) DeleteServiceAccount(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, nerr := range globalNotificationSys.DeleteServiceAccount(serviceAccount) {
|
||||||
|
if nerr.Err != nil {
|
||||||
|
logger.GetReqInfo(ctx).SetTags("peerAddress", nerr.Host.String())
|
||||||
|
logger.LogIf(ctx, nerr.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
writeSuccessNoContent(w)
|
writeSuccessNoContent(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -984,6 +996,9 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
// Set delimiter value for "s3:delimiter" policy conditionals.
|
// Set delimiter value for "s3:delimiter" policy conditionals.
|
||||||
r.Header.Set("delimiter", SlashSeparator)
|
r.Header.Set("delimiter", SlashSeparator)
|
||||||
|
|
||||||
|
// Check if we are asked to return prefix usage
|
||||||
|
enablePrefixUsage := r.URL.Query().Get("prefix-usage") == "true"
|
||||||
|
|
||||||
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
isAllowedAccess := func(bucketName string) (rd, wr bool) {
|
||||||
if globalIAMSys.IsAllowed(iampolicy.Args{
|
if globalIAMSys.IsAllowed(iampolicy.Args{
|
||||||
AccountName: cred.AccessKey,
|
AccountName: cred.AccessKey,
|
||||||
@@ -1080,21 +1095,32 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
|
|
||||||
acctInfo := madmin.AccountInfo{
|
acctInfo := madmin.AccountInfo{
|
||||||
AccountName: accountName,
|
AccountName: accountName,
|
||||||
|
Server: objectAPI.BackendInfo(),
|
||||||
Policy: buf,
|
Policy: buf,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bucket := range buckets {
|
for _, bucket := range buckets {
|
||||||
rd, wr := isAllowedAccess(bucket.Name)
|
rd, wr := isAllowedAccess(bucket.Name)
|
||||||
if rd || wr {
|
if rd || wr {
|
||||||
var size uint64
|
|
||||||
// Fetch the data usage of the current bucket
|
// Fetch the data usage of the current bucket
|
||||||
|
var size uint64
|
||||||
if !dataUsageInfo.LastUpdate.IsZero() {
|
if !dataUsageInfo.LastUpdate.IsZero() {
|
||||||
size = dataUsageInfo.BucketsUsage[bucket.Name].Size
|
size = dataUsageInfo.BucketsUsage[bucket.Name].Size
|
||||||
}
|
}
|
||||||
|
// Fetch the prefix usage of the current bucket
|
||||||
|
var prefixUsage map[string]uint64
|
||||||
|
if enablePrefixUsage {
|
||||||
|
if pu, err := loadPrefixUsageFromBackend(ctx, objectAPI, bucket.Name); err == nil {
|
||||||
|
prefixUsage = pu
|
||||||
|
} else {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
acctInfo.Buckets = append(acctInfo.Buckets, madmin.BucketAccessInfo{
|
acctInfo.Buckets = append(acctInfo.Buckets, madmin.BucketAccessInfo{
|
||||||
Name: bucket.Name,
|
Name: bucket.Name,
|
||||||
Created: bucket.Created,
|
Created: bucket.Created,
|
||||||
Size: size,
|
Size: size,
|
||||||
|
PrefixUsage: prefixUsage,
|
||||||
Access: madmin.AccountAccess{
|
Access: madmin.AccountAccess{
|
||||||
Read: rd,
|
Read: rd,
|
||||||
Write: wr,
|
Write: wr,
|
||||||
|
|||||||
+341
-23
@@ -25,18 +25,21 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
humanize "github.com/dustin/go-humanize"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/klauspost/compress/zip"
|
"github.com/klauspost/compress/zip"
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes"
|
||||||
@@ -906,6 +909,50 @@ func (a adminAPIHandlers) BackgroundHealStatusHandler(w http.ResponseWriter, r *
|
|||||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a adminAPIHandlers) SpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := newContext(r, w, "SpeedtestHandler")
|
||||||
|
|
||||||
|
defer logger.AuditLog(ctx, w, r, mustGetClaimsFromToken(r))
|
||||||
|
|
||||||
|
objectAPI, _ := validateAdminReq(ctx, w, r, iampolicy.HealAdminAction)
|
||||||
|
if objectAPI == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !globalIsErasure {
|
||||||
|
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrNotImplemented), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeStr := r.URL.Query().Get(peerRESTSize)
|
||||||
|
durationStr := r.URL.Query().Get(peerRESTDuration)
|
||||||
|
concurrentStr := r.URL.Query().Get(peerRESTConcurrent)
|
||||||
|
|
||||||
|
size, err := strconv.Atoi(sizeStr)
|
||||||
|
if err != nil {
|
||||||
|
size = 64 * humanize.MiByte
|
||||||
|
}
|
||||||
|
|
||||||
|
concurrent, err := strconv.Atoi(concurrentStr)
|
||||||
|
if err != nil {
|
||||||
|
concurrent = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
duration, err := time.ParseDuration(durationStr)
|
||||||
|
if err != nil {
|
||||||
|
duration = time.Second * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
results := globalNotificationSys.Speedtest(ctx, size, concurrent, duration)
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(results); err != nil {
|
||||||
|
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
objectAPI.DeleteBucket(ctx, pathJoin(minioMetaSpeedTestBucket, minioMetaSpeedTestBucketPrefix), true)
|
||||||
|
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
@@ -1184,8 +1231,14 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err := enc.Encode(entry); err != nil {
|
if err := enc.Encode(entry); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.(http.Flusher).Flush()
|
if len(traceCh) == 0 {
|
||||||
|
// Flush if nothing is queued
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
case <-keepAliveTicker.C:
|
case <-keepAliveTicker.C:
|
||||||
|
if len(traceCh) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, err := w.Write([]byte(" ")); err != nil {
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1255,9 +1308,15 @@ func (a adminAPIHandlers) ConsoleLogHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
if err := enc.Encode(log); err != nil {
|
if err := enc.Encode(log); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if len(logCh) == 0 {
|
||||||
|
// Flush if nothing is queued
|
||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
case <-keepAliveTicker.C:
|
case <-keepAliveTicker.C:
|
||||||
|
if len(logCh) > 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if _, err := w.Write([]byte(" ")); err != nil {
|
if _, err := w.Write([]byte(" ")); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1561,41 +1620,164 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
defer nsLock.Unlock(lkctx.Cancel)
|
defer nsLock.Unlock(lkctx.Cancel)
|
||||||
|
|
||||||
go func() {
|
hostAnonymizer := createHostAnonymizer()
|
||||||
defer close(healthInfoCh)
|
// anonAddr - Anonymizes hosts in given input string.
|
||||||
|
anonAddr := func(addr string) string {
|
||||||
|
newAddr, found := hostAnonymizer[addr]
|
||||||
|
if found {
|
||||||
|
return newAddr
|
||||||
|
}
|
||||||
|
|
||||||
partialWrite(healthInfo) // Write first message with only version populated
|
// If we reach here, it means that the given addr doesn't contain any of the hosts.
|
||||||
|
// Return it as is. Can happen for drive paths in non-distributed mode
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// anonymizedAddr - Updated the addr of the node info with anonymized one
|
||||||
|
anonymizeAddr := func(info madmin.NodeInfo) {
|
||||||
|
info.SetAddr(anonAddr(info.GetAddr()))
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteCPUs := func() {
|
||||||
if query.Get("syscpu") == "true" {
|
if query.Get("syscpu") == "true" {
|
||||||
healthInfo.Sys.CPUInfo = append(healthInfo.Sys.CPUInfo, madmin.GetCPUs(deadlinedCtx, r.Host))
|
localCPUInfo := madmin.GetCPUs(deadlinedCtx, globalLocalNodeName)
|
||||||
healthInfo.Sys.CPUInfo = append(healthInfo.Sys.CPUInfo, globalNotificationSys.GetCPUs(deadlinedCtx)...)
|
anonymizeAddr(&localCPUInfo)
|
||||||
|
healthInfo.Sys.CPUInfo = append(healthInfo.Sys.CPUInfo, localCPUInfo)
|
||||||
|
|
||||||
|
peerCPUInfo := globalNotificationSys.GetCPUs(deadlinedCtx)
|
||||||
|
for _, cpuInfo := range peerCPUInfo {
|
||||||
|
anonymizeAddr(&cpuInfo)
|
||||||
|
healthInfo.Sys.CPUInfo = append(healthInfo.Sys.CPUInfo, cpuInfo)
|
||||||
|
}
|
||||||
|
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWritePartitions := func() {
|
||||||
if query.Get("sysdrivehw") == "true" {
|
if query.Get("sysdrivehw") == "true" {
|
||||||
healthInfo.Sys.Partitions = append(healthInfo.Sys.Partitions, madmin.GetPartitions(deadlinedCtx, r.Host))
|
localPartitions := madmin.GetPartitions(deadlinedCtx, globalLocalNodeName)
|
||||||
healthInfo.Sys.Partitions = append(healthInfo.Sys.Partitions, globalNotificationSys.GetPartitions(deadlinedCtx)...)
|
anonymizeAddr(&localPartitions)
|
||||||
|
healthInfo.Sys.Partitions = append(healthInfo.Sys.Partitions, localPartitions)
|
||||||
|
|
||||||
|
peerPartitions := globalNotificationSys.GetPartitions(deadlinedCtx)
|
||||||
|
for _, p := range peerPartitions {
|
||||||
|
anonymizeAddr(&p)
|
||||||
|
healthInfo.Sys.Partitions = append(healthInfo.Sys.Partitions, p)
|
||||||
|
}
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteOSInfo := func() {
|
||||||
if query.Get("sysosinfo") == "true" {
|
if query.Get("sysosinfo") == "true" {
|
||||||
healthInfo.Sys.OSInfo = append(healthInfo.Sys.OSInfo, madmin.GetOSInfo(deadlinedCtx, r.Host))
|
localOSInfo := madmin.GetOSInfo(deadlinedCtx, globalLocalNodeName)
|
||||||
healthInfo.Sys.OSInfo = append(healthInfo.Sys.OSInfo, globalNotificationSys.GetOSInfo(deadlinedCtx)...)
|
anonymizeAddr(&localOSInfo)
|
||||||
|
healthInfo.Sys.OSInfo = append(healthInfo.Sys.OSInfo, localOSInfo)
|
||||||
|
|
||||||
|
peerOSInfos := globalNotificationSys.GetOSInfo(deadlinedCtx)
|
||||||
|
for _, o := range peerOSInfos {
|
||||||
|
anonymizeAddr(&o)
|
||||||
|
healthInfo.Sys.OSInfo = append(healthInfo.Sys.OSInfo, o)
|
||||||
|
}
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteMemInfo := func() {
|
||||||
if query.Get("sysmem") == "true" {
|
if query.Get("sysmem") == "true" {
|
||||||
healthInfo.Sys.MemInfo = append(healthInfo.Sys.MemInfo, madmin.GetMemInfo(deadlinedCtx, r.Host))
|
localMemInfo := madmin.GetMemInfo(deadlinedCtx, globalLocalNodeName)
|
||||||
healthInfo.Sys.MemInfo = append(healthInfo.Sys.MemInfo, globalNotificationSys.GetMemInfo(deadlinedCtx)...)
|
anonymizeAddr(&localMemInfo)
|
||||||
|
healthInfo.Sys.MemInfo = append(healthInfo.Sys.MemInfo, localMemInfo)
|
||||||
|
|
||||||
|
peerMemInfos := globalNotificationSys.GetMemInfo(deadlinedCtx)
|
||||||
|
for _, m := range peerMemInfos {
|
||||||
|
anonymizeAddr(&m)
|
||||||
|
healthInfo.Sys.MemInfo = append(healthInfo.Sys.MemInfo, m)
|
||||||
|
}
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anonymizeCmdLine := func(cmdLine string) string {
|
||||||
|
if !globalIsDistErasure {
|
||||||
|
// FS mode - single server - hard code to `server1`
|
||||||
|
anonCmdLine := strings.Replace(cmdLine, globalLocalNodeName, "server1", -1)
|
||||||
|
return strings.Replace(anonCmdLine, globalMinioConsoleHost, "server1", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server start command regex groups:
|
||||||
|
// 1 - minio server
|
||||||
|
// 2 - flags e.g. `--address :9000 --certs-dir /etc/minio/certs`
|
||||||
|
// 3 - pool args e.g. `https://node{01...16}.domain/data/disk{001...204} https://node{17...32}.domain/data/disk{001...204}`
|
||||||
|
re := regexp.MustCompile(`^(.*minio\s+server\s+)(--[^\s]+\s+[^\s]+\s+)*(.*)`)
|
||||||
|
|
||||||
|
// stays unchanged in the anonymized version
|
||||||
|
cmdLineWithoutPools := re.ReplaceAllString(cmdLine, `$1$2`)
|
||||||
|
|
||||||
|
// to be anonymized
|
||||||
|
poolsArgs := re.ReplaceAllString(cmdLine, `$3`)
|
||||||
|
var anonPools []string
|
||||||
|
|
||||||
|
if !(strings.Contains(poolsArgs, "{") && strings.Contains(poolsArgs, "}")) {
|
||||||
|
// No ellipses pattern. Anonymize host name from every pool arg
|
||||||
|
pools := strings.Fields(poolsArgs)
|
||||||
|
anonPools = make([]string, len(pools))
|
||||||
|
for _, arg := range pools {
|
||||||
|
anonPools = append(anonPools, anonAddr(arg))
|
||||||
|
}
|
||||||
|
return cmdLineWithoutPools + strings.Join(anonPools, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ellipses pattern in pool args. Regex groups:
|
||||||
|
// 1 - server prefix
|
||||||
|
// 2 - number sequence for servers
|
||||||
|
// 3 - server suffix
|
||||||
|
// 4 - drive prefix (starting with /)
|
||||||
|
// 5 - number sequence for drives
|
||||||
|
// 6 - drive suffix
|
||||||
|
re = regexp.MustCompile(`([^\s^{]*)({\d+...\d+})?([^\s^{^/]*)(/[^\s^{]*)({\d+...\d+})?([^\s]*)`)
|
||||||
|
poolsMatches := re.FindAllStringSubmatch(poolsArgs, -1)
|
||||||
|
|
||||||
|
anonPools = make([]string, len(poolsMatches))
|
||||||
|
idxMap := map[int]string{
|
||||||
|
1: "spfx",
|
||||||
|
3: "ssfx",
|
||||||
|
}
|
||||||
|
for pi, poolsMatch := range poolsMatches {
|
||||||
|
// Replace the server prefix/suffix with anonymized ones
|
||||||
|
for idx, lbl := range idxMap {
|
||||||
|
if len(poolsMatch[idx]) > 0 {
|
||||||
|
poolsMatch[idx] = fmt.Sprintf("%s%d", lbl, crc32.ChecksumIEEE([]byte(poolsMatch[idx])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the original pools args present at index 0
|
||||||
|
anonPools[pi] = strings.Join(poolsMatch[1:], "")
|
||||||
|
}
|
||||||
|
return cmdLineWithoutPools + strings.Join(anonPools, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
anonymizeProcInfo := func(p *madmin.ProcInfo) {
|
||||||
|
p.CmdLine = anonymizeCmdLine(p.CmdLine)
|
||||||
|
anonymizeAddr(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteProcInfo := func() {
|
||||||
if query.Get("sysprocess") == "true" {
|
if query.Get("sysprocess") == "true" {
|
||||||
healthInfo.Sys.ProcInfo = append(healthInfo.Sys.ProcInfo, madmin.GetProcInfo(deadlinedCtx, r.Host))
|
localProcInfo := madmin.GetProcInfo(deadlinedCtx, globalLocalNodeName)
|
||||||
healthInfo.Sys.ProcInfo = append(healthInfo.Sys.ProcInfo, globalNotificationSys.GetProcInfo(deadlinedCtx)...)
|
anonymizeProcInfo(&localProcInfo)
|
||||||
|
healthInfo.Sys.ProcInfo = append(healthInfo.Sys.ProcInfo, localProcInfo)
|
||||||
|
peerProcInfos := globalNotificationSys.GetProcInfo(deadlinedCtx)
|
||||||
|
for _, p := range peerProcInfos {
|
||||||
|
anonymizeProcInfo(&p)
|
||||||
|
healthInfo.Sys.ProcInfo = append(healthInfo.Sys.ProcInfo, p)
|
||||||
|
}
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteMinioConfig := func() {
|
||||||
if query.Get("minioconfig") == "true" {
|
if query.Get("minioconfig") == "true" {
|
||||||
config, err := readServerConfig(ctx, objectAPI)
|
config, err := readServerConfig(ctx, objectAPI)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1609,47 +1791,101 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
|
|||||||
}
|
}
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteDrivePerfInfo := func() {
|
||||||
if query.Get("perfdrive") == "true" {
|
if query.Get("perfdrive") == "true" {
|
||||||
healthInfo.Perf.Drives = append(healthInfo.Perf.Drives, getDrivePerfInfos(deadlinedCtx, r.Host))
|
localDPI := getDrivePerfInfos(deadlinedCtx, globalLocalNodeName)
|
||||||
|
anonymizeAddr(&localDPI)
|
||||||
|
healthInfo.Perf.Drives = append(healthInfo.Perf.Drives, localDPI)
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
|
|
||||||
perfCh := globalNotificationSys.GetDrivePerfInfos(deadlinedCtx)
|
perfCh := globalNotificationSys.GetDrivePerfInfos(deadlinedCtx)
|
||||||
for perfInfo := range perfCh {
|
for perfInfo := range perfCh {
|
||||||
|
anonymizeAddr(&perfInfo)
|
||||||
healthInfo.Perf.Drives = append(healthInfo.Perf.Drives, perfInfo)
|
healthInfo.Perf.Drives = append(healthInfo.Perf.Drives, perfInfo)
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
partialWrite(healthInfo)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anonymizeNetPerfInfo := func(npi *madmin.NetPerfInfo) {
|
||||||
|
anonymizeAddr(npi)
|
||||||
|
rps := npi.RemotePeers
|
||||||
|
for idx, peer := range rps {
|
||||||
|
anonymizeAddr(&peer)
|
||||||
|
rps[idx] = peer
|
||||||
|
}
|
||||||
|
npi.RemotePeers = rps
|
||||||
|
}
|
||||||
|
|
||||||
|
getAndWriteNetPerfInfo := func() {
|
||||||
if globalIsDistErasure && query.Get("perfnet") == "true" {
|
if globalIsDistErasure && query.Get("perfnet") == "true" {
|
||||||
healthInfo.Perf.Net = append(healthInfo.Perf.Net, globalNotificationSys.GetNetPerfInfo(deadlinedCtx))
|
localNPI := globalNotificationSys.GetNetPerfInfo(deadlinedCtx)
|
||||||
|
anonymizeNetPerfInfo(&localNPI)
|
||||||
|
healthInfo.Perf.Net = append(healthInfo.Perf.Net, localNPI)
|
||||||
|
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
|
|
||||||
netInfos := globalNotificationSys.DispatchNetPerfChan(deadlinedCtx)
|
netInfos := globalNotificationSys.DispatchNetPerfChan(deadlinedCtx)
|
||||||
for netInfo := range netInfos {
|
for netInfo := range netInfos {
|
||||||
|
anonymizeNetPerfInfo(&netInfo)
|
||||||
healthInfo.Perf.Net = append(healthInfo.Perf.Net, netInfo)
|
healthInfo.Perf.Net = append(healthInfo.Perf.Net, netInfo)
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
partialWrite(healthInfo)
|
|
||||||
|
|
||||||
healthInfo.Perf.NetParallel = globalNotificationSys.GetParallelNetPerfInfo(deadlinedCtx)
|
ppi := globalNotificationSys.GetParallelNetPerfInfo(deadlinedCtx)
|
||||||
|
anonymizeNetPerfInfo(&ppi)
|
||||||
|
healthInfo.Perf.NetParallel = ppi
|
||||||
partialWrite(healthInfo)
|
partialWrite(healthInfo)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anonymizeNetwork := func(network map[string]string) map[string]string {
|
||||||
|
anonNetwork := map[string]string{}
|
||||||
|
for endpoint, status := range network {
|
||||||
|
anonEndpoint := anonAddr(endpoint)
|
||||||
|
anonNetwork[anonEndpoint] = status
|
||||||
|
}
|
||||||
|
return anonNetwork
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
anonymizeDrives := func(drives []madmin.Disk) []madmin.Disk {
|
||||||
|
anonDrives := []madmin.Disk{}
|
||||||
|
for _, drive := range drives {
|
||||||
|
drive.Endpoint = anonAddr(drive.Endpoint)
|
||||||
|
anonDrives = append(anonDrives, drive)
|
||||||
|
}
|
||||||
|
return anonDrives
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(healthInfoCh)
|
||||||
|
|
||||||
|
partialWrite(healthInfo) // Write first message with only version populated
|
||||||
|
getAndWriteCPUs()
|
||||||
|
getAndWritePartitions()
|
||||||
|
getAndWriteOSInfo()
|
||||||
|
getAndWriteMemInfo()
|
||||||
|
getAndWriteProcInfo()
|
||||||
|
getAndWriteMinioConfig()
|
||||||
|
getAndWriteDrivePerfInfo()
|
||||||
|
getAndWriteNetPerfInfo()
|
||||||
|
|
||||||
if query.Get("minioinfo") == "true" {
|
if query.Get("minioinfo") == "true" {
|
||||||
infoMessage := getServerInfo(ctx, r)
|
infoMessage := getServerInfo(ctx, r)
|
||||||
servers := []madmin.ServerInfo{}
|
servers := []madmin.ServerInfo{}
|
||||||
for _, server := range infoMessage.Servers {
|
for _, server := range infoMessage.Servers {
|
||||||
|
anonEndpoint := anonAddr(server.Endpoint)
|
||||||
servers = append(servers, madmin.ServerInfo{
|
servers = append(servers, madmin.ServerInfo{
|
||||||
State: server.State,
|
State: server.State,
|
||||||
Endpoint: server.Endpoint,
|
Endpoint: anonEndpoint,
|
||||||
Uptime: server.Uptime,
|
Uptime: server.Uptime,
|
||||||
Version: server.Version,
|
Version: server.Version,
|
||||||
CommitID: server.CommitID,
|
CommitID: server.CommitID,
|
||||||
Network: server.Network,
|
Network: anonymizeNetwork(server.Network),
|
||||||
Drives: server.Disks,
|
Drives: anonymizeDrives(server.Disks),
|
||||||
PoolNumber: server.PoolNumber,
|
PoolNumber: server.PoolNumber,
|
||||||
MemStats: madmin.MemStats{
|
MemStats: madmin.MemStats{
|
||||||
Alloc: server.MemStats.Alloc,
|
Alloc: server.MemStats.Alloc,
|
||||||
@@ -2055,3 +2291,85 @@ func (a adminAPIHandlers) InspectDataHandler(w http.ResponseWriter, r *http.Requ
|
|||||||
})
|
})
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createHostAnonymizerForFSMode() map[string]string {
|
||||||
|
hostAnonymizer := map[string]string{
|
||||||
|
globalLocalNodeName: "server1",
|
||||||
|
}
|
||||||
|
|
||||||
|
apiEndpoints := getAPIEndpoints()
|
||||||
|
for _, ep := range apiEndpoints {
|
||||||
|
if len(ep) > 0 {
|
||||||
|
if url, err := xnet.ParseHTTPURL(ep); err == nil {
|
||||||
|
// In FS mode the drive names don't include the host.
|
||||||
|
// So mapping just the host should be sufficient.
|
||||||
|
hostAnonymizer[url.Host] = "server1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostAnonymizer
|
||||||
|
}
|
||||||
|
|
||||||
|
// anonymizeHost - Add entries related to given endpoint in the host anonymizer map
|
||||||
|
// The health report data can contain the hostname in various forms e.g. host, host:port,
|
||||||
|
// host:port/drivepath, full url (http://host:port/drivepath)
|
||||||
|
// The anonymizer map will have mappings for all these varients for efficiently replacing
|
||||||
|
// any of these strings to the anonymized versions at the time of health report generation.
|
||||||
|
func anonymizeHost(hostAnonymizer map[string]string, endpoint Endpoint, poolNum int, srvrNum int) {
|
||||||
|
if len(endpoint.Host) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentURL := endpoint.String()
|
||||||
|
|
||||||
|
// mapIfNotPresent - Maps the given key to the value only if the key is not present in the map
|
||||||
|
mapIfNotPresent := func(m map[string]string, key string, val string) {
|
||||||
|
_, found := m[key]
|
||||||
|
if !found {
|
||||||
|
m[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, found := hostAnonymizer[currentURL]
|
||||||
|
if !found {
|
||||||
|
// In distributed setup, anonymized addr = 'poolNum.serverNum'
|
||||||
|
newHost := fmt.Sprintf("pool%d.server%d", poolNum, srvrNum)
|
||||||
|
|
||||||
|
// Hostname
|
||||||
|
mapIfNotPresent(hostAnonymizer, endpoint.Hostname(), newHost)
|
||||||
|
|
||||||
|
newHostPort := newHost
|
||||||
|
if len(endpoint.Port()) > 0 {
|
||||||
|
// Host + port
|
||||||
|
newHostPort = newHost + ":" + endpoint.Port()
|
||||||
|
mapIfNotPresent(hostAnonymizer, endpoint.Host, newHostPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
newHostPortPath := newHostPort
|
||||||
|
if len(endpoint.Path) > 0 {
|
||||||
|
// Host + port + path
|
||||||
|
currentHostPortPath := endpoint.Host + endpoint.Path
|
||||||
|
newHostPortPath = newHostPort + endpoint.Path
|
||||||
|
mapIfNotPresent(hostAnonymizer, currentHostPortPath, newHostPortPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full url
|
||||||
|
hostAnonymizer[currentURL] = endpoint.Scheme + "://" + newHostPortPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createHostAnonymizer - Creats a map of various strings to corresponding anonymized names
|
||||||
|
func createHostAnonymizer() map[string]string {
|
||||||
|
if !globalIsDistErasure {
|
||||||
|
return createHostAnonymizerForFSMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
hostAnonymizer := map[string]string{}
|
||||||
|
|
||||||
|
for poolIdx, pool := range globalEndpoints {
|
||||||
|
for srvrIdx, endpoint := range pool.Endpoints {
|
||||||
|
anonymizeHost(hostAnonymizer, endpoint, poolIdx+1, srvrIdx+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hostAnonymizer
|
||||||
|
}
|
||||||
|
|||||||
+74
-60
@@ -21,6 +21,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/klauspost/compress/gzhttp"
|
||||||
|
"github.com/klauspost/compress/gzip"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,173 +48,185 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
|
|||||||
adminAPIVersionPrefix,
|
adminAPIVersionPrefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gz := func(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
||||||
|
if err == nil {
|
||||||
|
gz = func(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return wrapper(h).(http.HandlerFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, adminVersion := range adminVersions {
|
for _, adminVersion := range adminVersions {
|
||||||
// Restart and stop MinIO service.
|
// Restart and stop MinIO service.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(httpTraceAll(adminAPI.ServiceHandler)).Queries("action", "{action:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/service").HandlerFunc(gz(httpTraceAll(adminAPI.ServiceHandler))).Queries("action", "{action:.*}")
|
||||||
// Update MinIO servers.
|
// Update MinIO servers.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(httpTraceAll(adminAPI.ServerUpdateHandler)).Queries("updateURL", "{updateURL:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update").HandlerFunc(gz(httpTraceAll(adminAPI.ServerUpdateHandler))).Queries("updateURL", "{updateURL:.*}")
|
||||||
|
|
||||||
// Info operations
|
// Info operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(httpTraceAll(adminAPI.ServerInfoHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/info").HandlerFunc(gz(httpTraceAll(adminAPI.ServerInfoHandler)))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/inspect-data").HandlerFunc(httpTraceHdrs(adminAPI.InspectDataHandler)).Queries("volume", "{volume:.*}", "file", "{file:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/inspect-data").HandlerFunc(httpTraceHdrs(adminAPI.InspectDataHandler)).Queries("volume", "{volume:.*}", "file", "{file:.*}")
|
||||||
|
|
||||||
// StorageInfo operations
|
// StorageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(httpTraceAll(adminAPI.StorageInfoHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/storageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.StorageInfoHandler)))
|
||||||
// DataUsageInfo operations
|
// DataUsageInfo operations
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(httpTraceAll(adminAPI.DataUsageInfoHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/datausageinfo").HandlerFunc(gz(httpTraceAll(adminAPI.DataUsageInfoHandler)))
|
||||||
|
|
||||||
if globalIsDistErasure || globalIsErasure {
|
if globalIsDistErasure || globalIsErasure {
|
||||||
/// Heal operations
|
/// Heal operations
|
||||||
|
|
||||||
// Heal processing endpoint.
|
// Heal processing endpoint.
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(httpTraceAll(adminAPI.HealHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/heal/{bucket}/{prefix:.*}").HandlerFunc(gz(httpTraceAll(adminAPI.HealHandler)))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(httpTraceAll(adminAPI.BackgroundHealStatusHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/background-heal/status").HandlerFunc(gz(httpTraceAll(adminAPI.BackgroundHealStatusHandler)))
|
||||||
|
|
||||||
/// Health operations
|
/// Health operations
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profiling operations
|
// Profiling operations
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(httpTraceAll(adminAPI.StartProfilingHandler)).
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/profiling/start").HandlerFunc(gz(httpTraceAll(adminAPI.StartProfilingHandler))).
|
||||||
Queries("profilerType", "{profilerType:.*}")
|
Queries("profilerType", "{profilerType:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(httpTraceAll(adminAPI.DownloadProfilingHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/profiling/download").HandlerFunc(gz(httpTraceAll(adminAPI.DownloadProfilingHandler)))
|
||||||
|
|
||||||
// Config KV operations.
|
// Config KV operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigKVHandler)).Queries("key", "{key:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigKVHandler))).Queries("key", "{key:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigKVHandler))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/set-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigKVHandler)))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(httpTraceHdrs(adminAPI.DelConfigKVHandler))
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/del-config-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.DelConfigKVHandler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable config help in all modes.
|
// Enable config help in all modes.
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(httpTraceAll(adminAPI.HelpConfigKVHandler)).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/help-config-kv").HandlerFunc(gz(httpTraceAll(adminAPI.HelpConfigKVHandler))).Queries("subSys", "{subSys:.*}", "key", "{key:.*}")
|
||||||
|
|
||||||
// Config KV history operations.
|
// Config KV history operations.
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(httpTraceAll(adminAPI.ListConfigHistoryKVHandler)).Queries("count", "{count:[0-9]+}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-config-history-kv").HandlerFunc(gz(httpTraceAll(adminAPI.ListConfigHistoryKVHandler))).Queries("count", "{count:[0-9]+}")
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(httpTraceHdrs(adminAPI.ClearConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/clear-config-history-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.ClearConfigHistoryKVHandler))).Queries("restoreId", "{restoreId:.*}")
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(httpTraceHdrs(adminAPI.RestoreConfigHistoryKVHandler)).Queries("restoreId", "{restoreId:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/restore-config-history-kv").HandlerFunc(gz(httpTraceHdrs(adminAPI.RestoreConfigHistoryKVHandler))).Queries("restoreId", "{restoreId:.*}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Config import/export bulk operations
|
/// Config import/export bulk operations
|
||||||
if enableConfigOps {
|
if enableConfigOps {
|
||||||
// Get config
|
// Get config
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(httpTraceHdrs(adminAPI.GetConfigHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/config").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetConfigHandler)))
|
||||||
// Set config
|
// Set config
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(httpTraceHdrs(adminAPI.SetConfigHandler))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/config").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetConfigHandler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- IAM APIs --
|
// -- IAM APIs --
|
||||||
|
|
||||||
// Add policy IAM
|
// Add policy IAM
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(httpTraceAll(adminAPI.AddCannedPolicy)).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-canned-policy").HandlerFunc(gz(httpTraceAll(adminAPI.AddCannedPolicy))).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Add user IAM
|
// Add user IAM
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(httpTraceAll(adminAPI.AccountInfoHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/accountinfo").HandlerFunc(gz(httpTraceAll(adminAPI.AccountInfoHandler)))
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(httpTraceHdrs(adminAPI.AddUser)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/add-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddUser))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(httpTraceHdrs(adminAPI.SetUserStatus)).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetUserStatus))).Queries("accessKey", "{accessKey:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
// Service accounts ops
|
// Service accounts ops
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(httpTraceHdrs(adminAPI.AddServiceAccount))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/add-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddServiceAccount)))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(httpTraceHdrs(adminAPI.UpdateServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/update-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-service-account").HandlerFunc(httpTraceHdrs(adminAPI.InfoServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(httpTraceHdrs(adminAPI.ListServiceAccounts))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-service-accounts").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListServiceAccounts)))
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(httpTraceHdrs(adminAPI.DeleteServiceAccount)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/delete-service-account").HandlerFunc(gz(httpTraceHdrs(adminAPI.DeleteServiceAccount))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// Info policy IAM latest
|
// Info policy IAM latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.InfoCannedPolicy)).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/info-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.InfoCannedPolicy))).Queries("name", "{name:.*}")
|
||||||
// List policies latest
|
// List policies latest
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListBucketPolicies)).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketPolicies))).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(httpTraceHdrs(adminAPI.ListCannedPolicies))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-canned-policies").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListCannedPolicies)))
|
||||||
|
|
||||||
// Remove policy IAM
|
// Remove policy IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(httpTraceHdrs(adminAPI.RemoveCannedPolicy)).Queries("name", "{name:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-canned-policy").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveCannedPolicy))).Queries("name", "{name:.*}")
|
||||||
|
|
||||||
// Set user or group policy
|
// Set user or group policy
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-user-or-group-policy").
|
||||||
HandlerFunc(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup)).
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.SetPolicyForUserOrGroup))).
|
||||||
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
Queries("policyName", "{policyName:.*}", "userOrGroup", "{userOrGroup:.*}", "isGroup", "{isGroup:true|false}")
|
||||||
|
|
||||||
// Remove user IAM
|
// Remove user IAM
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(httpTraceHdrs(adminAPI.RemoveUser)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-user").HandlerFunc(gz(httpTraceHdrs(adminAPI.RemoveUser))).Queries("accessKey", "{accessKey:.*}")
|
||||||
|
|
||||||
// List users
|
// List users
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListBucketUsers)).Queries("bucket", "{bucket:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-users").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListBucketUsers))).Queries("bucket", "{bucket:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(httpTraceHdrs(adminAPI.ListUsers))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-users").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListUsers)))
|
||||||
|
|
||||||
// User info
|
// User info
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(httpTraceHdrs(adminAPI.GetUserInfo)).Queries("accessKey", "{accessKey:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/user-info").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetUserInfo))).Queries("accessKey", "{accessKey:.*}")
|
||||||
// Add/Remove members from group
|
// Add/Remove members from group
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(httpTraceHdrs(adminAPI.UpdateGroupMembers))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/update-group-members").HandlerFunc(gz(httpTraceHdrs(adminAPI.UpdateGroupMembers)))
|
||||||
|
|
||||||
// Get Group
|
// Get Group
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(httpTraceHdrs(adminAPI.GetGroup)).Queries("group", "{group:.*}")
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/group").HandlerFunc(gz(httpTraceHdrs(adminAPI.GetGroup))).Queries("group", "{group:.*}")
|
||||||
|
|
||||||
// List Groups
|
// List Groups
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(httpTraceHdrs(adminAPI.ListGroups))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/groups").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListGroups)))
|
||||||
|
|
||||||
// Set Group Status
|
// Set Group Status
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(httpTraceHdrs(adminAPI.SetGroupStatus)).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-group-status").HandlerFunc(gz(httpTraceHdrs(adminAPI.SetGroupStatus))).Queries("group", "{group:.*}").Queries("status", "{status:.*}")
|
||||||
|
|
||||||
if globalIsDistErasure || globalIsErasure {
|
if globalIsDistErasure || globalIsErasure {
|
||||||
// GetBucketQuotaConfig
|
// GetBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/get-bucket-quota").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
gz(httpTraceHdrs(adminAPI.GetBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
// PutBucketQuotaConfig
|
// PutBucketQuotaConfig
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-bucket-quota").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler)).Queries("bucket", "{bucket:.*}")
|
gz(httpTraceHdrs(adminAPI.PutBucketQuotaConfigHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
|
|
||||||
// Bucket replication operations
|
// Bucket replication operations
|
||||||
// GetBucketTargetHandler
|
// GetBucketTargetHandler
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/list-remote-targets").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.ListRemoteTargetsHandler)).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
gz(httpTraceHdrs(adminAPI.ListRemoteTargetsHandler))).Queries("bucket", "{bucket:.*}", "type", "{type:.*}")
|
||||||
// SetRemoteTargetHandler
|
// SetRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodPut).Path(adminVersion+"/set-remote-target").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.SetRemoteTargetHandler)).Queries("bucket", "{bucket:.*}")
|
gz(httpTraceHdrs(adminAPI.SetRemoteTargetHandler))).Queries("bucket", "{bucket:.*}")
|
||||||
// RemoveRemoteTargetHandler
|
// RemoveRemoteTargetHandler
|
||||||
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
adminRouter.Methods(http.MethodDelete).Path(adminVersion+"/remove-remote-target").HandlerFunc(
|
||||||
httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler)).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
gz(httpTraceHdrs(adminAPI.RemoveRemoteTargetHandler))).Queries("bucket", "{bucket:.*}", "arn", "{arn:.*}")
|
||||||
|
|
||||||
// Remote Tier management operations
|
// Remote Tier management operations
|
||||||
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(httpTraceHdrs(adminAPI.AddTierHandler))
|
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.AddTierHandler)))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(httpTraceHdrs(adminAPI.EditTierHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/tier/{tier}").HandlerFunc(gz(httpTraceHdrs(adminAPI.EditTierHandler)))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(httpTraceHdrs(adminAPI.ListTierHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/tier").HandlerFunc(gz(httpTraceHdrs(adminAPI.ListTierHandler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
// Top locks
|
// Top locks
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(httpTraceHdrs(adminAPI.TopLocksHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/top/locks").HandlerFunc(gz(httpTraceHdrs(adminAPI.TopLocksHandler)))
|
||||||
// Force unlocks paths
|
// Force unlocks paths
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/force-unlock").
|
||||||
Queries("paths", "{paths:.*}").HandlerFunc(httpTraceHdrs(adminAPI.ForceUnlockHandler))
|
Queries("paths", "{paths:.*}").HandlerFunc(gz(httpTraceHdrs(adminAPI.ForceUnlockHandler)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/speedtest").HandlerFunc(httpTraceHdrs(adminAPI.SpeedtestHandler))
|
||||||
|
|
||||||
// HTTP Trace
|
// HTTP Trace
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(adminAPI.TraceHandler)
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/trace").HandlerFunc(gz(adminAPI.TraceHandler))
|
||||||
|
|
||||||
// Console Logs
|
// Console Logs
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(httpTraceAll(adminAPI.ConsoleLogHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/log").HandlerFunc(gz(httpTraceAll(adminAPI.ConsoleLogHandler)))
|
||||||
|
|
||||||
// -- KMS APIs --
|
// -- KMS APIs --
|
||||||
//
|
//
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(httpTraceAll(adminAPI.KMSStatusHandler))
|
adminRouter.Methods(http.MethodPost).Path(adminVersion + "/kms/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSStatusHandler)))
|
||||||
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(httpTraceAll(adminAPI.KMSCreateKeyHandler)).Queries("key-id", "{key-id:.*}")
|
adminRouter.Methods(http.MethodPost).Path(adminVersion+"/kms/key/create").HandlerFunc(gz(httpTraceAll(adminAPI.KMSCreateKeyHandler))).Queries("key-id", "{key-id:.*}")
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(httpTraceAll(adminAPI.KMSKeyStatusHandler))
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/kms/key/status").HandlerFunc(gz(httpTraceAll(adminAPI.KMSKeyStatusHandler)))
|
||||||
|
|
||||||
if !globalIsGateway {
|
if !globalIsGateway {
|
||||||
// Keep obdinfo for backward compatibility with mc
|
// Keep obdinfo for backward compatibility with mc
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/obdinfo").
|
||||||
HandlerFunc(httpTraceHdrs(adminAPI.HealthInfoHandler))
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
||||||
// -- Health API --
|
// -- Health API --
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/healthinfo").
|
||||||
HandlerFunc(httpTraceHdrs(adminAPI.HealthInfoHandler))
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.HealthInfoHandler)))
|
||||||
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
|
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/bandwidth").
|
||||||
HandlerFunc(httpTraceHdrs(adminAPI.BandwidthMonitorHandler))
|
HandlerFunc(gz(httpTraceHdrs(adminAPI.BandwidthMonitorHandler)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-32
@@ -28,7 +28,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
|
||||||
"github.com/minio/minio/internal/crypto"
|
"github.com/minio/minio/internal/crypto"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
@@ -186,42 +185,12 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
||||||
if opts.VersionID == "" {
|
|
||||||
if ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
|
||||||
Name: objInfo.Name,
|
|
||||||
UserTags: objInfo.UserTags,
|
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
ModTime: objInfo.ModTime,
|
|
||||||
IsLatest: objInfo.IsLatest,
|
|
||||||
DeleteMarker: objInfo.DeleteMarker,
|
|
||||||
SuccessorModTime: objInfo.SuccessorModTime,
|
|
||||||
}); !expiryTime.IsZero() {
|
|
||||||
w.Header()[xhttp.AmzExpiration] = []string{
|
|
||||||
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if objInfo.IsRemote() {
|
if objInfo.IsRemote() {
|
||||||
// Check if object is being restored. For more information on x-amz-restore header see
|
// Check if object is being restored. For more information on x-amz-restore header see
|
||||||
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
|
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
|
||||||
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.TransitionTier}
|
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.TransitionTier}
|
||||||
}
|
}
|
||||||
ruleID, transitionTime := lc.PredictTransitionTime(lifecycle.ObjectOpts{
|
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
|
||||||
Name: objInfo.Name,
|
|
||||||
UserTags: objInfo.UserTags,
|
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
ModTime: objInfo.ModTime,
|
|
||||||
IsLatest: objInfo.IsLatest,
|
|
||||||
DeleteMarker: objInfo.DeleteMarker,
|
|
||||||
TransitionStatus: objInfo.TransitionStatus,
|
|
||||||
})
|
|
||||||
if !transitionTime.IsZero() {
|
|
||||||
// This header is a MinIO centric extension to show expected transition date in a similar spirit as x-amz-expiration
|
|
||||||
w.Header()[xhttp.MinIOTransition] = []string{
|
|
||||||
fmt.Sprintf(`transition-date="%s", rule-id="%s"`, transitionTime.Format(http.TimeFormat), ruleID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+83
-70
@@ -18,10 +18,12 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/klauspost/compress/gzhttp"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/pkg/wildcard"
|
"github.com/minio/pkg/wildcard"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
@@ -206,6 +208,17 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
}
|
}
|
||||||
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter())
|
||||||
|
|
||||||
|
gz := func(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper, err := gzhttp.NewWrapper(gzhttp.MinSize(1000), gzhttp.CompressionLevel(gzip.BestSpeed))
|
||||||
|
if err == nil {
|
||||||
|
gz = func(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return wrapper(h).(http.HandlerFunc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, router := range routers {
|
for _, router := range routers {
|
||||||
// Register all rejected object APIs
|
// Register all rejected object APIs
|
||||||
for _, r := range rejectedObjAPIs {
|
for _, r := range rejectedObjAPIs {
|
||||||
@@ -218,218 +231,218 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
// Object operations
|
// Object operations
|
||||||
// HeadObject
|
// HeadObject
|
||||||
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("headobject", maxClients(httpTraceAll(api.HeadObjectHandler))))
|
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
||||||
// CopyObjectPart
|
// CopyObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").
|
router.Methods(http.MethodPut).Path("/{object:.+}").
|
||||||
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").
|
||||||
HandlerFunc(collectAPIStats("copyobjectpart", maxClients(httpTraceAll(api.CopyObjectPartHandler)))).
|
HandlerFunc(collectAPIStats("copyobjectpart", maxClients(gz(httpTraceAll(api.CopyObjectPartHandler))))).
|
||||||
Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||||
// PutObjectPart
|
// PutObjectPart
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobjectpart", maxClients(httpTraceHdrs(api.PutObjectPartHandler)))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
collectAPIStats("putobjectpart", maxClients(gz(httpTraceHdrs(api.PutObjectPartHandler))))).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}")
|
||||||
// ListObjectParts
|
// ListObjectParts
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("listobjectparts", maxClients(httpTraceAll(api.ListObjectPartsHandler)))).Queries("uploadId", "{uploadId:.*}")
|
collectAPIStats("listobjectparts", maxClients(gz(httpTraceAll(api.ListObjectPartsHandler))))).Queries("uploadId", "{uploadId:.*}")
|
||||||
// CompleteMultipartUpload
|
// CompleteMultipartUpload
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("completemutipartupload", maxClients(httpTraceAll(api.CompleteMultipartUploadHandler)))).Queries("uploadId", "{uploadId:.*}")
|
collectAPIStats("completemutipartupload", maxClients(gz(httpTraceAll(api.CompleteMultipartUploadHandler))))).Queries("uploadId", "{uploadId:.*}")
|
||||||
// NewMultipartUpload
|
// NewMultipartUpload
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("newmultipartupload", maxClients(httpTraceAll(api.NewMultipartUploadHandler)))).Queries("uploads", "")
|
collectAPIStats("newmultipartupload", maxClients(gz(httpTraceAll(api.NewMultipartUploadHandler))))).Queries("uploads", "")
|
||||||
// AbortMultipartUpload
|
// AbortMultipartUpload
|
||||||
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("abortmultipartupload", maxClients(httpTraceAll(api.AbortMultipartUploadHandler)))).Queries("uploadId", "{uploadId:.*}")
|
collectAPIStats("abortmultipartupload", maxClients(gz(httpTraceAll(api.AbortMultipartUploadHandler))))).Queries("uploadId", "{uploadId:.*}")
|
||||||
// GetObjectACL - this is a dummy call.
|
// GetObjectACL - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobjectacl", maxClients(httpTraceHdrs(api.GetObjectACLHandler)))).Queries("acl", "")
|
collectAPIStats("getobjectacl", maxClients(gz(httpTraceHdrs(api.GetObjectACLHandler))))).Queries("acl", "")
|
||||||
// PutObjectACL - this is a dummy call.
|
// PutObjectACL - this is a dummy call.
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobjectacl", maxClients(httpTraceHdrs(api.PutObjectACLHandler)))).Queries("acl", "")
|
collectAPIStats("putobjectacl", maxClients(gz(httpTraceHdrs(api.PutObjectACLHandler))))).Queries("acl", "")
|
||||||
// GetObjectTagging
|
// GetObjectTagging
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobjecttagging", maxClients(httpTraceHdrs(api.GetObjectTaggingHandler)))).Queries("tagging", "")
|
collectAPIStats("getobjecttagging", maxClients(gz(httpTraceHdrs(api.GetObjectTaggingHandler))))).Queries("tagging", "")
|
||||||
// PutObjectTagging
|
// PutObjectTagging
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobjecttagging", maxClients(httpTraceHdrs(api.PutObjectTaggingHandler)))).Queries("tagging", "")
|
collectAPIStats("putobjecttagging", maxClients(gz(httpTraceHdrs(api.PutObjectTaggingHandler))))).Queries("tagging", "")
|
||||||
// DeleteObjectTagging
|
// DeleteObjectTagging
|
||||||
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("deleteobjecttagging", maxClients(httpTraceHdrs(api.DeleteObjectTaggingHandler)))).Queries("tagging", "")
|
collectAPIStats("deleteobjecttagging", maxClients(gz(httpTraceHdrs(api.DeleteObjectTaggingHandler))))).Queries("tagging", "")
|
||||||
// SelectObjectContent
|
// SelectObjectContent
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("selectobjectcontent", maxClients(httpTraceHdrs(api.SelectObjectContentHandler)))).Queries("select", "").Queries("select-type", "2")
|
collectAPIStats("selectobjectcontent", maxClients(gz(httpTraceHdrs(api.SelectObjectContentHandler))))).Queries("select", "").Queries("select-type", "2")
|
||||||
// GetObjectRetention
|
// GetObjectRetention
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobjectretention", maxClients(httpTraceAll(api.GetObjectRetentionHandler)))).Queries("retention", "")
|
collectAPIStats("getobjectretention", maxClients(gz(httpTraceAll(api.GetObjectRetentionHandler))))).Queries("retention", "")
|
||||||
// GetObjectLegalHold
|
// GetObjectLegalHold
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobjectlegalhold", maxClients(httpTraceAll(api.GetObjectLegalHoldHandler)))).Queries("legal-hold", "")
|
collectAPIStats("getobjectlegalhold", maxClients(gz(httpTraceAll(api.GetObjectLegalHoldHandler))))).Queries("legal-hold", "")
|
||||||
// GetObject
|
// GetObject - note gzip compression is *not* added due to Range requests.
|
||||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("getobject", maxClients(httpTraceHdrs(api.GetObjectHandler))))
|
collectAPIStats("getobject", maxClients(httpTraceHdrs(api.GetObjectHandler))))
|
||||||
// CopyObject
|
// CopyObject
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzCopySource, ".*?(\\/|%2F).*?").HandlerFunc(
|
||||||
collectAPIStats("copyobject", maxClients(httpTraceAll(api.CopyObjectHandler))))
|
collectAPIStats("copyobject", maxClients(gz(httpTraceAll(api.CopyObjectHandler)))))
|
||||||
// PutObjectRetention
|
// PutObjectRetention
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobjectretention", maxClients(httpTraceAll(api.PutObjectRetentionHandler)))).Queries("retention", "")
|
collectAPIStats("putobjectretention", maxClients(gz(httpTraceAll(api.PutObjectRetentionHandler))))).Queries("retention", "")
|
||||||
// PutObjectLegalHold
|
// PutObjectLegalHold
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobjectlegalhold", maxClients(httpTraceAll(api.PutObjectLegalHoldHandler)))).Queries("legal-hold", "")
|
collectAPIStats("putobjectlegalhold", maxClients(gz(httpTraceAll(api.PutObjectLegalHoldHandler))))).Queries("legal-hold", "")
|
||||||
|
|
||||||
// PutObject with auto-extract support for zip
|
// PutObject with auto-extract support for zip
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzSnowballExtract, "true").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HeadersRegexp(xhttp.AmzSnowballExtract, "true").HandlerFunc(
|
||||||
collectAPIStats("putobject", maxClients(httpTraceHdrs(api.PutObjectExtractHandler))))
|
collectAPIStats("putobject", maxClients(gz(httpTraceHdrs(api.PutObjectExtractHandler)))))
|
||||||
|
|
||||||
// PutObject
|
// PutObject
|
||||||
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPut).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("putobject", maxClients(httpTraceHdrs(api.PutObjectHandler))))
|
collectAPIStats("putobject", maxClients(gz(httpTraceHdrs(api.PutObjectHandler)))))
|
||||||
|
|
||||||
// DeleteObject
|
// DeleteObject
|
||||||
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodDelete).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("deleteobject", maxClients(httpTraceAll(api.DeleteObjectHandler))))
|
collectAPIStats("deleteobject", maxClients(gz(httpTraceAll(api.DeleteObjectHandler)))))
|
||||||
|
|
||||||
// PostRestoreObject
|
// PostRestoreObject
|
||||||
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
router.Methods(http.MethodPost).Path("/{object:.+}").HandlerFunc(
|
||||||
collectAPIStats("restoreobject", maxClients(httpTraceAll(api.PostRestoreObjectHandler)))).Queries("restore", "")
|
collectAPIStats("restoreobject", maxClients(gz(httpTraceAll(api.PostRestoreObjectHandler))))).Queries("restore", "")
|
||||||
|
|
||||||
/// Bucket operations
|
/// Bucket operations
|
||||||
// GetBucketLocation
|
// GetBucketLocation
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketlocation", maxClients(httpTraceAll(api.GetBucketLocationHandler)))).Queries("location", "")
|
collectAPIStats("getbucketlocation", maxClients(gz(httpTraceAll(api.GetBucketLocationHandler))))).Queries("location", "")
|
||||||
// GetBucketPolicy
|
// GetBucketPolicy
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketpolicy", maxClients(httpTraceAll(api.GetBucketPolicyHandler)))).Queries("policy", "")
|
collectAPIStats("getbucketpolicy", maxClients(gz(httpTraceAll(api.GetBucketPolicyHandler))))).Queries("policy", "")
|
||||||
// GetBucketLifecycle
|
// GetBucketLifecycle
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketlifecycle", maxClients(httpTraceAll(api.GetBucketLifecycleHandler)))).Queries("lifecycle", "")
|
collectAPIStats("getbucketlifecycle", maxClients(gz(httpTraceAll(api.GetBucketLifecycleHandler))))).Queries("lifecycle", "")
|
||||||
// GetBucketEncryption
|
// GetBucketEncryption
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketencryption", maxClients(httpTraceAll(api.GetBucketEncryptionHandler)))).Queries("encryption", "")
|
collectAPIStats("getbucketencryption", maxClients(gz(httpTraceAll(api.GetBucketEncryptionHandler))))).Queries("encryption", "")
|
||||||
// GetBucketObjectLockConfig
|
// GetBucketObjectLockConfig
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketobjectlockconfiguration", maxClients(httpTraceAll(api.GetBucketObjectLockConfigHandler)))).Queries("object-lock", "")
|
collectAPIStats("getbucketobjectlockconfiguration", maxClients(gz(httpTraceAll(api.GetBucketObjectLockConfigHandler))))).Queries("object-lock", "")
|
||||||
// GetBucketReplicationConfig
|
// GetBucketReplicationConfig
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketreplicationconfiguration", maxClients(httpTraceAll(api.GetBucketReplicationConfigHandler)))).Queries("replication", "")
|
collectAPIStats("getbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.GetBucketReplicationConfigHandler))))).Queries("replication", "")
|
||||||
// GetBucketVersioning
|
// GetBucketVersioning
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketversioning", maxClients(httpTraceAll(api.GetBucketVersioningHandler)))).Queries("versioning", "")
|
collectAPIStats("getbucketversioning", maxClients(gz(httpTraceAll(api.GetBucketVersioningHandler))))).Queries("versioning", "")
|
||||||
// GetBucketNotification
|
// GetBucketNotification
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketnotification", maxClients(httpTraceAll(api.GetBucketNotificationHandler)))).Queries("notification", "")
|
collectAPIStats("getbucketnotification", maxClients(gz(httpTraceAll(api.GetBucketNotificationHandler))))).Queries("notification", "")
|
||||||
// ListenNotification
|
// ListenNotification
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listennotification", maxClients(httpTraceAll(api.ListenNotificationHandler)))).Queries("events", "{events:.*}")
|
collectAPIStats("listennotification", maxClients(gz(httpTraceAll(api.ListenNotificationHandler))))).Queries("events", "{events:.*}")
|
||||||
|
|
||||||
// Dummy Bucket Calls
|
// Dummy Bucket Calls
|
||||||
// GetBucketACL -- this is a dummy call.
|
// GetBucketACL -- this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketacl", maxClients(httpTraceAll(api.GetBucketACLHandler)))).Queries("acl", "")
|
collectAPIStats("getbucketacl", maxClients(gz(httpTraceAll(api.GetBucketACLHandler))))).Queries("acl", "")
|
||||||
// PutBucketACL -- this is a dummy call.
|
// PutBucketACL -- this is a dummy call.
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketacl", maxClients(httpTraceAll(api.PutBucketACLHandler)))).Queries("acl", "")
|
collectAPIStats("putbucketacl", maxClients(gz(httpTraceAll(api.PutBucketACLHandler))))).Queries("acl", "")
|
||||||
// GetBucketCors - this is a dummy call.
|
// GetBucketCors - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketcors", maxClients(httpTraceAll(api.GetBucketCorsHandler)))).Queries("cors", "")
|
collectAPIStats("getbucketcors", maxClients(gz(httpTraceAll(api.GetBucketCorsHandler))))).Queries("cors", "")
|
||||||
// GetBucketWebsiteHandler - this is a dummy call.
|
// GetBucketWebsiteHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketwebsite", maxClients(httpTraceAll(api.GetBucketWebsiteHandler)))).Queries("website", "")
|
collectAPIStats("getbucketwebsite", maxClients(gz(httpTraceAll(api.GetBucketWebsiteHandler))))).Queries("website", "")
|
||||||
// GetBucketAccelerateHandler - this is a dummy call.
|
// GetBucketAccelerateHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketaccelerate", maxClients(httpTraceAll(api.GetBucketAccelerateHandler)))).Queries("accelerate", "")
|
collectAPIStats("getbucketaccelerate", maxClients(gz(httpTraceAll(api.GetBucketAccelerateHandler))))).Queries("accelerate", "")
|
||||||
// GetBucketRequestPaymentHandler - this is a dummy call.
|
// GetBucketRequestPaymentHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketrequestpayment", maxClients(httpTraceAll(api.GetBucketRequestPaymentHandler)))).Queries("requestPayment", "")
|
collectAPIStats("getbucketrequestpayment", maxClients(gz(httpTraceAll(api.GetBucketRequestPaymentHandler))))).Queries("requestPayment", "")
|
||||||
// GetBucketLoggingHandler - this is a dummy call.
|
// GetBucketLoggingHandler - this is a dummy call.
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketlogging", maxClients(httpTraceAll(api.GetBucketLoggingHandler)))).Queries("logging", "")
|
collectAPIStats("getbucketlogging", maxClients(gz(httpTraceAll(api.GetBucketLoggingHandler))))).Queries("logging", "")
|
||||||
// GetBucketTaggingHandler
|
// GetBucketTaggingHandler
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbuckettagging", maxClients(httpTraceAll(api.GetBucketTaggingHandler)))).Queries("tagging", "")
|
collectAPIStats("getbuckettagging", maxClients(gz(httpTraceAll(api.GetBucketTaggingHandler))))).Queries("tagging", "")
|
||||||
//DeleteBucketWebsiteHandler
|
//DeleteBucketWebsiteHandler
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucketwebsite", maxClients(httpTraceAll(api.DeleteBucketWebsiteHandler)))).Queries("website", "")
|
collectAPIStats("deletebucketwebsite", maxClients(gz(httpTraceAll(api.DeleteBucketWebsiteHandler))))).Queries("website", "")
|
||||||
// DeleteBucketTaggingHandler
|
// DeleteBucketTaggingHandler
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebuckettagging", maxClients(httpTraceAll(api.DeleteBucketTaggingHandler)))).Queries("tagging", "")
|
collectAPIStats("deletebuckettagging", maxClients(gz(httpTraceAll(api.DeleteBucketTaggingHandler))))).Queries("tagging", "")
|
||||||
|
|
||||||
// ListMultipartUploads
|
// ListMultipartUploads
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listmultipartuploads", maxClients(httpTraceAll(api.ListMultipartUploadsHandler)))).Queries("uploads", "")
|
collectAPIStats("listmultipartuploads", maxClients(gz(httpTraceAll(api.ListMultipartUploadsHandler))))).Queries("uploads", "")
|
||||||
// ListObjectsV2M
|
// ListObjectsV2M
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectsv2M", maxClients(httpTraceAll(api.ListObjectsV2MHandler)))).Queries("list-type", "2", "metadata", "true")
|
collectAPIStats("listobjectsv2M", maxClients(gz(httpTraceAll(api.ListObjectsV2MHandler))))).Queries("list-type", "2", "metadata", "true")
|
||||||
// ListObjectsV2
|
// ListObjectsV2
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectsv2", maxClients(httpTraceAll(api.ListObjectsV2Handler)))).Queries("list-type", "2")
|
collectAPIStats("listobjectsv2", maxClients(gz(httpTraceAll(api.ListObjectsV2Handler))))).Queries("list-type", "2")
|
||||||
// ListObjectVersions
|
// ListObjectVersions
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectversions", maxClients(httpTraceAll(api.ListObjectVersionsHandler)))).Queries("versions", "")
|
collectAPIStats("listobjectversions", maxClients(gz(httpTraceAll(api.ListObjectVersionsHandler))))).Queries("versions", "")
|
||||||
// GetBucketPolicyStatus
|
// GetBucketPolicyStatus
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getpolicystatus", maxClients(httpTraceAll(api.GetBucketPolicyStatusHandler)))).Queries("policyStatus", "")
|
collectAPIStats("getpolicystatus", maxClients(gz(httpTraceAll(api.GetBucketPolicyStatusHandler))))).Queries("policyStatus", "")
|
||||||
// PutBucketLifecycle
|
// PutBucketLifecycle
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketlifecycle", maxClients(httpTraceAll(api.PutBucketLifecycleHandler)))).Queries("lifecycle", "")
|
collectAPIStats("putbucketlifecycle", maxClients(gz(httpTraceAll(api.PutBucketLifecycleHandler))))).Queries("lifecycle", "")
|
||||||
// PutBucketReplicationConfig
|
// PutBucketReplicationConfig
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketreplicationconfiguration", maxClients(httpTraceAll(api.PutBucketReplicationConfigHandler)))).Queries("replication", "")
|
collectAPIStats("putbucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.PutBucketReplicationConfigHandler))))).Queries("replication", "")
|
||||||
// PutBucketEncryption
|
// PutBucketEncryption
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketencryption", maxClients(httpTraceAll(api.PutBucketEncryptionHandler)))).Queries("encryption", "")
|
collectAPIStats("putbucketencryption", maxClients(gz(httpTraceAll(api.PutBucketEncryptionHandler))))).Queries("encryption", "")
|
||||||
|
|
||||||
// PutBucketPolicy
|
// PutBucketPolicy
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketpolicy", maxClients(httpTraceAll(api.PutBucketPolicyHandler)))).Queries("policy", "")
|
collectAPIStats("putbucketpolicy", maxClients(gz(httpTraceAll(api.PutBucketPolicyHandler))))).Queries("policy", "")
|
||||||
|
|
||||||
// PutBucketObjectLockConfig
|
// PutBucketObjectLockConfig
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketobjectlockconfig", maxClients(httpTraceAll(api.PutBucketObjectLockConfigHandler)))).Queries("object-lock", "")
|
collectAPIStats("putbucketobjectlockconfig", maxClients(gz(httpTraceAll(api.PutBucketObjectLockConfigHandler))))).Queries("object-lock", "")
|
||||||
// PutBucketTaggingHandler
|
// PutBucketTaggingHandler
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbuckettagging", maxClients(httpTraceAll(api.PutBucketTaggingHandler)))).Queries("tagging", "")
|
collectAPIStats("putbuckettagging", maxClients(gz(httpTraceAll(api.PutBucketTaggingHandler))))).Queries("tagging", "")
|
||||||
// PutBucketVersioning
|
// PutBucketVersioning
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketversioning", maxClients(httpTraceAll(api.PutBucketVersioningHandler)))).Queries("versioning", "")
|
collectAPIStats("putbucketversioning", maxClients(gz(httpTraceAll(api.PutBucketVersioningHandler))))).Queries("versioning", "")
|
||||||
// PutBucketNotification
|
// PutBucketNotification
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucketnotification", maxClients(httpTraceAll(api.PutBucketNotificationHandler)))).Queries("notification", "")
|
collectAPIStats("putbucketnotification", maxClients(gz(httpTraceAll(api.PutBucketNotificationHandler))))).Queries("notification", "")
|
||||||
// ResetBucketReplicationState - MinIO extension API
|
// ResetBucketReplicationState - MinIO extension API
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("resetbucketreplicationstate", maxClients(httpTraceAll(api.ResetBucketReplicationStateHandler)))).Queries("replication-reset", "")
|
collectAPIStats("resetbucketreplicationstate", maxClients(gz(httpTraceAll(api.ResetBucketReplicationStateHandler))))).Queries("replication-reset", "")
|
||||||
// PutBucket
|
// PutBucket
|
||||||
router.Methods(http.MethodPut).HandlerFunc(
|
router.Methods(http.MethodPut).HandlerFunc(
|
||||||
collectAPIStats("putbucket", maxClients(httpTraceAll(api.PutBucketHandler))))
|
collectAPIStats("putbucket", maxClients(gz(httpTraceAll(api.PutBucketHandler)))))
|
||||||
// HeadBucket
|
// HeadBucket
|
||||||
router.Methods(http.MethodHead).HandlerFunc(
|
router.Methods(http.MethodHead).HandlerFunc(
|
||||||
collectAPIStats("headbucket", maxClients(httpTraceAll(api.HeadBucketHandler))))
|
collectAPIStats("headbucket", maxClients(gz(httpTraceAll(api.HeadBucketHandler)))))
|
||||||
// PostPolicy
|
// PostPolicy
|
||||||
router.Methods(http.MethodPost).HeadersRegexp(xhttp.ContentType, "multipart/form-data*").HandlerFunc(
|
router.Methods(http.MethodPost).HeadersRegexp(xhttp.ContentType, "multipart/form-data*").HandlerFunc(
|
||||||
collectAPIStats("postpolicybucket", maxClients(httpTraceHdrs(api.PostPolicyBucketHandler))))
|
collectAPIStats("postpolicybucket", maxClients(gz(httpTraceHdrs(api.PostPolicyBucketHandler)))))
|
||||||
// DeleteMultipleObjects
|
// DeleteMultipleObjects
|
||||||
router.Methods(http.MethodPost).HandlerFunc(
|
router.Methods(http.MethodPost).HandlerFunc(
|
||||||
collectAPIStats("deletemultipleobjects", maxClients(httpTraceAll(api.DeleteMultipleObjectsHandler)))).Queries("delete", "")
|
collectAPIStats("deletemultipleobjects", maxClients(gz(httpTraceAll(api.DeleteMultipleObjectsHandler))))).Queries("delete", "")
|
||||||
// DeleteBucketPolicy
|
// DeleteBucketPolicy
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucketpolicy", maxClients(httpTraceAll(api.DeleteBucketPolicyHandler)))).Queries("policy", "")
|
collectAPIStats("deletebucketpolicy", maxClients(gz(httpTraceAll(api.DeleteBucketPolicyHandler))))).Queries("policy", "")
|
||||||
// DeleteBucketReplication
|
// DeleteBucketReplication
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucketreplicationconfiguration", maxClients(httpTraceAll(api.DeleteBucketReplicationConfigHandler)))).Queries("replication", "")
|
collectAPIStats("deletebucketreplicationconfiguration", maxClients(gz(httpTraceAll(api.DeleteBucketReplicationConfigHandler))))).Queries("replication", "")
|
||||||
// DeleteBucketLifecycle
|
// DeleteBucketLifecycle
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucketlifecycle", maxClients(httpTraceAll(api.DeleteBucketLifecycleHandler)))).Queries("lifecycle", "")
|
collectAPIStats("deletebucketlifecycle", maxClients(gz(httpTraceAll(api.DeleteBucketLifecycleHandler))))).Queries("lifecycle", "")
|
||||||
// DeleteBucketEncryption
|
// DeleteBucketEncryption
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucketencryption", maxClients(httpTraceAll(api.DeleteBucketEncryptionHandler)))).Queries("encryption", "")
|
collectAPIStats("deletebucketencryption", maxClients(gz(httpTraceAll(api.DeleteBucketEncryptionHandler))))).Queries("encryption", "")
|
||||||
// DeleteBucket
|
// DeleteBucket
|
||||||
router.Methods(http.MethodDelete).HandlerFunc(
|
router.Methods(http.MethodDelete).HandlerFunc(
|
||||||
collectAPIStats("deletebucket", maxClients(httpTraceAll(api.DeleteBucketHandler))))
|
collectAPIStats("deletebucket", maxClients(gz(httpTraceAll(api.DeleteBucketHandler)))))
|
||||||
|
|
||||||
// MinIO extension API for replication.
|
// MinIO extension API for replication.
|
||||||
//
|
//
|
||||||
// GetBucketReplicationMetrics
|
// GetBucketReplicationMetrics
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("getbucketreplicationmetrics", maxClients(httpTraceAll(api.GetBucketReplicationMetricsHandler)))).Queries("replication-metrics", "")
|
collectAPIStats("getbucketreplicationmetrics", maxClients(gz(httpTraceAll(api.GetBucketReplicationMetricsHandler))))).Queries("replication-metrics", "")
|
||||||
|
|
||||||
// Register rejected bucket APIs
|
// Register rejected bucket APIs
|
||||||
for _, r := range rejectedBucketAPIs {
|
for _, r := range rejectedBucketAPIs {
|
||||||
@@ -440,23 +453,23 @@ func registerAPIRouter(router *mux.Router) {
|
|||||||
|
|
||||||
// S3 ListObjectsV1 (Legacy)
|
// S3 ListObjectsV1 (Legacy)
|
||||||
router.Methods(http.MethodGet).HandlerFunc(
|
router.Methods(http.MethodGet).HandlerFunc(
|
||||||
collectAPIStats("listobjectsv1", maxClients(httpTraceAll(api.ListObjectsV1Handler))))
|
collectAPIStats("listobjectsv1", maxClients(gz(httpTraceAll(api.ListObjectsV1Handler)))))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Root operation
|
/// Root operation
|
||||||
|
|
||||||
// ListenNotification
|
// ListenNotification
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
||||||
collectAPIStats("listennotification", maxClients(httpTraceAll(api.ListenNotificationHandler)))).Queries("events", "{events:.*}")
|
collectAPIStats("listennotification", maxClients(gz(httpTraceAll(api.ListenNotificationHandler))))).Queries("events", "{events:.*}")
|
||||||
|
|
||||||
// ListBuckets
|
// ListBuckets
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator).HandlerFunc(
|
||||||
collectAPIStats("listbuckets", maxClients(httpTraceAll(api.ListBucketsHandler))))
|
collectAPIStats("listbuckets", maxClients(gz(httpTraceAll(api.ListBucketsHandler)))))
|
||||||
|
|
||||||
// S3 browser with signature v4 adds '//' for ListBuckets request, so rather
|
// S3 browser with signature v4 adds '//' for ListBuckets request, so rather
|
||||||
// than failing with UnknownAPIRequest we simply handle it for now.
|
// than failing with UnknownAPIRequest we simply handle it for now.
|
||||||
apiRouter.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).HandlerFunc(
|
apiRouter.Methods(http.MethodGet).Path(SlashSeparator + SlashSeparator).HandlerFunc(
|
||||||
collectAPIStats("listbuckets", maxClients(httpTraceAll(api.ListBucketsHandler))))
|
collectAPIStats("listbuckets", maxClients(gz(httpTraceAll(api.ListBucketsHandler)))))
|
||||||
|
|
||||||
// If none of the routes match add default error handler routes
|
// If none of the routes match add default error handler routes
|
||||||
apiRouter.NotFoundHandler = collectAPIStats("notfound", httpTraceAll(errorResponseHandler))
|
apiRouter.NotFoundHandler = collectAPIStats("notfound", httpTraceAll(errorResponseHandler))
|
||||||
|
|||||||
+22
-19
@@ -213,27 +213,17 @@ func getClaimsFromToken(token string) (map[string]interface{}, error) {
|
|||||||
return nil, errAuthentication
|
return nil, errAuthentication
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalPolicyOPA == nil {
|
// If OPA is set, return without any further checks.
|
||||||
// If OPA is not set and if ldap claim key is set, allow the claim.
|
if globalPolicyOPA != nil {
|
||||||
if _, ok := claims.MapClaims[ldapUser]; ok {
|
return claims.Map(), nil
|
||||||
return claims.Map(), nil
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If OPA is not set, session token should
|
// Check if a session policy is set. If so, decode it here.
|
||||||
// have a policy and its mandatory, reject
|
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
||||||
// requests without policy claim.
|
if spok {
|
||||||
_, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]
|
|
||||||
_, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
|
|
||||||
if !pokOpenID && !pokSA {
|
|
||||||
return nil, errAuthentication
|
|
||||||
}
|
|
||||||
|
|
||||||
sp, spok := claims.Lookup(iampolicy.SessionPolicyName)
|
|
||||||
if !spok {
|
|
||||||
return claims.Map(), nil
|
|
||||||
}
|
|
||||||
// Looks like subpolicy is set and is a string, if set then its
|
// Looks like subpolicy is set and is a string, if set then its
|
||||||
// base64 encoded, decode it. Decoding fails reject such requests.
|
// base64 encoded, decode it. Decoding fails reject such
|
||||||
|
// requests.
|
||||||
spBytes, err := base64.StdEncoding.DecodeString(sp)
|
spBytes, err := base64.StdEncoding.DecodeString(sp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Base64 decoding fails, we should log to indicate
|
// Base64 decoding fails, we should log to indicate
|
||||||
@@ -244,6 +234,19 @@ func getClaimsFromToken(token string) (map[string]interface{}, error) {
|
|||||||
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
|
claims.MapClaims[iampolicy.SessionPolicyName] = string(spBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If LDAP claim key is set, return here.
|
||||||
|
if _, ok := claims.MapClaims[ldapUser]; ok {
|
||||||
|
return claims.Map(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session token must have a policy, reject requests without policy
|
||||||
|
// claim.
|
||||||
|
_, pokOpenID := claims.MapClaims[iamPolicyClaimNameOpenID()]
|
||||||
|
_, pokSA := claims.MapClaims[iamPolicyClaimNameSA()]
|
||||||
|
if !pokOpenID && !pokSA {
|
||||||
|
return nil, errAuthentication
|
||||||
|
}
|
||||||
|
|
||||||
return claims.Map(), nil
|
return claims.Map(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,18 +48,23 @@ const (
|
|||||||
type healingTracker struct {
|
type healingTracker struct {
|
||||||
disk StorageAPI `msg:"-"`
|
disk StorageAPI `msg:"-"`
|
||||||
|
|
||||||
ID string
|
ID string
|
||||||
PoolIndex int
|
PoolIndex int
|
||||||
SetIndex int
|
SetIndex int
|
||||||
DiskIndex int
|
DiskIndex int
|
||||||
Path string
|
Path string
|
||||||
Endpoint string
|
Endpoint string
|
||||||
Started time.Time
|
Started time.Time
|
||||||
LastUpdate time.Time
|
LastUpdate time.Time
|
||||||
ObjectsHealed uint64
|
|
||||||
ObjectsFailed uint64
|
ObjectsTotalCount uint64
|
||||||
BytesDone uint64
|
ObjectsTotalSize uint64
|
||||||
BytesFailed uint64
|
|
||||||
|
ItemsHealed uint64
|
||||||
|
ItemsFailed uint64
|
||||||
|
|
||||||
|
BytesDone uint64
|
||||||
|
BytesFailed uint64
|
||||||
|
|
||||||
// Last object scanned.
|
// Last object scanned.
|
||||||
Bucket string `json:"-"`
|
Bucket string `json:"-"`
|
||||||
@@ -67,10 +72,10 @@ type healingTracker struct {
|
|||||||
|
|
||||||
// Numbers when current bucket started healing,
|
// Numbers when current bucket started healing,
|
||||||
// for resuming with correct numbers.
|
// for resuming with correct numbers.
|
||||||
ResumeObjectsHealed uint64 `json:"-"`
|
ResumeItemsHealed uint64 `json:"-"`
|
||||||
ResumeObjectsFailed uint64 `json:"-"`
|
ResumeItemsFailed uint64 `json:"-"`
|
||||||
ResumeBytesDone uint64 `json:"-"`
|
ResumeBytesDone uint64 `json:"-"`
|
||||||
ResumeBytesFailed uint64 `json:"-"`
|
ResumeBytesFailed uint64 `json:"-"`
|
||||||
|
|
||||||
// Filled on startup/restarts.
|
// Filled on startup/restarts.
|
||||||
QueuedBuckets []string
|
QueuedBuckets []string
|
||||||
@@ -175,8 +180,8 @@ func (h *healingTracker) isHealed(bucket string) bool {
|
|||||||
|
|
||||||
// resume will reset progress to the numbers at the start of the bucket.
|
// resume will reset progress to the numbers at the start of the bucket.
|
||||||
func (h *healingTracker) resume() {
|
func (h *healingTracker) resume() {
|
||||||
h.ObjectsHealed = h.ResumeObjectsHealed
|
h.ItemsHealed = h.ResumeItemsHealed
|
||||||
h.ObjectsFailed = h.ResumeObjectsFailed
|
h.ItemsFailed = h.ResumeItemsFailed
|
||||||
h.BytesDone = h.ResumeBytesDone
|
h.BytesDone = h.ResumeBytesDone
|
||||||
h.BytesFailed = h.ResumeBytesFailed
|
h.BytesFailed = h.ResumeBytesFailed
|
||||||
}
|
}
|
||||||
@@ -184,8 +189,8 @@ func (h *healingTracker) resume() {
|
|||||||
// bucketDone should be called when a bucket is done healing.
|
// bucketDone should be called when a bucket is done healing.
|
||||||
// Adds the bucket to the list of healed buckets and updates resume numbers.
|
// Adds the bucket to the list of healed buckets and updates resume numbers.
|
||||||
func (h *healingTracker) bucketDone(bucket string) {
|
func (h *healingTracker) bucketDone(bucket string) {
|
||||||
h.ResumeObjectsHealed = h.ObjectsHealed
|
h.ResumeItemsHealed = h.ItemsHealed
|
||||||
h.ResumeObjectsFailed = h.ObjectsFailed
|
h.ResumeItemsFailed = h.ItemsFailed
|
||||||
h.ResumeBytesDone = h.BytesDone
|
h.ResumeBytesDone = h.BytesDone
|
||||||
h.ResumeBytesFailed = h.BytesFailed
|
h.ResumeBytesFailed = h.BytesFailed
|
||||||
h.HealedBuckets = append(h.HealedBuckets, bucket)
|
h.HealedBuckets = append(h.HealedBuckets, bucket)
|
||||||
@@ -220,22 +225,28 @@ func (h *healingTracker) printTo(writer io.Writer) {
|
|||||||
// toHealingDisk converts the information to madmin.HealingDisk
|
// toHealingDisk converts the information to madmin.HealingDisk
|
||||||
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
func (h *healingTracker) toHealingDisk() madmin.HealingDisk {
|
||||||
return madmin.HealingDisk{
|
return madmin.HealingDisk{
|
||||||
ID: h.ID,
|
ID: h.ID,
|
||||||
Endpoint: h.Endpoint,
|
Endpoint: h.Endpoint,
|
||||||
PoolIndex: h.PoolIndex,
|
PoolIndex: h.PoolIndex,
|
||||||
SetIndex: h.SetIndex,
|
SetIndex: h.SetIndex,
|
||||||
DiskIndex: h.DiskIndex,
|
DiskIndex: h.DiskIndex,
|
||||||
Path: h.Path,
|
Path: h.Path,
|
||||||
Started: h.Started.UTC(),
|
Started: h.Started.UTC(),
|
||||||
LastUpdate: h.LastUpdate.UTC(),
|
LastUpdate: h.LastUpdate.UTC(),
|
||||||
ObjectsHealed: h.ObjectsHealed,
|
ObjectsTotalCount: h.ObjectsTotalCount,
|
||||||
ObjectsFailed: h.ObjectsFailed,
|
ObjectsTotalSize: h.ObjectsTotalSize,
|
||||||
BytesDone: h.BytesDone,
|
ItemsHealed: h.ItemsHealed,
|
||||||
BytesFailed: h.BytesFailed,
|
ItemsFailed: h.ItemsFailed,
|
||||||
Bucket: h.Bucket,
|
BytesDone: h.BytesDone,
|
||||||
Object: h.Object,
|
BytesFailed: h.BytesFailed,
|
||||||
QueuedBuckets: h.QueuedBuckets,
|
Bucket: h.Bucket,
|
||||||
HealedBuckets: h.HealedBuckets,
|
Object: h.Object,
|
||||||
|
QueuedBuckets: h.QueuedBuckets,
|
||||||
|
HealedBuckets: h.HealedBuckets,
|
||||||
|
|
||||||
|
ObjectsHealed: h.ItemsHealed, // Deprecated July 2021
|
||||||
|
ObjectsFailed: h.ItemsFailed, // Deprecated July 2021
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +424,14 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools, bgSeq
|
|||||||
tracker = newHealingTracker(disk)
|
tracker = newHealingTracker(disk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load bucket totals
|
||||||
|
cache := dataUsageCache{}
|
||||||
|
if err := cache.load(ctx, z.serverPools[i].sets[setIndex], dataUsageCacheName); err == nil {
|
||||||
|
dataUsageInfo := cache.dui(dataUsageRoot, nil)
|
||||||
|
tracker.ObjectsTotalCount = dataUsageInfo.ObjectsTotalCount
|
||||||
|
tracker.ObjectsTotalSize = dataUsageInfo.ObjectsTotalSize
|
||||||
|
}
|
||||||
|
|
||||||
tracker.PoolIndex, tracker.SetIndex, tracker.DiskIndex = disk.GetDiskLoc()
|
tracker.PoolIndex, tracker.SetIndex, tracker.DiskIndex = disk.GetDiskLoc()
|
||||||
tracker.setQueuedBuckets(buckets)
|
tracker.setQueuedBuckets(buckets)
|
||||||
if err := tracker.save(ctx); err != nil {
|
if err := tracker.save(ctx); err != nil {
|
||||||
|
|||||||
@@ -72,16 +72,28 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "LastUpdate")
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ObjectsHealed":
|
case "ObjectsTotalCount":
|
||||||
z.ObjectsHealed, err = dc.ReadUint64()
|
z.ObjectsTotalCount, err = dc.ReadUint64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ObjectsHealed")
|
err = msgp.WrapError(err, "ObjectsTotalCount")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ObjectsFailed":
|
case "ObjectsTotalSize":
|
||||||
z.ObjectsFailed, err = dc.ReadUint64()
|
z.ObjectsTotalSize, err = dc.ReadUint64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ObjectsFailed")
|
err = msgp.WrapError(err, "ObjectsTotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ItemsHealed":
|
||||||
|
z.ItemsHealed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ItemsFailed":
|
||||||
|
z.ItemsFailed, err = dc.ReadUint64()
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsFailed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "BytesDone":
|
case "BytesDone":
|
||||||
@@ -108,16 +120,16 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
err = msgp.WrapError(err, "Object")
|
err = msgp.WrapError(err, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ResumeObjectsHealed":
|
case "ResumeItemsHealed":
|
||||||
z.ResumeObjectsHealed, err = dc.ReadUint64()
|
z.ResumeItemsHealed, err = dc.ReadUint64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ResumeObjectsHealed")
|
err = msgp.WrapError(err, "ResumeItemsHealed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ResumeObjectsFailed":
|
case "ResumeItemsFailed":
|
||||||
z.ResumeObjectsFailed, err = dc.ReadUint64()
|
z.ResumeItemsFailed, err = dc.ReadUint64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ResumeObjectsFailed")
|
err = msgp.WrapError(err, "ResumeItemsFailed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ResumeBytesDone":
|
case "ResumeBytesDone":
|
||||||
@@ -183,9 +195,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
|
|||||||
|
|
||||||
// EncodeMsg implements msgp.Encodable
|
// EncodeMsg implements msgp.Encodable
|
||||||
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
||||||
// map header, size 20
|
// map header, size 22
|
||||||
// write "ID"
|
// write "ID"
|
||||||
err = en.Append(0xde, 0x0, 0x14, 0xa2, 0x49, 0x44)
|
err = en.Append(0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -264,24 +276,44 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "LastUpdate")
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "ObjectsHealed"
|
// write "ObjectsTotalCount"
|
||||||
err = en.Append(0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
err = en.Append(0xb1, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteUint64(z.ObjectsHealed)
|
err = en.WriteUint64(z.ObjectsTotalCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ObjectsHealed")
|
err = msgp.WrapError(err, "ObjectsTotalCount")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "ObjectsFailed"
|
// write "ObjectsTotalSize"
|
||||||
err = en.Append(0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
err = en.Append(0xb0, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x69, 0x7a, 0x65)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteUint64(z.ObjectsFailed)
|
err = en.WriteUint64(z.ObjectsTotalSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ObjectsFailed")
|
err = msgp.WrapError(err, "ObjectsTotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ItemsHealed"
|
||||||
|
err = en.Append(0xab, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ItemsHealed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// write "ItemsFailed"
|
||||||
|
err = en.Append(0xab, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = en.WriteUint64(z.ItemsFailed)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsFailed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "BytesDone"
|
// write "BytesDone"
|
||||||
@@ -324,24 +356,24 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
err = msgp.WrapError(err, "Object")
|
err = msgp.WrapError(err, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "ResumeObjectsHealed"
|
// write "ResumeItemsHealed"
|
||||||
err = en.Append(0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
err = en.Append(0xb1, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteUint64(z.ResumeObjectsHealed)
|
err = en.WriteUint64(z.ResumeItemsHealed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ResumeObjectsHealed")
|
err = msgp.WrapError(err, "ResumeItemsHealed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "ResumeObjectsFailed"
|
// write "ResumeItemsFailed"
|
||||||
err = en.Append(0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
err = en.Append(0xb1, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = en.WriteUint64(z.ResumeObjectsFailed)
|
err = en.WriteUint64(z.ResumeItemsFailed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ResumeObjectsFailed")
|
err = msgp.WrapError(err, "ResumeItemsFailed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// write "ResumeBytesDone"
|
// write "ResumeBytesDone"
|
||||||
@@ -404,9 +436,9 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
|
|||||||
// MarshalMsg implements msgp.Marshaler
|
// MarshalMsg implements msgp.Marshaler
|
||||||
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
||||||
o = msgp.Require(b, z.Msgsize())
|
o = msgp.Require(b, z.Msgsize())
|
||||||
// map header, size 20
|
// map header, size 22
|
||||||
// string "ID"
|
// string "ID"
|
||||||
o = append(o, 0xde, 0x0, 0x14, 0xa2, 0x49, 0x44)
|
o = append(o, 0xde, 0x0, 0x16, 0xa2, 0x49, 0x44)
|
||||||
o = msgp.AppendString(o, z.ID)
|
o = msgp.AppendString(o, z.ID)
|
||||||
// string "PoolIndex"
|
// string "PoolIndex"
|
||||||
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
|
||||||
@@ -429,12 +461,18 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "LastUpdate"
|
// string "LastUpdate"
|
||||||
o = append(o, 0xaa, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65)
|
o = append(o, 0xaa, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65)
|
||||||
o = msgp.AppendTime(o, z.LastUpdate)
|
o = msgp.AppendTime(o, z.LastUpdate)
|
||||||
// string "ObjectsHealed"
|
// string "ObjectsTotalCount"
|
||||||
o = append(o, 0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
o = append(o, 0xb1, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74)
|
||||||
o = msgp.AppendUint64(o, z.ObjectsHealed)
|
o = msgp.AppendUint64(o, z.ObjectsTotalCount)
|
||||||
// string "ObjectsFailed"
|
// string "ObjectsTotalSize"
|
||||||
o = append(o, 0xad, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
o = append(o, 0xb0, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x53, 0x69, 0x7a, 0x65)
|
||||||
o = msgp.AppendUint64(o, z.ObjectsFailed)
|
o = msgp.AppendUint64(o, z.ObjectsTotalSize)
|
||||||
|
// string "ItemsHealed"
|
||||||
|
o = append(o, 0xab, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ItemsHealed)
|
||||||
|
// string "ItemsFailed"
|
||||||
|
o = append(o, 0xab, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
|
o = msgp.AppendUint64(o, z.ItemsFailed)
|
||||||
// string "BytesDone"
|
// string "BytesDone"
|
||||||
o = append(o, 0xa9, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
o = append(o, 0xa9, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
||||||
o = msgp.AppendUint64(o, z.BytesDone)
|
o = msgp.AppendUint64(o, z.BytesDone)
|
||||||
@@ -447,12 +485,12 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
|
|||||||
// string "Object"
|
// string "Object"
|
||||||
o = append(o, 0xa6, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74)
|
o = append(o, 0xa6, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74)
|
||||||
o = msgp.AppendString(o, z.Object)
|
o = msgp.AppendString(o, z.Object)
|
||||||
// string "ResumeObjectsHealed"
|
// string "ResumeItemsHealed"
|
||||||
o = append(o, 0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
o = append(o, 0xb1, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x65, 0x64)
|
||||||
o = msgp.AppendUint64(o, z.ResumeObjectsHealed)
|
o = msgp.AppendUint64(o, z.ResumeItemsHealed)
|
||||||
// string "ResumeObjectsFailed"
|
// string "ResumeItemsFailed"
|
||||||
o = append(o, 0xb3, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
o = append(o, 0xb1, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64)
|
||||||
o = msgp.AppendUint64(o, z.ResumeObjectsFailed)
|
o = msgp.AppendUint64(o, z.ResumeItemsFailed)
|
||||||
// string "ResumeBytesDone"
|
// string "ResumeBytesDone"
|
||||||
o = append(o, 0xaf, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
o = append(o, 0xaf, 0x52, 0x65, 0x73, 0x75, 0x6d, 0x65, 0x42, 0x79, 0x74, 0x65, 0x73, 0x44, 0x6f, 0x6e, 0x65)
|
||||||
o = msgp.AppendUint64(o, z.ResumeBytesDone)
|
o = msgp.AppendUint64(o, z.ResumeBytesDone)
|
||||||
@@ -540,16 +578,28 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "LastUpdate")
|
err = msgp.WrapError(err, "LastUpdate")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ObjectsHealed":
|
case "ObjectsTotalCount":
|
||||||
z.ObjectsHealed, bts, err = msgp.ReadUint64Bytes(bts)
|
z.ObjectsTotalCount, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ObjectsHealed")
|
err = msgp.WrapError(err, "ObjectsTotalCount")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ObjectsFailed":
|
case "ObjectsTotalSize":
|
||||||
z.ObjectsFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
z.ObjectsTotalSize, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ObjectsFailed")
|
err = msgp.WrapError(err, "ObjectsTotalSize")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ItemsHealed":
|
||||||
|
z.ItemsHealed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsHealed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case "ItemsFailed":
|
||||||
|
z.ItemsFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
|
if err != nil {
|
||||||
|
err = msgp.WrapError(err, "ItemsFailed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "BytesDone":
|
case "BytesDone":
|
||||||
@@ -576,16 +626,16 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
err = msgp.WrapError(err, "Object")
|
err = msgp.WrapError(err, "Object")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ResumeObjectsHealed":
|
case "ResumeItemsHealed":
|
||||||
z.ResumeObjectsHealed, bts, err = msgp.ReadUint64Bytes(bts)
|
z.ResumeItemsHealed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ResumeObjectsHealed")
|
err = msgp.WrapError(err, "ResumeItemsHealed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ResumeObjectsFailed":
|
case "ResumeItemsFailed":
|
||||||
z.ResumeObjectsFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
z.ResumeItemsFailed, bts, err = msgp.ReadUint64Bytes(bts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = msgp.WrapError(err, "ResumeObjectsFailed")
|
err = msgp.WrapError(err, "ResumeItemsFailed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "ResumeBytesDone":
|
case "ResumeBytesDone":
|
||||||
@@ -652,7 +702,7 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
|||||||
|
|
||||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||||
func (z *healingTracker) Msgsize() (s int) {
|
func (z *healingTracker) Msgsize() (s int) {
|
||||||
s = 3 + 3 + msgp.StringPrefixSize + len(z.ID) + 10 + msgp.IntSize + 9 + msgp.IntSize + 10 + msgp.IntSize + 5 + msgp.StringPrefixSize + len(z.Path) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 8 + msgp.TimeSize + 11 + msgp.TimeSize + 14 + msgp.Uint64Size + 14 + msgp.Uint64Size + 10 + msgp.Uint64Size + 12 + msgp.Uint64Size + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Object) + 20 + msgp.Uint64Size + 20 + msgp.Uint64Size + 16 + msgp.Uint64Size + 18 + msgp.Uint64Size + 14 + msgp.ArrayHeaderSize
|
s = 3 + 3 + msgp.StringPrefixSize + len(z.ID) + 10 + msgp.IntSize + 9 + msgp.IntSize + 10 + msgp.IntSize + 5 + msgp.StringPrefixSize + len(z.Path) + 9 + msgp.StringPrefixSize + len(z.Endpoint) + 8 + msgp.TimeSize + 11 + msgp.TimeSize + 18 + msgp.Uint64Size + 17 + msgp.Uint64Size + 12 + msgp.Uint64Size + 12 + msgp.Uint64Size + 10 + msgp.Uint64Size + 12 + msgp.Uint64Size + 7 + msgp.StringPrefixSize + len(z.Bucket) + 7 + msgp.StringPrefixSize + len(z.Object) + 18 + msgp.Uint64Size + 18 + msgp.Uint64Size + 16 + msgp.Uint64Size + 18 + msgp.Uint64Size + 14 + msgp.ArrayHeaderSize
|
||||||
for za0001 := range z.QueuedBuckets {
|
for za0001 := range z.QueuedBuckets {
|
||||||
s += msgp.StringPrefixSize + len(z.QueuedBuckets[za0001])
|
s += msgp.StringPrefixSize + len(z.QueuedBuckets[za0001])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate the transition storage ARNs
|
// Validate the transition storage ARNs
|
||||||
if err = validateTransitionTier(ctx, bucketLifecycle); err != nil {
|
if err = validateTransitionTier(bucketLifecycle); err != nil {
|
||||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-20
@@ -180,8 +180,8 @@ func initBackgroundTransition(ctx context.Context, objectAPI ObjectLayer) {
|
|||||||
|
|
||||||
var errInvalidStorageClass = errors.New("invalid storage class")
|
var errInvalidStorageClass = errors.New("invalid storage class")
|
||||||
|
|
||||||
func validateTransitionTier(ctx context.Context, lfc *lifecycle.Lifecycle) error {
|
func validateTransitionTier(lc *lifecycle.Lifecycle) error {
|
||||||
for _, rule := range lfc.Rules {
|
for _, rule := range lc.Rules {
|
||||||
if rule.Transition.StorageClass == "" {
|
if rule.Transition.StorageClass == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -269,13 +269,13 @@ func expireTransitionedObject(ctx context.Context, objectAPI ObjectLayer, oi *Ob
|
|||||||
}
|
}
|
||||||
|
|
||||||
// generate an object name for transitioned object
|
// generate an object name for transitioned object
|
||||||
func genTransitionObjName() (string, error) {
|
func genTransitionObjName(bucket string) (string, error) {
|
||||||
u, err := uuid.NewRandom()
|
u, err := uuid.NewRandom()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
us := u.String()
|
us := u.String()
|
||||||
obj := fmt.Sprintf("%s/%s/%s", us[0:2], us[2:4], us)
|
obj := fmt.Sprintf("%s/%s/%s/%s/%s", globalDeploymentID, bucket, us[0:2], us[2:4], us)
|
||||||
return obj, nil
|
return obj, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,15 +288,10 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
lcOpts := lifecycle.ObjectOpts{
|
|
||||||
Name: oi.Name,
|
|
||||||
UserTags: oi.UserTags,
|
|
||||||
}
|
|
||||||
tierName := getLifeCycleTransitionTier(ctx, lc, oi.Bucket, lcOpts)
|
|
||||||
opts := ObjectOptions{
|
opts := ObjectOptions{
|
||||||
Transition: TransitionOptions{
|
Transition: TransitionOptions{
|
||||||
Status: lifecycle.TransitionPending,
|
Status: lifecycle.TransitionPending,
|
||||||
Tier: tierName,
|
Tier: lc.TransitionTier(oi.ToLifecycleOpts()),
|
||||||
ETag: oi.ETag,
|
ETag: oi.ETag,
|
||||||
},
|
},
|
||||||
VersionID: oi.VersionID,
|
VersionID: oi.VersionID,
|
||||||
@@ -306,16 +301,6 @@ func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo)
|
|||||||
return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
return objectAPI.TransitionObject(ctx, oi.Bucket, oi.Name, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLifeCycleTransitionTier returns storage class for transition target
|
|
||||||
func getLifeCycleTransitionTier(ctx context.Context, lc *lifecycle.Lifecycle, bucket string, obj lifecycle.ObjectOpts) string {
|
|
||||||
for _, rule := range lc.FilterActionableRules(obj) {
|
|
||||||
if rule.Transition.StorageClass != "" {
|
|
||||||
return rule.Transition.StorageClass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTransitionedObjectReader returns a reader from the transitioned tier.
|
// getTransitionedObjectReader returns a reader from the transitioned tier.
|
||||||
func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, oi ObjectInfo, opts ObjectOptions) (gr *GetObjectReader, err error) {
|
||||||
tgtClient, err := globalTierConfigMgr.getDriver(oi.TransitionTier)
|
tgtClient, err := globalTierConfigMgr.getDriver(oi.TransitionTier)
|
||||||
@@ -673,3 +658,21 @@ func isRestoredObjectOnDisk(meta map[string]string) (onDisk bool) {
|
|||||||
}
|
}
|
||||||
return onDisk
|
return onDisk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToLifecycleOpts returns lifecycle.ObjectOpts value for oi.
|
||||||
|
func (oi ObjectInfo) ToLifecycleOpts() lifecycle.ObjectOpts {
|
||||||
|
return lifecycle.ObjectOpts{
|
||||||
|
Name: oi.Name,
|
||||||
|
UserTags: oi.UserTags,
|
||||||
|
VersionID: oi.VersionID,
|
||||||
|
ModTime: oi.ModTime,
|
||||||
|
IsLatest: oi.IsLatest,
|
||||||
|
NumVersions: oi.NumVersions,
|
||||||
|
DeleteMarker: oi.DeleteMarker,
|
||||||
|
SuccessorModTime: oi.SuccessorModTime,
|
||||||
|
RestoreOngoing: oi.RestoreOngoing,
|
||||||
|
RestoreExpires: oi.RestoreExpires,
|
||||||
|
TransitionStatus: oi.TransitionStatus,
|
||||||
|
RemoteTiersImmediately: globalDebugRemoteTiersImmediately,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -240,7 +239,7 @@ func TestValidateTransitionTier(t *testing.T) {
|
|||||||
t.Fatalf("Test %d: Failed to parse lifecycle config %v", i+1, err)
|
t.Fatalf("Test %d: Failed to parse lifecycle config %v", i+1, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = validateTransitionTier(context.Background(), lc)
|
err = validateTransitionTier(lc)
|
||||||
if err != tc.expectedErr {
|
if err != tc.expectedErr {
|
||||||
t.Fatalf("Test %d: Expected %v but got %v", i+1, tc.expectedErr, err)
|
t.Fatalf("Test %d: Expected %v but got %v", i+1, tc.expectedErr, err)
|
||||||
}
|
}
|
||||||
|
|||||||
+37
-18
@@ -43,6 +43,8 @@ import (
|
|||||||
iampolicy "github.com/minio/pkg/iam/policy"
|
iampolicy "github.com/minio/pkg/iam/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const throttleDeadline = 1 * time.Hour
|
||||||
|
|
||||||
// gets replication config associated to a given bucket name.
|
// gets replication config associated to a given bucket name.
|
||||||
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
func getReplicationConfig(ctx context.Context, bucketName string) (rc *replication.Config, err error) {
|
||||||
if globalIsGateway {
|
if globalIsGateway {
|
||||||
@@ -792,15 +794,18 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
Bucket: objInfo.Bucket,
|
Bucket: objInfo.Bucket,
|
||||||
HeaderSize: headerSize,
|
HeaderSize: headerSize,
|
||||||
}
|
}
|
||||||
newCtx, cancel := context.WithTimeout(ctx, globalOperationTimeout.Timeout())
|
newCtx := ctx
|
||||||
defer cancel()
|
if globalBucketMonitor.IsThrottled(bucket) {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
newCtx, cancel = context.WithTimeout(ctx, throttleDeadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
|
r := bandwidth.NewMonitoredReader(newCtx, globalBucketMonitor, gr, opts)
|
||||||
if len(objInfo.Parts) > 1 {
|
if objInfo.Multipart {
|
||||||
if uploadID, err := replicateObjectWithMultipart(ctx, c, dest.Bucket, object, r, objInfo, putOpts); err != nil {
|
if err := replicateObjectWithMultipart(ctx, c, dest.Bucket, object,
|
||||||
|
r, objInfo, putOpts); err != nil {
|
||||||
replicationStatus = replication.Failed
|
replicationStatus = replication.Failed
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||||
// block and abort remote upload upon failure.
|
|
||||||
c.AbortMultipartUpload(ctx, dest.Bucket, object, uploadID)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err = c.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts); err != nil {
|
if _, err = c.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts); err != nil {
|
||||||
@@ -839,7 +844,7 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
for k, v := range objInfo.UserDefined {
|
for k, v := range objInfo.UserDefined {
|
||||||
popts.UserDefined[k] = v
|
popts.UserDefined[k] = v
|
||||||
}
|
}
|
||||||
popts.UserDefined[xhttp.AmzBucketReplicationStatus] = replication.Completed.String()
|
popts.UserDefined[xhttp.AmzBucketReplicationStatus] = replicationStatus.String()
|
||||||
if objInfo.UserTags != "" {
|
if objInfo.UserTags != "" {
|
||||||
popts.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
popts.UserDefined[xhttp.AmzObjectTagging] = objInfo.UserTags
|
||||||
}
|
}
|
||||||
@@ -868,38 +873,52 @@ func replicateObject(ctx context.Context, ri ReplicateObjectInfo, objectAPI Obje
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func replicateObjectWithMultipart(ctx context.Context, c *miniogo.Core, bucket, object string, r io.Reader, objInfo ObjectInfo, opts miniogo.PutObjectOptions) (uploadID string, err error) {
|
func replicateObjectWithMultipart(ctx context.Context, c *miniogo.Core, bucket, object string, r io.Reader, objInfo ObjectInfo, opts miniogo.PutObjectOptions) (err error) {
|
||||||
var uploadedParts []miniogo.CompletePart
|
var uploadedParts []miniogo.CompletePart
|
||||||
uploadID, err = c.NewMultipartUpload(context.Background(), bucket, object, opts)
|
uploadID, err := c.NewMultipartUpload(context.Background(), bucket, object, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
// block and abort remote upload upon failure.
|
||||||
|
if aerr := c.AbortMultipartUpload(ctx, bucket, object, uploadID); aerr != nil {
|
||||||
|
aerr = fmt.Errorf("Unable to cleanup failed multipart replication %s on remote %s/%s: %w", uploadID, bucket, object, aerr)
|
||||||
|
logger.LogIf(ctx, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hr *hash.Reader
|
hr *hash.Reader
|
||||||
pInfo miniogo.ObjectPart
|
pInfo miniogo.ObjectPart
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, partInfo := range objInfo.Parts {
|
for _, partInfo := range objInfo.Parts {
|
||||||
hr, err = hash.NewReader(r, partInfo.Size, "", "", partInfo.Size)
|
hr, err = hash.NewReader(r, partInfo.Size, "", "", partInfo.Size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, "", "", opts.ServerSideEncryption)
|
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, "", "", opts.ServerSideEncryption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return err
|
||||||
}
|
}
|
||||||
if pInfo.Size != partInfo.Size {
|
if pInfo.Size != partInfo.Size {
|
||||||
return uploadID, fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.Size)
|
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.Size)
|
||||||
}
|
}
|
||||||
uploadedParts = append(uploadedParts, miniogo.CompletePart{
|
uploadedParts = append(uploadedParts, miniogo.CompletePart{
|
||||||
PartNumber: pInfo.PartNumber,
|
PartNumber: pInfo.PartNumber,
|
||||||
ETag: pInfo.ETag,
|
ETag: pInfo.ETag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_, err = c.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, miniogo.PutObjectOptions{Internal: miniogo.AdvancedPutOptions{
|
_, err = c.CompleteMultipartUpload(ctx, bucket, object, uploadID, uploadedParts, miniogo.PutObjectOptions{
|
||||||
SourceMTime: objInfo.ModTime,
|
Internal: miniogo.AdvancedPutOptions{
|
||||||
ReplicationRequest: true, // always set this to distinguish between `mc mirror` replication and serverside
|
SourceMTime: objInfo.ModTime,
|
||||||
}})
|
// always set this to distinguish between `mc mirror` replication and serverside
|
||||||
return
|
ReplicationRequest: true,
|
||||||
|
}})
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterReplicationStatusMetadata filters replication status metadata for COPY
|
// filterReplicationStatusMetadata filters replication status metadata for COPY
|
||||||
|
|||||||
@@ -346,6 +346,7 @@ func (sys *BucketTargetSys) getRemoteTargetClient(tcfg *madmin.BucketTarget) (*T
|
|||||||
getRemoteTargetInstanceTransportOnce.Do(func() {
|
getRemoteTargetInstanceTransportOnce.Do(func() {
|
||||||
getRemoteTargetInstanceTransport = NewRemoteTargetHTTPTransport()
|
getRemoteTargetInstanceTransport = NewRemoteTargetHTTPTransport()
|
||||||
})
|
})
|
||||||
|
|
||||||
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{
|
api, err := minio.New(tcfg.Endpoint, &miniogo.Options{
|
||||||
Creds: creds,
|
Creds: creds,
|
||||||
Secure: tcfg.Secure,
|
Secure: tcfg.Secure,
|
||||||
|
|||||||
+28
-27
@@ -44,6 +44,7 @@ import (
|
|||||||
"github.com/minio/console/restapi"
|
"github.com/minio/console/restapi"
|
||||||
"github.com/minio/console/restapi/operations"
|
"github.com/minio/console/restapi/operations"
|
||||||
"github.com/minio/kes"
|
"github.com/minio/kes"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
"github.com/minio/minio-go/v7/pkg/set"
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
"github.com/minio/minio/internal/auth"
|
"github.com/minio/minio/internal/auth"
|
||||||
@@ -104,6 +105,10 @@ func init() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All minio-go API operations shall be performed only once,
|
||||||
|
// another way to look at this is we are turning off retries.
|
||||||
|
minio.MaxRetry = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
const consolePrefix = "CONSOLE_"
|
const consolePrefix = "CONSOLE_"
|
||||||
@@ -111,7 +116,10 @@ const consolePrefix = "CONSOLE_"
|
|||||||
func minioConfigToConsoleFeatures() {
|
func minioConfigToConsoleFeatures() {
|
||||||
os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID)
|
os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID)
|
||||||
os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID)
|
os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID)
|
||||||
os.Setenv("CONSOLE_MINIO_SERVER", getAPIEndpoints()[0])
|
if globalMinioEndpoint == "" {
|
||||||
|
logger.Fatal(errInvalidArgument, "Unable to start console service MinIO Endpoint is empty")
|
||||||
|
}
|
||||||
|
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
|
||||||
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
if value := env.Get("MINIO_LOG_QUERY_URL", ""); value != "" {
|
||||||
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
os.Setenv("CONSOLE_LOG_QUERY_URL", value)
|
||||||
if value := env.Get("MINIO_LOG_QUERY_AUTH_TOKEN", ""); value != "" {
|
if value := env.Get("MINIO_LOG_QUERY_AUTH_TOKEN", ""); value != "" {
|
||||||
@@ -200,30 +208,6 @@ func initConsoleServer() (*restapi.Server, error) {
|
|||||||
restapi.Hostname = globalMinioConsoleHost
|
restapi.Hostname = globalMinioConsoleHost
|
||||||
}
|
}
|
||||||
|
|
||||||
// subnet license refresh process
|
|
||||||
go func() {
|
|
||||||
// start refreshing subnet license after 5 seconds..
|
|
||||||
time.Sleep(time.Second * 5)
|
|
||||||
|
|
||||||
failedAttempts := 0
|
|
||||||
for {
|
|
||||||
if err := restapi.RefreshLicense(); err != nil {
|
|
||||||
failedAttempts++
|
|
||||||
// end license refresh after 3 consecutive failed attempts
|
|
||||||
if failedAttempts >= 3 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// wait 5 minutes and retry again
|
|
||||||
time.Sleep(time.Minute * 5)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// if license refreshed successfully reset the counter
|
|
||||||
failedAttempts = 0
|
|
||||||
// try to refresh license every 24 hrs
|
|
||||||
time.Sleep(time.Hour * 24)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return server, nil
|
return server, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,19 +403,36 @@ func handleCommonEnvVars() {
|
|||||||
if redirectURL := env.Get(config.EnvMinIOBrowserRedirectURL, ""); redirectURL != "" {
|
if redirectURL := env.Get(config.EnvMinIOBrowserRedirectURL, ""); redirectURL != "" {
|
||||||
u, err := xnet.ParseHTTPURL(redirectURL)
|
u, err := xnet.ParseHTTPURL(redirectURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT value in environment variable")
|
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value in environment variable")
|
||||||
}
|
}
|
||||||
// Look for if URL has invalid values and return error.
|
// Look for if URL has invalid values and return error.
|
||||||
if !((u.Scheme == "http" || u.Scheme == "https") &&
|
if !((u.Scheme == "http" || u.Scheme == "https") &&
|
||||||
(u.Path == "/" || u.Path == "") && u.Opaque == "" &&
|
(u.Path == "/" || u.Path == "") && u.Opaque == "" &&
|
||||||
!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
|
!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
|
||||||
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u)
|
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u)
|
||||||
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT value is environment variable")
|
logger.Fatal(err, "Invalid MINIO_BROWSER_REDIRECT_URL value is environment variable")
|
||||||
}
|
}
|
||||||
|
u.Path = "" // remove any path component such as `/`
|
||||||
globalBrowserRedirectURL = u
|
globalBrowserRedirectURL = u
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if serverURL := env.Get(config.EnvMinIOServerURL, globalEndpoints.Localhost()); serverURL != "" {
|
||||||
|
u, err := xnet.ParseHTTPURL(serverURL)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal(err, "Invalid MINIO_SERVER_URL value in environment variable")
|
||||||
|
}
|
||||||
|
// Look for if URL has invalid values and return error.
|
||||||
|
if !((u.Scheme == "http" || u.Scheme == "https") &&
|
||||||
|
(u.Path == "/" || u.Path == "") && u.Opaque == "" &&
|
||||||
|
!u.ForceQuery && u.RawQuery == "" && u.Fragment == "") {
|
||||||
|
err := fmt.Errorf("URL contains unexpected resources, expected URL to be of http(s)://minio.example.com format: %v", u)
|
||||||
|
logger.Fatal(err, "Invalid MINIO_SERVER_URL value is environment variable")
|
||||||
|
}
|
||||||
|
u.Path = "" // remove any path component such as `/`
|
||||||
|
globalMinioEndpoint = u.String()
|
||||||
|
}
|
||||||
|
|
||||||
globalFSOSync, err = config.ParseBool(env.Get(config.EnvFSOSync, config.EnableOff))
|
globalFSOSync, err = config.ParseBool(env.Get(config.EnvFSOSync, config.EnableOff))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable")
|
logger.Fatal(config.ErrInvalidFSOSyncValue(err), "Invalid MINIO_FS_OSYNC value in environment variable")
|
||||||
|
|||||||
+32
-26
@@ -43,6 +43,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/logger/target/http"
|
"github.com/minio/minio/internal/logger/target/http"
|
||||||
|
"github.com/minio/minio/internal/logger/target/kafka"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -58,7 +59,8 @@ func initHelp() {
|
|||||||
config.APISubSys: api.DefaultKVS,
|
config.APISubSys: api.DefaultKVS,
|
||||||
config.CredentialsSubSys: config.DefaultCredentialKVS,
|
config.CredentialsSubSys: config.DefaultCredentialKVS,
|
||||||
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
||||||
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
config.AuditWebhookSubSys: logger.DefaultAuditWebhookKVS,
|
||||||
|
config.AuditKafkaSubSys: logger.DefaultAuditKafkaKVS,
|
||||||
config.HealSubSys: heal.DefaultKVS,
|
config.HealSubSys: heal.DefaultKVS,
|
||||||
config.ScannerSubSys: scanner.DefaultKVS,
|
config.ScannerSubSys: scanner.DefaultKVS,
|
||||||
}
|
}
|
||||||
@@ -122,6 +124,11 @@ func initHelp() {
|
|||||||
Description: "send audit logs to webhook endpoints",
|
Description: "send audit logs to webhook endpoints",
|
||||||
MultipleTargets: true,
|
MultipleTargets: true,
|
||||||
},
|
},
|
||||||
|
config.HelpKV{
|
||||||
|
Key: config.AuditKafkaSubSys,
|
||||||
|
Description: "send audit logs to kafka endpoints",
|
||||||
|
MultipleTargets: true,
|
||||||
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: config.NotifyWebhookSubSys,
|
Key: config.NotifyWebhookSubSys,
|
||||||
Description: "publish bucket notifications to webhook endpoints",
|
Description: "publish bucket notifications to webhook endpoints",
|
||||||
@@ -197,7 +204,8 @@ func initHelp() {
|
|||||||
config.IdentityLDAPSubSys: xldap.Help,
|
config.IdentityLDAPSubSys: xldap.Help,
|
||||||
config.PolicyOPASubSys: opa.Help,
|
config.PolicyOPASubSys: opa.Help,
|
||||||
config.LoggerWebhookSubSys: logger.Help,
|
config.LoggerWebhookSubSys: logger.Help,
|
||||||
config.AuditWebhookSubSys: logger.HelpAudit,
|
config.AuditWebhookSubSys: logger.HelpWebhook,
|
||||||
|
config.AuditKafkaSubSys: logger.HelpKafka,
|
||||||
config.NotifyAMQPSubSys: notify.HelpAMQP,
|
config.NotifyAMQPSubSys: notify.HelpAMQP,
|
||||||
config.NotifyKafkaSubSys: notify.HelpKafka,
|
config.NotifyKafkaSubSys: notify.HelpKafka,
|
||||||
config.NotifyMQTTSubSys: notify.HelpMQTT,
|
config.NotifyMQTTSubSys: notify.HelpMQTT,
|
||||||
@@ -478,42 +486,40 @@ func lookupConfigs(s config.Config, setDriveCounts []int) {
|
|||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize logger: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize logger: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, l := range loggerCfg.HTTP {
|
for _, l := range loggerCfg.HTTP {
|
||||||
if l.Enabled {
|
if l.Enabled {
|
||||||
|
l.LogOnce = logger.LogOnceIf
|
||||||
|
l.UserAgent = loggerUserAgent
|
||||||
|
l.Transport = NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
||||||
// Enable http logging
|
// Enable http logging
|
||||||
if err = logger.AddTarget(
|
if err = logger.AddTarget(http.New(l)); err != nil {
|
||||||
http.New(
|
|
||||||
http.WithTargetName(k),
|
|
||||||
http.WithEndpoint(l.Endpoint),
|
|
||||||
http.WithAuthToken(l.AuthToken),
|
|
||||||
http.WithUserAgent(loggerUserAgent),
|
|
||||||
http.WithLogKind(string(logger.All)),
|
|
||||||
http.WithTransport(NewGatewayHTTPTransport()),
|
|
||||||
),
|
|
||||||
); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize console HTTP target: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize console HTTP target: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, l := range loggerCfg.Audit {
|
for _, l := range loggerCfg.AuditWebhook {
|
||||||
if l.Enabled {
|
if l.Enabled {
|
||||||
|
l.LogOnce = logger.LogOnceIf
|
||||||
|
l.UserAgent = loggerUserAgent
|
||||||
|
l.Transport = NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)
|
||||||
// Enable http audit logging
|
// Enable http audit logging
|
||||||
if err = logger.AddAuditTarget(
|
if err = logger.AddAuditTarget(http.New(l)); err != nil {
|
||||||
http.New(
|
|
||||||
http.WithTargetName(k),
|
|
||||||
http.WithEndpoint(l.Endpoint),
|
|
||||||
http.WithAuthToken(l.AuthToken),
|
|
||||||
http.WithUserAgent(loggerUserAgent),
|
|
||||||
http.WithLogKind(string(logger.All)),
|
|
||||||
http.WithTransport(NewGatewayHTTPTransportWithClientCerts(l.ClientCert, l.ClientKey)),
|
|
||||||
),
|
|
||||||
); err != nil {
|
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit HTTP target: %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit HTTP target: %w", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, l := range loggerCfg.AuditKafka {
|
||||||
|
if l.Enabled {
|
||||||
|
l.LogOnce = logger.LogOnceIf
|
||||||
|
// Enable Kafka audit logging
|
||||||
|
if err = logger.AddAuditTarget(kafka.New(l)); err != nil {
|
||||||
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize audit Kafka target: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
globalConfigTargetList, err = notify.GetNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), false)
|
globalConfigTargetList, err = notify.GetNotificationTargets(GlobalContext, s, NewGatewayHTTPTransport(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
logger.LogIf(ctx, fmt.Errorf("Unable to initialize notification target(s): %w", err))
|
||||||
@@ -717,8 +723,8 @@ func loadConfig(objAPI ObjectLayer) error {
|
|||||||
func getOpenIDValidators(cfg openid.Config) *openid.Validators {
|
func getOpenIDValidators(cfg openid.Config) *openid.Validators {
|
||||||
validators := openid.NewValidators()
|
validators := openid.NewValidators()
|
||||||
|
|
||||||
if cfg.JWKS.URL != nil {
|
if cfg.Enabled {
|
||||||
validators.Add(openid.NewJWT(cfg))
|
validators.Add(&cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
return validators
|
return validators
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import (
|
|||||||
"github.com/minio/minio/internal/event/target"
|
"github.com/minio/minio/internal/event/target"
|
||||||
"github.com/minio/minio/internal/kms"
|
"github.com/minio/minio/internal/kms"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
|
"github.com/minio/minio/internal/logger/target/http"
|
||||||
xnet "github.com/minio/pkg/net"
|
xnet "github.com/minio/pkg/net"
|
||||||
"github.com/minio/pkg/quick"
|
"github.com/minio/pkg/quick"
|
||||||
)
|
)
|
||||||
@@ -2383,8 +2384,8 @@ func migrateV26ToV27() error {
|
|||||||
// Enable console logging by default to avoid breaking users
|
// Enable console logging by default to avoid breaking users
|
||||||
// current deployments
|
// current deployments
|
||||||
srvConfig.Logger.Console.Enabled = true
|
srvConfig.Logger.Console.Enabled = true
|
||||||
srvConfig.Logger.HTTP = make(map[string]logger.HTTP)
|
srvConfig.Logger.HTTP = make(map[string]http.Config)
|
||||||
srvConfig.Logger.HTTP["1"] = logger.HTTP{}
|
srvConfig.Logger.HTTP["1"] = http.Config{}
|
||||||
|
|
||||||
if err = quick.SaveConfig(srvConfig, configFile, globalEtcdClient); err != nil {
|
if err = quick.SaveConfig(srvConfig, configFile, globalEtcdClient); err != nil {
|
||||||
return fmt.Errorf("Failed to migrate config from ‘26’ to ‘27’. %w", err)
|
return fmt.Errorf("Failed to migrate config from ‘26’ to ‘27’. %w", err)
|
||||||
@@ -2748,7 +2749,7 @@ func migrateMinioSysConfigToKV(objAPI ObjectLayer) error {
|
|||||||
for k, loggerArgs := range cfg.Logger.HTTP {
|
for k, loggerArgs := range cfg.Logger.HTTP {
|
||||||
logger.SetLoggerHTTP(newCfg, k, loggerArgs)
|
logger.SetLoggerHTTP(newCfg, k, loggerArgs)
|
||||||
}
|
}
|
||||||
for k, auditArgs := range cfg.Logger.Audit {
|
for k, auditArgs := range cfg.Logger.AuditWebhook {
|
||||||
logger.SetLoggerHTTPAudit(newCfg, k, auditArgs)
|
logger.SetLoggerHTTPAudit(newCfg, k, auditArgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ func (sys *HTTPConsoleLoggerSys) Subscribe(subCh chan interface{}, doneCh <-chan
|
|||||||
sys.pubsub.Subscribe(subCh, doneCh, filter)
|
sys.pubsub.Subscribe(subCh, doneCh, filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate if HTTPConsoleLoggerSys is valid, always returns nil right now
|
// Init if HTTPConsoleLoggerSys is valid, always returns nil right now
|
||||||
func (sys *HTTPConsoleLoggerSys) Validate() error {
|
func (sys *HTTPConsoleLoggerSys) Init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-31
@@ -1020,22 +1020,7 @@ func (i *scannerItem) applyActions(ctx context.Context, o ObjectLayer, meta acti
|
|||||||
}
|
}
|
||||||
|
|
||||||
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, obj ObjectInfo, debug bool) (action lifecycle.Action) {
|
func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, obj ObjectInfo, debug bool) (action lifecycle.Action) {
|
||||||
lcOpts := lifecycle.ObjectOpts{
|
action = lc.ComputeAction(obj.ToLifecycleOpts())
|
||||||
Name: obj.Name,
|
|
||||||
UserTags: obj.UserTags,
|
|
||||||
ModTime: obj.ModTime,
|
|
||||||
VersionID: obj.VersionID,
|
|
||||||
DeleteMarker: obj.DeleteMarker,
|
|
||||||
IsLatest: obj.IsLatest,
|
|
||||||
NumVersions: obj.NumVersions,
|
|
||||||
SuccessorModTime: obj.SuccessorModTime,
|
|
||||||
RestoreOngoing: obj.RestoreOngoing,
|
|
||||||
RestoreExpires: obj.RestoreExpires,
|
|
||||||
TransitionStatus: obj.TransitionStatus,
|
|
||||||
RemoteTiersImmediately: globalDebugRemoteTiersImmediately,
|
|
||||||
}
|
|
||||||
|
|
||||||
action = lc.ComputeAction(lcOpts)
|
|
||||||
if debug {
|
if debug {
|
||||||
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
console.Debugf(applyActionsLogPrefix+" lifecycle: Secondary scan: %v\n", action)
|
||||||
}
|
}
|
||||||
@@ -1086,25 +1071,11 @@ func applyTransitionAction(ctx context.Context, action lifecycle.Action, objLaye
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, restoredObject bool) bool {
|
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, restoredObject bool) bool {
|
||||||
lcOpts := lifecycle.ObjectOpts{
|
|
||||||
Name: obj.Name,
|
|
||||||
UserTags: obj.UserTags,
|
|
||||||
ModTime: obj.ModTime,
|
|
||||||
VersionID: obj.VersionID,
|
|
||||||
DeleteMarker: obj.DeleteMarker,
|
|
||||||
IsLatest: obj.IsLatest,
|
|
||||||
NumVersions: obj.NumVersions,
|
|
||||||
SuccessorModTime: obj.SuccessorModTime,
|
|
||||||
RestoreOngoing: obj.RestoreOngoing,
|
|
||||||
RestoreExpires: obj.RestoreExpires,
|
|
||||||
TransitionStatus: obj.TransitionStatus,
|
|
||||||
}
|
|
||||||
|
|
||||||
action := expireObj
|
action := expireObj
|
||||||
if restoredObject {
|
if restoredObject {
|
||||||
action = expireRestoredObj
|
action = expireRestoredObj
|
||||||
}
|
}
|
||||||
if err := expireTransitionedObject(ctx, objLayer, &obj, lcOpts, action); err != nil {
|
if err := expireTransitionedObject(ctx, objLayer, &obj, obj.ToLifecycleOpts(), action); err != nil {
|
||||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (t *testingLogger) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testingLogger) Validate() error {
|
func (t *testingLogger) Init() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -530,6 +530,18 @@ func (h dataUsageHash) Key() string {
|
|||||||
return string(h)
|
return string(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dataUsageCache) flattenChildrens(root dataUsageEntry) (m map[string]dataUsageEntry) {
|
||||||
|
m = make(map[string]dataUsageEntry)
|
||||||
|
for id := range root.Children {
|
||||||
|
e := d.Cache[id]
|
||||||
|
if len(e.Children) > 0 {
|
||||||
|
e = d.flatten(e)
|
||||||
|
}
|
||||||
|
m[id] = e
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// flatten all children of the root into the root element and return it.
|
// flatten all children of the root into the root element and return it.
|
||||||
func (d *dataUsageCache) flatten(root dataUsageEntry) dataUsageEntry {
|
func (d *dataUsageCache) flatten(root dataUsageEntry) dataUsageEntry {
|
||||||
for id := range root.Children {
|
for id := range root.Children {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
@@ -59,6 +60,40 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// loadPrefixUsageFromBackend returns prefix usages found in passed buckets
|
||||||
|
// e.g.: /testbucket/prefix => 355601334
|
||||||
|
func loadPrefixUsageFromBackend(ctx context.Context, objAPI ObjectLayer, bucket string) (map[string]uint64, error) {
|
||||||
|
z, ok := objAPI.(*erasureServerPools)
|
||||||
|
if !ok {
|
||||||
|
// Prefix usage is empty
|
||||||
|
return map[string]uint64{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cache := dataUsageCache{}
|
||||||
|
|
||||||
|
m := make(map[string]uint64)
|
||||||
|
for _, pool := range z.serverPools {
|
||||||
|
for _, er := range pool.sets {
|
||||||
|
// Load bucket usage prefixes
|
||||||
|
if err := cache.load(ctx, er, bucket+slashSeparator+dataUsageCacheName); err == nil {
|
||||||
|
root := cache.find(bucket)
|
||||||
|
if root == nil {
|
||||||
|
// We dont have usage information for this bucket in this
|
||||||
|
// set, go to the next set
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for id, usageInfo := range cache.flattenChildrens(*root) {
|
||||||
|
prefix := strings.TrimPrefix(id, bucket+slashSeparator)
|
||||||
|
m[prefix] += uint64(usageInfo.Size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (madmin.DataUsageInfo, error) {
|
func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (madmin.DataUsageInfo, error) {
|
||||||
r, err := objAPI.GetObjectNInfo(ctx, dataUsageBucket, dataUsageObjName, nil, http.Header{}, readLock, ObjectOptions{})
|
r, err := objAPI.GetObjectNInfo(ctx, dataUsageBucket, dataUsageObjName, nil, http.Header{}, readLock, ObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+6
-2
@@ -245,7 +245,7 @@ func (l *EndpointServerPools) Add(zeps PoolEndpoints) error {
|
|||||||
func (l EndpointServerPools) Localhost() string {
|
func (l EndpointServerPools) Localhost() string {
|
||||||
for _, ep := range l {
|
for _, ep := range l {
|
||||||
for _, endpoint := range ep.Endpoints {
|
for _, endpoint := range ep.Endpoints {
|
||||||
if endpoint.IsLocal {
|
if endpoint.IsLocal && endpoint.Host != "" {
|
||||||
u := &url.URL{
|
u := &url.URL{
|
||||||
Scheme: endpoint.Scheme,
|
Scheme: endpoint.Scheme,
|
||||||
Host: endpoint.Host,
|
Host: endpoint.Host,
|
||||||
@@ -254,7 +254,11 @@ func (l EndpointServerPools) Localhost() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
host := globalMinioHost
|
||||||
|
if host == "" {
|
||||||
|
host = sortIPs(localIP4.ToSlice())[0]
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalDisksPaths returns the disk paths of the local disks
|
// LocalDisksPaths returns the disk paths of the local disks
|
||||||
|
|||||||
@@ -47,38 +47,6 @@ func (er erasureObjects) getLoadBalancedLocalDisks() (newDisks []StorageAPI) {
|
|||||||
return newDisks
|
return newDisks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {
|
|
||||||
disks := er.getDisks()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var mu sync.Mutex
|
|
||||||
for _, i := range hashOrder(UTCNow().String(), len(disks)) {
|
|
||||||
i := i
|
|
||||||
wg.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
if disks[i-1] == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
di, err := disks[i-1].DiskInfo(context.Background())
|
|
||||||
if err != nil || di.Healing {
|
|
||||||
// - Do not consume disks which are not reachable
|
|
||||||
// unformatted or simply not accessible for some reason.
|
|
||||||
//
|
|
||||||
// - Do not consume disks which are being healed
|
|
||||||
//
|
|
||||||
// - Future: skip busy disks
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mu.Lock()
|
|
||||||
newDisks = append(newDisks, disks[i-1])
|
|
||||||
mu.Unlock()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
return newDisks
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLoadBalancedDisks - fetches load balanced (sufficiently randomized) disk slice.
|
// getLoadBalancedDisks - fetches load balanced (sufficiently randomized) disk slice.
|
||||||
// ensures to skip disks if they are not healing and online.
|
// ensures to skip disks if they are not healing and online.
|
||||||
func (er erasureObjects) getLoadBalancedDisks(optimized bool) []StorageAPI {
|
func (er erasureObjects) getLoadBalancedDisks(optimized bool) []StorageAPI {
|
||||||
|
|||||||
@@ -189,35 +189,6 @@ func getLatestFileInfo(ctx context.Context, partsMetadata []FileInfo, errs []err
|
|||||||
return latestFileInfo, nil
|
return latestFileInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fileInfoConsistent whether all fileinfos are consistent with each other.
|
|
||||||
// Will return false if any fileinfo mismatches.
|
|
||||||
func fileInfoConsistent(ctx context.Context, partsMetadata []FileInfo, errs []error) bool {
|
|
||||||
// There should be atleast half correct entries, if not return failure
|
|
||||||
if reducedErr := reduceReadQuorumErrs(ctx, errs, nil, len(partsMetadata)/2); reducedErr != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if len(partsMetadata) == 1 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Reference
|
|
||||||
ref := partsMetadata[0]
|
|
||||||
if !ref.IsValid() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, meta := range partsMetadata[1:] {
|
|
||||||
if !meta.IsValid() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !meta.ModTime.Equal(ref.ModTime) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if meta.DataDir != ref.DataDir {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// disksWithAllParts - This function needs to be called with
|
// disksWithAllParts - This function needs to be called with
|
||||||
// []StorageAPI returned by listOnlineDisks. Returns,
|
// []StorageAPI returned by listOnlineDisks. Returns,
|
||||||
//
|
//
|
||||||
@@ -225,8 +196,9 @@ func fileInfoConsistent(ctx context.Context, partsMetadata []FileInfo, errs []er
|
|||||||
//
|
//
|
||||||
// - slice of errors about the state of data files on disk - can have
|
// - slice of errors about the state of data files on disk - can have
|
||||||
// a not-found error or a hash-mismatch error.
|
// a not-found error or a hash-mismatch error.
|
||||||
func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetadata []FileInfo, errs []error, bucket,
|
func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetadata []FileInfo,
|
||||||
object string, scanMode madmin.HealScanMode) ([]StorageAPI, []error) {
|
errs []error, bucket, object string, scanMode madmin.HealScanMode) ([]StorageAPI, []error) {
|
||||||
|
|
||||||
// List of disks having latest version of the object er.meta (by modtime)
|
// List of disks having latest version of the object er.meta (by modtime)
|
||||||
_, modTime, dataDir := listOnlineDisks(onlineDisks, partsMetadata, errs)
|
_, modTime, dataDir := listOnlineDisks(onlineDisks, partsMetadata, errs)
|
||||||
|
|
||||||
@@ -239,15 +211,17 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
// consider the offline disks as consistent.
|
// consider the offline disks as consistent.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(meta.Erasure.Distribution) != len(onlineDisks) {
|
if !meta.Deleted {
|
||||||
// Erasure distribution seems to have lesser
|
if len(meta.Erasure.Distribution) != len(onlineDisks) {
|
||||||
// number of items than number of online disks.
|
// Erasure distribution seems to have lesser
|
||||||
inconsistent++
|
// number of items than number of online disks.
|
||||||
continue
|
inconsistent++
|
||||||
}
|
continue
|
||||||
if meta.Erasure.Distribution[i] != meta.Erasure.Index {
|
}
|
||||||
// Mismatch indexes with distribution order
|
if meta.Erasure.Distribution[i] != meta.Erasure.Index {
|
||||||
inconsistent++
|
// Mismatch indexes with distribution order
|
||||||
|
inconsistent++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,8 +241,8 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
dataErrs[i] = errDiskNotFound
|
dataErrs[i] = errDiskNotFound
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
meta := partsMetadata[i]
|
|
||||||
|
|
||||||
|
meta := partsMetadata[i]
|
||||||
if !meta.ModTime.Equal(modTime) || meta.DataDir != dataDir {
|
if !meta.ModTime.Equal(modTime) || meta.DataDir != dataDir {
|
||||||
dataErrs[i] = errFileCorrupt
|
dataErrs[i] = errFileCorrupt
|
||||||
partsMetadata[i] = FileInfo{}
|
partsMetadata[i] = FileInfo{}
|
||||||
@@ -280,20 +254,22 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(meta.Erasure.Distribution) != len(onlineDisks) {
|
if !meta.Deleted {
|
||||||
// Erasure distribution is not the same as onlineDisks
|
if len(meta.Erasure.Distribution) != len(onlineDisks) {
|
||||||
// attempt a fix if possible, assuming other entries
|
// Erasure distribution is not the same as onlineDisks
|
||||||
// might have the right erasure distribution.
|
// attempt a fix if possible, assuming other entries
|
||||||
partsMetadata[i] = FileInfo{}
|
// might have the right erasure distribution.
|
||||||
dataErrs[i] = errFileCorrupt
|
partsMetadata[i] = FileInfo{}
|
||||||
continue
|
dataErrs[i] = errFileCorrupt
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Since erasure.Distribution is trustable we can fix the mismatching erasure.Index
|
// Since erasure.Distribution is trustable we can fix the mismatching erasure.Index
|
||||||
if meta.Erasure.Distribution[i] != meta.Erasure.Index {
|
if meta.Erasure.Distribution[i] != meta.Erasure.Index {
|
||||||
partsMetadata[i] = FileInfo{}
|
partsMetadata[i] = FileInfo{}
|
||||||
dataErrs[i] = errFileCorrupt
|
dataErrs[i] = errFileCorrupt
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,11 +296,11 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
|
|||||||
// disk has a valid xl.meta but may not have all the
|
// disk has a valid xl.meta but may not have all the
|
||||||
// parts. This is considered an outdated disk, since
|
// parts. This is considered an outdated disk, since
|
||||||
// it needs healing too.
|
// it needs healing too.
|
||||||
if !partsMetadata[i].IsRemote() {
|
if !partsMetadata[i].Deleted && !partsMetadata[i].IsRemote() {
|
||||||
dataErrs[i] = onlineDisk.VerifyFile(ctx, bucket, object, partsMetadata[i])
|
dataErrs[i] = onlineDisk.VerifyFile(ctx, bucket, object, partsMetadata[i])
|
||||||
}
|
}
|
||||||
case madmin.HealNormalScan:
|
case madmin.HealNormalScan:
|
||||||
if !partsMetadata[i].IsRemote() {
|
if !partsMetadata[i].Deleted && !partsMetadata[i].IsRemote() {
|
||||||
dataErrs[i] = onlineDisk.CheckParts(ctx, bucket, object, partsMetadata[i])
|
dataErrs[i] = onlineDisk.CheckParts(ctx, bucket, object, partsMetadata[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-41
@@ -212,7 +212,7 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, quorumModTime t
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if erErr == nil {
|
if erErr == nil {
|
||||||
if !meta.IsRemote() {
|
if !meta.Deleted && !meta.IsRemote() {
|
||||||
// If xl.meta was read fine but there may be problem with the part.N files.
|
// If xl.meta was read fine but there may be problem with the part.N files.
|
||||||
if IsErr(dataErr, []error{
|
if IsErr(dataErr, []error{
|
||||||
errFileNotFound,
|
errFileNotFound,
|
||||||
@@ -267,10 +267,10 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
// Re-read when we have lock...
|
// Re-read when we have lock...
|
||||||
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, bucket, object, versionID, true)
|
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, bucket, object, versionID, true)
|
||||||
|
|
||||||
_, err = getLatestFileInfo(ctx, partsMetadata, errs)
|
if _, err = getLatestFileInfo(ctx, partsMetadata, errs); err != nil {
|
||||||
if err != nil {
|
|
||||||
return er.purgeObjectDangling(ctx, bucket, object, versionID, partsMetadata, errs, []error{}, opts)
|
return er.purgeObjectDangling(ctx, bucket, object, versionID, partsMetadata, errs, []error{}, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// List of disks having latest version of the object er.meta
|
// List of disks having latest version of the object er.meta
|
||||||
// (by modtime).
|
// (by modtime).
|
||||||
_, modTime, dataDir := listOnlineDisks(storageDisks, partsMetadata, errs)
|
_, modTime, dataDir := listOnlineDisks(storageDisks, partsMetadata, errs)
|
||||||
@@ -292,7 +292,8 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
// used here for reconstruction. This is done to ensure that
|
// used here for reconstruction. This is done to ensure that
|
||||||
// we do not skip drives that have inconsistent metadata to be
|
// we do not skip drives that have inconsistent metadata to be
|
||||||
// skipped from purging when they are stale.
|
// skipped from purging when they are stale.
|
||||||
availableDisks, dataErrs := disksWithAllParts(ctx, storageDisks, partsMetadata, errs, bucket, object, scanMode)
|
availableDisks, dataErrs := disksWithAllParts(ctx, storageDisks, partsMetadata,
|
||||||
|
errs, bucket, object, scanMode)
|
||||||
|
|
||||||
// Loop to find number of disks with valid data, per-drive
|
// Loop to find number of disks with valid data, per-drive
|
||||||
// data state and a list of outdated disks on which data needs
|
// data state and a list of outdated disks on which data needs
|
||||||
@@ -357,7 +358,8 @@ func (er erasureObjects) healObject(ctx context.Context, bucket string, object s
|
|||||||
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
// File is fully gone, fileInfo is empty.
|
// File is fully gone, fileInfo is empty.
|
||||||
return defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
return er.defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs,
|
||||||
|
bucket, object, versionID), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If less than read quorum number of disks have all the parts
|
// If less than read quorum number of disks have all the parts
|
||||||
@@ -642,30 +644,25 @@ func (er erasureObjects) healObjectDir(ctx context.Context, bucket, object strin
|
|||||||
|
|
||||||
// Populates default heal result item entries with possible values when we are returning prematurely.
|
// Populates default heal result item entries with possible values when we are returning prematurely.
|
||||||
// This is to ensure that in any circumstance we are not returning empty arrays with wrong values.
|
// This is to ensure that in any circumstance we are not returning empty arrays with wrong values.
|
||||||
func defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints []string, errs []error, bucket, object, versionID string, defaultParityCount int) madmin.HealResultItem {
|
func (er erasureObjects) defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints []string, errs []error, bucket, object, versionID string) madmin.HealResultItem {
|
||||||
// Initialize heal result object
|
// Initialize heal result object
|
||||||
result := madmin.HealResultItem{
|
result := madmin.HealResultItem{
|
||||||
Type: madmin.HealItemObject,
|
Type: madmin.HealItemObject,
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Object: object,
|
Object: object,
|
||||||
VersionID: versionID,
|
ObjectSize: lfi.Size,
|
||||||
DiskCount: len(storageDisks),
|
VersionID: versionID,
|
||||||
|
DiskCount: len(storageDisks),
|
||||||
}
|
}
|
||||||
|
|
||||||
if lfi.IsValid() {
|
if lfi.IsValid() {
|
||||||
result.ObjectSize = lfi.Size
|
|
||||||
result.ParityBlocks = lfi.Erasure.ParityBlocks
|
result.ParityBlocks = lfi.Erasure.ParityBlocks
|
||||||
} else {
|
} else {
|
||||||
// Default to most common configuration for erasure blocks.
|
// Default to most common configuration for erasure blocks.
|
||||||
result.ParityBlocks = defaultParityCount
|
result.ParityBlocks = er.defaultParityCount
|
||||||
}
|
}
|
||||||
result.DataBlocks = len(storageDisks) - result.ParityBlocks
|
result.DataBlocks = len(storageDisks) - result.ParityBlocks
|
||||||
|
|
||||||
if errs == nil {
|
|
||||||
// No disks related errors are provided, quit
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, disk := range storageDisks {
|
for index, disk := range storageDisks {
|
||||||
if disk == nil {
|
if disk == nil {
|
||||||
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
||||||
@@ -684,6 +681,8 @@ func defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints
|
|||||||
switch errs[index] {
|
switch errs[index] {
|
||||||
case errFileNotFound, errVolumeNotFound:
|
case errFileNotFound, errVolumeNotFound:
|
||||||
driveState = madmin.DriveStateMissing
|
driveState = madmin.DriveStateMissing
|
||||||
|
case nil:
|
||||||
|
driveState = madmin.DriveStateOk
|
||||||
}
|
}
|
||||||
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
|
||||||
UUID: "",
|
UUID: "",
|
||||||
@@ -697,15 +696,6 @@ func defaultHealResult(lfi FileInfo, storageDisks []StorageAPI, storageEndpoints
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if !lfi.IsValid() {
|
|
||||||
// Default to most common configuration for erasure blocks.
|
|
||||||
result.ParityBlocks = defaultParityCount
|
|
||||||
result.DataBlocks = len(storageDisks) - defaultParityCount
|
|
||||||
} else {
|
|
||||||
result.ParityBlocks = lfi.Erasure.ParityBlocks
|
|
||||||
result.DataBlocks = lfi.Erasure.DataBlocks
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,15 +810,18 @@ func (er erasureObjects) purgeObjectDangling(ctx context.Context, bucket, object
|
|||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
return er.defaultHealResult(m, storageDisks, storageEndpoints,
|
||||||
|
errs, bucket, object, versionID), err
|
||||||
}
|
}
|
||||||
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), toObjectErr(err, bucket, object, versionID)
|
return er.defaultHealResult(m, storageDisks, storageEndpoints,
|
||||||
|
errs, bucket, object, versionID), toObjectErr(err, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
readQuorum := len(storageDisks) - er.defaultParityCount
|
readQuorum := len(storageDisks) - er.defaultParityCount
|
||||||
|
|
||||||
err := toObjectErr(reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum), bucket, object, versionID)
|
err := toObjectErr(reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum),
|
||||||
return defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
bucket, object, versionID)
|
||||||
|
return er.defaultHealResult(m, storageDisks, storageEndpoints, errs, bucket, object, versionID), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Object is considered dangling/corrupted if any only
|
// Object is considered dangling/corrupted if any only
|
||||||
@@ -914,22 +907,15 @@ func (er erasureObjects) HealObject(ctx context.Context, bucket, object, version
|
|||||||
|
|
||||||
// Perform quick read without lock.
|
// Perform quick read without lock.
|
||||||
// This allows to quickly check if all is ok or all are missing.
|
// This allows to quickly check if all is ok or all are missing.
|
||||||
partsMetadata, errs := readAllFileInfo(healCtx, storageDisks, bucket, object, versionID, false)
|
_, errs := readAllFileInfo(healCtx, storageDisks, bucket, object, versionID, false)
|
||||||
if isAllNotFound(errs) {
|
if isAllNotFound(errs) {
|
||||||
err = toObjectErr(errFileNotFound, bucket, object)
|
err = toObjectErr(errFileNotFound, bucket, object)
|
||||||
if versionID != "" {
|
if versionID != "" {
|
||||||
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
err = toObjectErr(errFileVersionNotFound, bucket, object, versionID)
|
||||||
}
|
}
|
||||||
// Nothing to do, file is already gone.
|
// Nothing to do, file is already gone.
|
||||||
return defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs, bucket, object, versionID, er.defaultParityCount), err
|
return er.defaultHealResult(FileInfo{}, storageDisks, storageEndpoints,
|
||||||
}
|
errs, bucket, object, versionID), err
|
||||||
|
|
||||||
// Return early if all ok and not deep scanning.
|
|
||||||
if opts.ScanMode == madmin.HealNormalScan && fileInfoConsistent(ctx, partsMetadata, errs) {
|
|
||||||
fi, err := getLatestFileInfo(ctx, partsMetadata, errs)
|
|
||||||
if err == nil && fi.VersionID == versionID {
|
|
||||||
return defaultHealResult(fi, storageDisks, storageEndpoints, errs, bucket, object, versionID, fi.Erasure.ParityBlocks), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Heal the object.
|
// Heal the object.
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/bucket/replication"
|
"github.com/minio/minio/internal/bucket/replication"
|
||||||
|
"github.com/minio/minio/internal/crypto"
|
||||||
|
"github.com/minio/minio/internal/etag"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/minio/internal/sync/errgroup"
|
"github.com/minio/minio/internal/sync/errgroup"
|
||||||
@@ -141,6 +143,13 @@ func (fi FileInfo) ToObjectInfo(bucket, object string) ObjectInfo {
|
|||||||
// Extract etag from metadata.
|
// Extract etag from metadata.
|
||||||
objInfo.ETag = extractETag(fi.Metadata)
|
objInfo.ETag = extractETag(fi.Metadata)
|
||||||
|
|
||||||
|
// Verify if Etag is parseable, if yes
|
||||||
|
// then check if its multipart etag.
|
||||||
|
et, e := etag.Parse(objInfo.ETag)
|
||||||
|
if e == nil {
|
||||||
|
objInfo.Multipart = et.IsMultipart()
|
||||||
|
}
|
||||||
|
|
||||||
// Add user tags to the object info
|
// Add user tags to the object info
|
||||||
tags := fi.Metadata[xhttp.AmzObjectTagging]
|
tags := fi.Metadata[xhttp.AmzObjectTagging]
|
||||||
if len(tags) != 0 {
|
if len(tags) != 0 {
|
||||||
@@ -165,6 +174,14 @@ func (fi FileInfo) ToObjectInfo(bucket, object string) ObjectInfo {
|
|||||||
// Tags have also been extracted, we remove that as well.
|
// Tags have also been extracted, we remove that as well.
|
||||||
objInfo.UserDefined = cleanMetadata(fi.Metadata)
|
objInfo.UserDefined = cleanMetadata(fi.Metadata)
|
||||||
|
|
||||||
|
// Set multipart for encryption properly if
|
||||||
|
// not set already.
|
||||||
|
if !objInfo.Multipart {
|
||||||
|
if _, ok := crypto.IsEncrypted(objInfo.UserDefined); ok {
|
||||||
|
objInfo.Multipart = crypto.IsMultiPart(objInfo.UserDefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All the parts per object.
|
// All the parts per object.
|
||||||
objInfo.Parts = fi.Parts
|
objInfo.Parts = fi.Parts
|
||||||
|
|
||||||
|
|||||||
@@ -945,7 +945,7 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
|||||||
if disk != nil && disk.IsOnline() {
|
if disk != nil && disk.IsOnline() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
er.addPartial(bucket, object, fi.VersionID)
|
er.addPartial(bucket, object, fi.VersionID, fi.Size)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+35
-22
@@ -267,7 +267,7 @@ func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, obje
|
|||||||
// due to a bug in RenameData() the fi.Data is not niled leading to
|
// due to a bug in RenameData() the fi.Data is not niled leading to
|
||||||
// GetObject thinking that fi.Data is valid while fi.Size has
|
// GetObject thinking that fi.Data is valid while fi.Size has
|
||||||
// changed already.
|
// changed already.
|
||||||
if _, ok := fi.Metadata[ReservedMetadataPrefixLower+"inline-data"]; ok {
|
if fi.InlineData() {
|
||||||
shardFileSize := erasure.ShardFileSize(fi.Size)
|
shardFileSize := erasure.ShardFileSize(fi.Size)
|
||||||
if shardFileSize >= 0 && shardFileSize >= smallFileThreshold {
|
if shardFileSize >= 0 && shardFileSize >= smallFileThreshold {
|
||||||
for i := range metaArr {
|
for i := range metaArr {
|
||||||
@@ -442,7 +442,9 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if missing metadata can be reconstructed, attempt to reconstruct.
|
// if missing metadata can be reconstructed, attempt to reconstruct.
|
||||||
if missingBlocks > 0 && missingBlocks < readQuorum {
|
// additionally do not heal delete markers inline, let them be
|
||||||
|
// healed upon regular heal process.
|
||||||
|
if !fi.Deleted && missingBlocks > 0 && missingBlocks < readQuorum {
|
||||||
if _, healing := er.getOnlineDisksWithHealing(); !healing {
|
if _, healing := er.getOnlineDisksWithHealing(); !healing {
|
||||||
go healObject(bucket, object, fi.VersionID, madmin.HealNormalScan)
|
go healObject(bucket, object, fi.VersionID, madmin.HealNormalScan)
|
||||||
}
|
}
|
||||||
@@ -816,7 +818,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
if len(inlineBuffers) > 0 {
|
if len(inlineBuffers) > 0 {
|
||||||
// Set an additional header when data is inlined.
|
// Set an additional header when data is inlined.
|
||||||
for index := range partsMetadata {
|
for index := range partsMetadata {
|
||||||
partsMetadata[index].Metadata[ReservedMetadataPrefixLower+"inline-data"] = "true"
|
partsMetadata[index].SetInlineData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -826,16 +828,6 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
return ObjectInfo{}, toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether a disk was initially or becomes offline
|
|
||||||
// during this upload, send it to the MRF list.
|
|
||||||
for i := 0; i < len(onlineDisks); i++ {
|
|
||||||
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
er.addPartial(bucket, object, fi.VersionID)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(onlineDisks); i++ {
|
for i := 0; i < len(onlineDisks); i++ {
|
||||||
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
||||||
// Object info is the same in all disks, so we can pick
|
// Object info is the same in all disks, so we can pick
|
||||||
@@ -844,6 +836,17 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Whether a disk was initially or becomes offline
|
||||||
|
// during this upload, send it to the MRF list.
|
||||||
|
for i := 0; i < len(onlineDisks); i++ {
|
||||||
|
if onlineDisks[i] != nil && onlineDisks[i].IsOnline() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
er.addPartial(bucket, object, fi.VersionID, fi.Size)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
online = countOnlineDisks(onlineDisks)
|
online = countOnlineDisks(onlineDisks)
|
||||||
|
|
||||||
return fi.ToObjectInfo(bucket, object), nil
|
return fi.ToObjectInfo(bucket, object), nil
|
||||||
@@ -1029,7 +1032,7 @@ func (er erasureObjects) DeleteObjects(ctx context.Context, bucket string, objec
|
|||||||
|
|
||||||
// all other direct versionId references we should
|
// all other direct versionId references we should
|
||||||
// ensure no dangling file is left over.
|
// ensure no dangling file is left over.
|
||||||
er.addPartial(bucket, version.Name, version.VersionID)
|
er.addPartial(bucket, version.Name, version.VersionID, -1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1177,7 +1180,7 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
if disk != nil && disk.IsOnline() {
|
if disk != nil && disk.IsOnline() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
er.addPartial(bucket, object, opts.VersionID)
|
er.addPartial(bucket, object, opts.VersionID, -1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1192,11 +1195,15 @@ func (er erasureObjects) DeleteObject(ctx context.Context, bucket, object string
|
|||||||
|
|
||||||
// Send the successful but partial upload/delete, however ignore
|
// Send the successful but partial upload/delete, however ignore
|
||||||
// if the channel is blocked by other items.
|
// if the channel is blocked by other items.
|
||||||
func (er erasureObjects) addPartial(bucket, object, versionID string) {
|
func (er erasureObjects) addPartial(bucket, object, versionID string, size int64) {
|
||||||
select {
|
globalMRFState.addPartialOp(partialOperation{
|
||||||
case er.mrfOpCh <- partialOperation{bucket: bucket, object: object, versionID: versionID}:
|
bucket: bucket,
|
||||||
default:
|
object: object,
|
||||||
}
|
versionID: versionID,
|
||||||
|
size: size,
|
||||||
|
setIndex: er.setIndex,
|
||||||
|
poolIndex: er.poolIndex,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
||||||
@@ -1388,7 +1395,13 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
|
|||||||
return toObjectErr(err, bucket, object)
|
return toObjectErr(err, bucket, object)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
destObj, err := genTransitionObjName()
|
|
||||||
|
// object content is small enough that it's inlined, skipping transition
|
||||||
|
if fi.InlineData() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
destObj, err := genTransitionObjName(bucket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1421,7 +1434,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
|
|||||||
if disk != nil && disk.IsOnline() {
|
if disk != nil && disk.IsOnline() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
er.addPartial(bucket, object, opts.VersionID)
|
er.addPartial(bucket, object, opts.VersionID, -1)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Notify object deleted event.
|
// Notify object deleted event.
|
||||||
|
|||||||
+1
-81
@@ -96,8 +96,6 @@ type erasureSets struct {
|
|||||||
|
|
||||||
disksStorageInfoCache timedValue
|
disksStorageInfoCache timedValue
|
||||||
|
|
||||||
mrfMU sync.Mutex
|
|
||||||
mrfOperations map[healSource]int
|
|
||||||
lastConnectDisksOpTime time.Time
|
lastConnectDisksOpTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,20 +266,11 @@ func (s *erasureSets) connectDisks() {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
idler := time.NewTimer(100 * time.Millisecond)
|
|
||||||
defer idler.Stop()
|
|
||||||
|
|
||||||
for setIndex, justConnected := range setsJustConnected {
|
for setIndex, justConnected := range setsJustConnected {
|
||||||
if !justConnected {
|
if !justConnected {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
globalMRFState.newSetReconnected(setIndex, s.poolIndex)
|
||||||
// Send a new set connect event with a timeout
|
|
||||||
idler.Reset(100 * time.Millisecond)
|
|
||||||
select {
|
|
||||||
case s.setReconnectEvent <- setIndex:
|
|
||||||
case <-idler.C:
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -375,7 +364,6 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
setReconnectEvent: make(chan int),
|
setReconnectEvent: make(chan int),
|
||||||
distributionAlgo: format.Erasure.DistributionAlgo,
|
distributionAlgo: format.Erasure.DistributionAlgo,
|
||||||
deploymentID: uuid.MustParse(format.ID),
|
deploymentID: uuid.MustParse(format.ID),
|
||||||
mrfOperations: make(map[healSource]int),
|
|
||||||
poolIndex: poolIdx,
|
poolIndex: poolIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,7 +434,6 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
nsMutex: mutex,
|
nsMutex: mutex,
|
||||||
bp: bp,
|
bp: bp,
|
||||||
bpOld: bpOld,
|
bpOld: bpOld,
|
||||||
mrfOpCh: make(chan partialOperation, 10000),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -466,8 +453,6 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
|||||||
|
|
||||||
// Start the disk monitoring and connect routine.
|
// Start the disk monitoring and connect routine.
|
||||||
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
|
go s.monitorAndConnectEndpoints(ctx, defaultMonitorConnectEndpointInterval)
|
||||||
go s.maintainMRFList()
|
|
||||||
go s.healMRFRoutine()
|
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
@@ -1388,71 +1373,6 @@ func (s *erasureSets) GetObjectTags(ctx context.Context, bucket, object string,
|
|||||||
return er.GetObjectTags(ctx, bucket, object, opts)
|
return er.GetObjectTags(ctx, bucket, object, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maintainMRFList gathers the list of successful partial uploads
|
|
||||||
// from all underlying er.sets and puts them in a global map which
|
|
||||||
// should not have more than 10000 entries.
|
|
||||||
func (s *erasureSets) maintainMRFList() {
|
|
||||||
var agg = make(chan partialOperation, 10000)
|
|
||||||
for i, er := range s.sets {
|
|
||||||
go func(c <-chan partialOperation, setIndex int) {
|
|
||||||
for msg := range c {
|
|
||||||
msg.failedSet = setIndex
|
|
||||||
select {
|
|
||||||
case agg <- msg:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(er.mrfOpCh, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
for fOp := range agg {
|
|
||||||
s.mrfMU.Lock()
|
|
||||||
if len(s.mrfOperations) > 10000 {
|
|
||||||
s.mrfMU.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s.mrfOperations[healSource{
|
|
||||||
bucket: fOp.bucket,
|
|
||||||
object: fOp.object,
|
|
||||||
versionID: fOp.versionID,
|
|
||||||
opts: &madmin.HealOpts{Remove: true},
|
|
||||||
}] = fOp.failedSet
|
|
||||||
s.mrfMU.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// healMRFRoutine monitors new disks connection, sweep the MRF list
|
|
||||||
// to find objects related to the new disk that needs to be healed.
|
|
||||||
func (s *erasureSets) healMRFRoutine() {
|
|
||||||
// Wait until background heal state is initialized
|
|
||||||
bgSeq := mustGetHealSequence(GlobalContext)
|
|
||||||
|
|
||||||
for setIndex := range s.setReconnectEvent {
|
|
||||||
// Get the list of objects related the er.set
|
|
||||||
// to which the connected disk belongs.
|
|
||||||
var mrfOperations []healSource
|
|
||||||
s.mrfMU.Lock()
|
|
||||||
for k, v := range s.mrfOperations {
|
|
||||||
if v == setIndex {
|
|
||||||
mrfOperations = append(mrfOperations, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.mrfMU.Unlock()
|
|
||||||
|
|
||||||
// Heal objects
|
|
||||||
for _, u := range mrfOperations {
|
|
||||||
waitForLowHTTPReq(globalHealConfig.IOCount, globalHealConfig.Sleep)
|
|
||||||
|
|
||||||
// Send an object to background heal
|
|
||||||
bgSeq.sourceCh <- u
|
|
||||||
|
|
||||||
s.mrfMU.Lock()
|
|
||||||
delete(s.mrfOperations, u)
|
|
||||||
s.mrfMU.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransitionObject - transition object content to target tier.
|
// TransitionObject - transition object content to target tier.
|
||||||
func (s *erasureSets) TransitionObject(ctx context.Context, bucket, object string, opts ObjectOptions) error {
|
func (s *erasureSets) TransitionObject(ctx context.Context, bucket, object string, opts ObjectOptions) error {
|
||||||
return s.getHashedSet(object).TransitionObject(ctx, bucket, object, opts)
|
return s.getHashedSet(object).TransitionObject(ctx, bucket, object, opts)
|
||||||
|
|||||||
@@ -39,15 +39,6 @@ import (
|
|||||||
// OfflineDisk represents an unavailable disk.
|
// OfflineDisk represents an unavailable disk.
|
||||||
var OfflineDisk StorageAPI // zero value is nil
|
var OfflineDisk StorageAPI // zero value is nil
|
||||||
|
|
||||||
// partialOperation is a successful upload/delete of an object
|
|
||||||
// but not written in all disks (having quorum)
|
|
||||||
type partialOperation struct {
|
|
||||||
bucket string
|
|
||||||
object string
|
|
||||||
versionID string
|
|
||||||
failedSet int
|
|
||||||
}
|
|
||||||
|
|
||||||
// erasureObjects - Implements ER object layer.
|
// erasureObjects - Implements ER object layer.
|
||||||
type erasureObjects struct {
|
type erasureObjects struct {
|
||||||
GatewayUnsupported
|
GatewayUnsupported
|
||||||
@@ -78,8 +69,6 @@ type erasureObjects struct {
|
|||||||
// legacy objects.
|
// legacy objects.
|
||||||
bpOld *bpool.BytePoolCap
|
bpOld *bpool.BytePoolCap
|
||||||
|
|
||||||
mrfOpCh chan partialOperation
|
|
||||||
|
|
||||||
deletedCleanupSleeper *dynamicSleeper
|
deletedCleanupSleeper *dynamicSleeper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import (
|
|||||||
pathutil "path"
|
pathutil "path"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/lock"
|
"github.com/minio/minio/internal/lock"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -168,6 +169,25 @@ func fsStat(ctx context.Context, statLoc string) (os.FileInfo, error) {
|
|||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fsTouch updates a file access & modtime with current time
|
||||||
|
func fsTouch(ctx context.Context, statLoc string) error {
|
||||||
|
if statLoc == "" {
|
||||||
|
logger.LogIf(ctx, errInvalidArgument)
|
||||||
|
return errInvalidArgument
|
||||||
|
}
|
||||||
|
if err := checkPathLength(statLoc); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
if err := os.Chtimes(statLoc, now, now); err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Lookup if volume exists, returns volume attributes upon success.
|
// Lookup if volume exists, returns volume attributes upon success.
|
||||||
func fsStatVolume(ctx context.Context, volume string) (os.FileInfo, error) {
|
func fsStatVolume(ctx context.Context, volume string) (os.FileInfo, error) {
|
||||||
fi, err := fsStat(ctx, volume)
|
fi, err := fsStat(ctx, volume)
|
||||||
|
|||||||
+16
-1
@@ -29,6 +29,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/minio/minio/internal/crypto"
|
||||||
|
"github.com/minio/minio/internal/etag"
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
"github.com/minio/minio/internal/lock"
|
"github.com/minio/minio/internal/lock"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -167,6 +169,12 @@ func (m fsMetaV1) ToObjectInfo(bucket, object string, fi os.FileInfo) ObjectInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
objInfo.ETag = extractETag(m.Meta)
|
objInfo.ETag = extractETag(m.Meta)
|
||||||
|
|
||||||
|
et, e := etag.Parse(objInfo.ETag)
|
||||||
|
if e == nil {
|
||||||
|
objInfo.Multipart = et.IsMultipart()
|
||||||
|
}
|
||||||
|
|
||||||
objInfo.ContentType = m.Meta["content-type"]
|
objInfo.ContentType = m.Meta["content-type"]
|
||||||
objInfo.ContentEncoding = m.Meta["content-encoding"]
|
objInfo.ContentEncoding = m.Meta["content-encoding"]
|
||||||
if storageClass, ok := m.Meta[xhttp.AmzStorageClass]; ok {
|
if storageClass, ok := m.Meta[xhttp.AmzStorageClass]; ok {
|
||||||
@@ -176,7 +184,6 @@ func (m fsMetaV1) ToObjectInfo(bucket, object string, fi os.FileInfo) ObjectInfo
|
|||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
t time.Time
|
t time.Time
|
||||||
e error
|
|
||||||
)
|
)
|
||||||
if exp, ok := m.Meta["expires"]; ok {
|
if exp, ok := m.Meta["expires"]; ok {
|
||||||
if t, e = time.Parse(http.TimeFormat, exp); e == nil {
|
if t, e = time.Parse(http.TimeFormat, exp); e == nil {
|
||||||
@@ -193,6 +200,14 @@ func (m fsMetaV1) ToObjectInfo(bucket, object string, fi os.FileInfo) ObjectInfo
|
|||||||
// Tags have also been extracted, we remove that as well.
|
// Tags have also been extracted, we remove that as well.
|
||||||
objInfo.UserDefined = cleanMetadata(m.Meta)
|
objInfo.UserDefined = cleanMetadata(m.Meta)
|
||||||
|
|
||||||
|
// Set multipart for encryption properly if
|
||||||
|
// not set already.
|
||||||
|
if !objInfo.Multipart {
|
||||||
|
if _, ok := crypto.IsEncrypted(objInfo.UserDefined); ok {
|
||||||
|
objInfo.Multipart = crypto.IsMultiPart(objInfo.UserDefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// All the parts per object.
|
// All the parts per object.
|
||||||
objInfo.Parts = m.Parts
|
objInfo.Parts = m.Parts
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -682,8 +682,15 @@ func (fs *FSObjects) CopyObject(ctx context.Context, srcBucket, srcObject, dstBu
|
|||||||
return oi, toObjectErr(err, srcBucket, srcObject)
|
return oi, toObjectErr(err, srcBucket, srcObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat the file to get file size.
|
fsObjectPath := pathJoin(fs.fsPath, srcBucket, srcObject)
|
||||||
fi, err := fsStatFile(ctx, pathJoin(fs.fsPath, srcBucket, srcObject))
|
|
||||||
|
// Update object modtime
|
||||||
|
err = fsTouch(ctx, fsObjectPath)
|
||||||
|
if err != nil {
|
||||||
|
return oi, toObjectErr(err, srcBucket, srcObject)
|
||||||
|
}
|
||||||
|
// Stat the file to get object info
|
||||||
|
fi, err := fsStatFile(ctx, fsObjectPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return oi, toObjectErr(err, srcBucket, srcObject)
|
return oi, toObjectErr(err, srcBucket, srcObject)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -209,14 +209,6 @@ func StartGateway(ctx *cli.Context, gw Gateway) {
|
|||||||
// To avoid this error situation we check for port availability.
|
// To avoid this error situation we check for port availability.
|
||||||
logger.FatalIf(checkPortAvailability(globalMinioHost, globalMinioPort), "Unable to start the gateway")
|
logger.FatalIf(checkPortAvailability(globalMinioHost, globalMinioPort), "Unable to start the gateway")
|
||||||
|
|
||||||
globalMinioEndpoint = func() string {
|
|
||||||
host := globalMinioHost
|
|
||||||
if host == "" {
|
|
||||||
host = sortIPs(localIP4.ToSlice())[0]
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Handle gateway specific env
|
// Handle gateway specific env
|
||||||
gatewayHandleEnvVars()
|
gatewayHandleEnvVars()
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -154,15 +154,15 @@ func setRedirectHandler(h http.Handler) http.Handler {
|
|||||||
|
|
||||||
func guessIsBrowserReq(r *http.Request) bool {
|
func guessIsBrowserReq(r *http.Request) bool {
|
||||||
aType := getRequestAuthType(r)
|
aType := getRequestAuthType(r)
|
||||||
ok := strings.Contains(r.Header.Get("User-Agent"), "Mozilla") && globalBrowserEnabled &&
|
return strings.Contains(r.Header.Get("User-Agent"), "Mozilla") &&
|
||||||
(aType == authTypeJWT || aType == authTypeAnonymous)
|
globalBrowserEnabled && aType == authTypeAnonymous
|
||||||
return ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBrowserRedirectHandler(h http.Handler) http.Handler {
|
func setBrowserRedirectHandler(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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.
|
// Re-direction is handled specifically for browser requests.
|
||||||
if guessIsBrowserReq(r) {
|
if guessIsBrowserReq(r) && read {
|
||||||
// Fetch the redirect location if any.
|
// Fetch the redirect location if any.
|
||||||
if u := getRedirectLocation(r); u != nil {
|
if u := getRedirectLocation(r); u != nil {
|
||||||
// Employ a temporary re-direct.
|
// Employ a temporary re-direct.
|
||||||
@@ -553,11 +553,15 @@ func addCustomHeaders(h http.Handler) http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addSecurityHeaders adds various HTTP(S) response headers.
|
||||||
|
// Security Headers enable various security protections behaviors in the client's browser.
|
||||||
func addSecurityHeaders(h http.Handler) http.Handler {
|
func addSecurityHeaders(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
header := w.Header()
|
header := w.Header()
|
||||||
header.Set("X-XSS-Protection", "1; mode=block") // Prevents against XSS attacks
|
header.Set("X-XSS-Protection", "1; mode=block") // Prevents against XSS attacks
|
||||||
header.Set("Content-Security-Policy", "block-all-mixed-content") // prevent mixed (HTTP / HTTPS content)
|
header.Set("Content-Security-Policy", "block-all-mixed-content") // prevent mixed (HTTP / HTTPS content)
|
||||||
|
header.Set("X-Content-Type-Options", "nosniff") // Prevent mime-sniff
|
||||||
|
header.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains") // HSTS mitigates variants of MITM attacks
|
||||||
h.ServeHTTP(w, r)
|
h.ServeHTTP(w, r)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-5
@@ -79,13 +79,20 @@ func getBackgroundHealStatus(ctx context.Context, o ObjectLayer) (madmin.BgHealS
|
|||||||
return madmin.BgHealState{}, false
|
return madmin.BgHealState{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
status := madmin.BgHealState{
|
||||||
|
ScannedItemsCount: bgSeq.getScannedItemsCount(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if globalMRFState.initialized() {
|
||||||
|
status.MRF = map[string]madmin.MRFStatus{
|
||||||
|
globalLocalNodeName: globalMRFState.getCurrentMRFRoundInfo(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var healDisksMap = map[string]struct{}{}
|
var healDisksMap = map[string]struct{}{}
|
||||||
for _, ep := range getLocalDisksToHeal() {
|
for _, ep := range getLocalDisksToHeal() {
|
||||||
healDisksMap[ep.String()] = struct{}{}
|
healDisksMap[ep.String()] = struct{}{}
|
||||||
}
|
}
|
||||||
status := madmin.BgHealState{
|
|
||||||
ScannedItemsCount: bgSeq.getScannedItemsCount(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if o == nil {
|
if o == nil {
|
||||||
healing := globalBackgroundHealState.getLocalHealingDisks()
|
healing := globalBackgroundHealState.getLocalHealingDisks()
|
||||||
@@ -225,12 +232,12 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []BucketIn
|
|||||||
ScanMode: madmin.HealNormalScan, Remove: healDeleteDangling}); err != nil {
|
ScanMode: madmin.HealNormalScan, Remove: healDeleteDangling}); err != nil {
|
||||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
// If not deleted, assume they failed.
|
// If not deleted, assume they failed.
|
||||||
tracker.ObjectsFailed++
|
tracker.ItemsFailed++
|
||||||
tracker.BytesFailed += uint64(version.Size)
|
tracker.BytesFailed += uint64(version.Size)
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracker.ObjectsHealed++
|
tracker.ItemsHealed++
|
||||||
tracker.BytesDone += uint64(version.Size)
|
tracker.BytesDone += uint64(version.Size)
|
||||||
}
|
}
|
||||||
bgSeq.logHeal(madmin.HealItemObject)
|
bgSeq.logHeal(madmin.HealItemObject)
|
||||||
@@ -265,6 +272,11 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []BucketIn
|
|||||||
finished: nil,
|
finished: nil,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
// If context is canceled don't mark as done...
|
// If context is canceled don't mark as done...
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
|||||||
@@ -293,6 +293,8 @@ var (
|
|||||||
globalBackgroundHealRoutine *healRoutine
|
globalBackgroundHealRoutine *healRoutine
|
||||||
globalBackgroundHealState *allHealState
|
globalBackgroundHealState *allHealState
|
||||||
|
|
||||||
|
globalMRFState mrfState
|
||||||
|
|
||||||
// If writes to FS backend should be O_SYNC.
|
// If writes to FS backend should be O_SYNC.
|
||||||
globalFSOSync bool
|
globalFSOSync bool
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -106,7 +106,7 @@ func getDrivePerfInfos(ctx context.Context, addr string) madmin.DrivePerfInfos {
|
|||||||
serialPerf := getDrivePerfInfo(ctx, false)
|
serialPerf := getDrivePerfInfo(ctx, false)
|
||||||
parallelPerf := getDrivePerfInfo(ctx, true)
|
parallelPerf := getDrivePerfInfo(ctx, true)
|
||||||
return madmin.DrivePerfInfos{
|
return madmin.DrivePerfInfos{
|
||||||
Addr: addr,
|
NodeCommon: madmin.NodeCommon{Addr: addr},
|
||||||
SerialPerf: serialPerf,
|
SerialPerf: serialPerf,
|
||||||
ParallelPerf: parallelPerf,
|
ParallelPerf: parallelPerf,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
"github.com/minio/minio/internal/handlers"
|
"github.com/minio/minio/internal/handlers"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
jsonrpc "github.com/minio/rpc"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// recordRequest - records the first recLen bytes
|
// recordRequest - records the first recLen bytes
|
||||||
@@ -110,75 +108,6 @@ func getOpName(name string) (op string) {
|
|||||||
return op
|
return op
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebTrace gets trace of web request
|
|
||||||
func WebTrace(ri *jsonrpc.RequestInfo) madmin.TraceInfo {
|
|
||||||
r := ri.Request
|
|
||||||
w := ri.ResponseWriter
|
|
||||||
|
|
||||||
name := ri.Method
|
|
||||||
// Setup a http request body recorder
|
|
||||||
reqHeaders := r.Header.Clone()
|
|
||||||
reqHeaders.Set("Host", r.Host)
|
|
||||||
if len(r.TransferEncoding) == 0 {
|
|
||||||
reqHeaders.Set("Content-Length", strconv.Itoa(int(r.ContentLength)))
|
|
||||||
} else {
|
|
||||||
reqHeaders.Set("Transfer-Encoding", strings.Join(r.TransferEncoding, ","))
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now().UTC()
|
|
||||||
t := madmin.TraceInfo{TraceType: madmin.TraceHTTP, FuncName: name, Time: now}
|
|
||||||
t.NodeName = r.Host
|
|
||||||
if globalIsDistErasure {
|
|
||||||
t.NodeName = globalLocalNodeName
|
|
||||||
}
|
|
||||||
if t.NodeName == "" {
|
|
||||||
t.NodeName = globalLocalNodeName
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip only standard port from the host address
|
|
||||||
if host, port, err := net.SplitHostPort(t.NodeName); err == nil {
|
|
||||||
if port == "443" || port == "80" {
|
|
||||||
t.NodeName = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
rq := madmin.TraceRequestInfo{
|
|
||||||
Time: now,
|
|
||||||
Proto: r.Proto,
|
|
||||||
Method: r.Method,
|
|
||||||
Path: SlashSeparator + pathJoin(vars["bucket"], vars["object"]),
|
|
||||||
RawQuery: redactLDAPPwd(r.URL.RawQuery),
|
|
||||||
Client: handlers.GetSourceIP(r),
|
|
||||||
Headers: reqHeaders,
|
|
||||||
}
|
|
||||||
|
|
||||||
rw, ok := w.(*logger.ResponseWriter)
|
|
||||||
if ok {
|
|
||||||
rs := madmin.TraceResponseInfo{
|
|
||||||
Time: time.Now().UTC(),
|
|
||||||
Headers: rw.Header().Clone(),
|
|
||||||
StatusCode: rw.StatusCode,
|
|
||||||
Body: logger.BodyPlaceHolder,
|
|
||||||
}
|
|
||||||
|
|
||||||
if rs.StatusCode == 0 {
|
|
||||||
rs.StatusCode = http.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
t.RespInfo = rs
|
|
||||||
t.CallStats = madmin.TraceCallStats{
|
|
||||||
Latency: rs.Time.Sub(rw.StartTime),
|
|
||||||
InputBytes: int(r.ContentLength),
|
|
||||||
OutputBytes: rw.Size(),
|
|
||||||
TimeToFirstByte: rw.TimeToFirstByte,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.ReqInfo = rq
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace gets trace of http request
|
// Trace gets trace of http request
|
||||||
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) madmin.TraceInfo {
|
func Trace(f http.HandlerFunc, logBody bool, w http.ResponseWriter, r *http.Request) madmin.TraceInfo {
|
||||||
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
|
name := getOpName(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name())
|
||||||
|
|||||||
+211
-49
@@ -650,14 +650,23 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if globalOpenIDConfig.ProviderEnabled() {
|
// Set up polling for expired accounts and credentials purging.
|
||||||
|
switch {
|
||||||
|
case globalOpenIDConfig.ProviderEnabled():
|
||||||
go func() {
|
go func() {
|
||||||
// Purge expired credentials
|
|
||||||
for {
|
for {
|
||||||
time.Sleep(globalRefreshIAMInterval)
|
time.Sleep(globalRefreshIAMInterval)
|
||||||
sys.purgeExpiredCredentialsForExternalSSO(ctx)
|
sys.purgeExpiredCredentialsForExternalSSO(ctx)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
case globalLDAPConfig.EnabledWithLookupBind():
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(globalRefreshIAMInterval)
|
||||||
|
sys.purgeExpiredCredentialsForLDAP(ctx)
|
||||||
|
sys.updateGroupMembershipsForLDAP(ctx)
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
go sys.store.watch(ctx, sys)
|
go sys.store.watch(ctx, sys)
|
||||||
@@ -1157,6 +1166,9 @@ type newServiceAccountOpts struct {
|
|||||||
sessionPolicy *iampolicy.Policy
|
sessionPolicy *iampolicy.Policy
|
||||||
accessKey string
|
accessKey string
|
||||||
secretKey string
|
secretKey string
|
||||||
|
|
||||||
|
// LDAP username
|
||||||
|
ldapUsername string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServiceAccount - create a new service account
|
// NewServiceAccount - create a new service account
|
||||||
@@ -1214,6 +1226,11 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
|
|||||||
m[iamPolicyClaimNameSA()] = "inherited-policy"
|
m[iamPolicyClaimNameSA()] = "inherited-policy"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For LDAP service account, save the ldap username in the metadata.
|
||||||
|
if opts.ldapUsername != "" {
|
||||||
|
m[ldapUserN] = opts.ldapUsername
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
cred auth.Credentials
|
cred auth.Credentials
|
||||||
)
|
)
|
||||||
@@ -1390,11 +1407,7 @@ func (sys *IAMSys) DeleteServiceAccount(ctx context.Context, accessKey string) e
|
|||||||
|
|
||||||
// It is ok to ignore deletion error on the mapped policy
|
// It is ok to ignore deletion error on the mapped policy
|
||||||
err := sys.store.deleteUserIdentity(context.Background(), accessKey, svcUser)
|
err := sys.store.deleteUserIdentity(context.Background(), accessKey, svcUser)
|
||||||
if err != nil {
|
if err != nil && err != errNoSuchUser {
|
||||||
// ignore if user is already deleted.
|
|
||||||
if err == errNoSuchUser {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1531,20 +1544,20 @@ func (sys *IAMSys) loadUserFromStore(accessKey string) {
|
|||||||
// by checking remote IDP if the relevant users are still active and present.
|
// by checking remote IDP if the relevant users are still active and present.
|
||||||
func (sys *IAMSys) purgeExpiredCredentialsForExternalSSO(ctx context.Context) {
|
func (sys *IAMSys) purgeExpiredCredentialsForExternalSSO(ctx context.Context) {
|
||||||
sys.store.lock()
|
sys.store.lock()
|
||||||
parentUsersMap := make(map[string]auth.Credentials, len(sys.iamUsersMap))
|
parentUsersMap := make(map[string][]auth.Credentials, len(sys.iamUsersMap))
|
||||||
for _, cred := range sys.iamUsersMap {
|
for _, cred := range sys.iamUsersMap {
|
||||||
if cred.IsServiceAccount() || cred.IsTemp() {
|
if cred.IsServiceAccount() || cred.IsTemp() {
|
||||||
userid, err := parseOpenIDParentUser(cred.ParentUser)
|
userid, err := parseOpenIDParentUser(cred.ParentUser)
|
||||||
if err == errSkipFile {
|
if err == errSkipFile {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
parentUsersMap[userid] = cred
|
parentUsersMap[userid] = append(parentUsersMap[userid], cred)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sys.store.unlock()
|
sys.store.unlock()
|
||||||
|
|
||||||
expiredUsers := make([]auth.Credentials, 0, len(parentUsersMap))
|
expiredUsers := make([]auth.Credentials, 0, len(parentUsersMap))
|
||||||
for userid, cred := range parentUsersMap {
|
for userid, creds := range parentUsersMap {
|
||||||
u, err := globalOpenIDConfig.LookupUser(userid)
|
u, err := globalOpenIDConfig.LookupUser(userid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.LogIf(GlobalContext, err)
|
logger.LogIf(GlobalContext, err)
|
||||||
@@ -1552,7 +1565,7 @@ func (sys *IAMSys) purgeExpiredCredentialsForExternalSSO(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
// Disabled parentUser purge the entries locally
|
// Disabled parentUser purge the entries locally
|
||||||
if !u.Enabled {
|
if !u.Enabled {
|
||||||
expiredUsers = append(expiredUsers, cred)
|
expiredUsers = append(expiredUsers, creds...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1575,6 +1588,140 @@ func (sys *IAMSys) purgeExpiredCredentialsForExternalSSO(ctx context.Context) {
|
|||||||
sys.store.unlock()
|
sys.store.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// purgeExpiredCredentialsForLDAP - validates if local credentials are still
|
||||||
|
// valid by checking LDAP server if the relevant users are still present.
|
||||||
|
func (sys *IAMSys) purgeExpiredCredentialsForLDAP(ctx context.Context) {
|
||||||
|
sys.store.lock()
|
||||||
|
parentUsersMap := make(map[string][]auth.Credentials, len(sys.iamUsersMap))
|
||||||
|
parentUsers := make([]string, 0, len(sys.iamUsersMap))
|
||||||
|
for _, cred := range sys.iamUsersMap {
|
||||||
|
if cred.IsServiceAccount() || cred.IsTemp() {
|
||||||
|
if globalLDAPConfig.IsLDAPUserDN(cred.ParentUser) {
|
||||||
|
if _, ok := parentUsersMap[cred.ParentUser]; !ok {
|
||||||
|
parentUsers = append(parentUsers, cred.ParentUser)
|
||||||
|
}
|
||||||
|
parentUsersMap[cred.ParentUser] = append(parentUsersMap[cred.ParentUser], cred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys.store.unlock()
|
||||||
|
|
||||||
|
expiredUsers, err := globalLDAPConfig.GetNonExistentUserDistNames(parentUsers)
|
||||||
|
if err != nil {
|
||||||
|
// Log and return on error - perhaps it'll work the next time.
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, expiredUser := range expiredUsers {
|
||||||
|
for _, cred := range parentUsersMap[expiredUser] {
|
||||||
|
userType := regUser
|
||||||
|
if cred.IsServiceAccount() {
|
||||||
|
userType = svcUser
|
||||||
|
} else if cred.IsTemp() {
|
||||||
|
userType = stsUser
|
||||||
|
}
|
||||||
|
sys.store.deleteIAMConfig(ctx, getUserIdentityPath(cred.AccessKey, userType))
|
||||||
|
sys.store.deleteIAMConfig(ctx, getMappedPolicyPath(cred.AccessKey, userType, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sys.store.lock()
|
||||||
|
for _, user := range expiredUsers {
|
||||||
|
for _, cred := range parentUsersMap[user] {
|
||||||
|
delete(sys.iamUsersMap, cred.AccessKey)
|
||||||
|
delete(sys.iamUserPolicyMap, cred.AccessKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys.store.unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateGroupMembershipsForLDAP - updates the list of groups associated with the credential.
|
||||||
|
func (sys *IAMSys) updateGroupMembershipsForLDAP(ctx context.Context) {
|
||||||
|
// 1. Collect all LDAP users with active creds.
|
||||||
|
sys.store.lock()
|
||||||
|
// List of unique LDAP (parent) user DNs that have active creds
|
||||||
|
parentUsers := make([]string, 0, len(sys.iamUsersMap))
|
||||||
|
// Map of LDAP user to list of active credential objects
|
||||||
|
parentUserToCredsMap := make(map[string][]auth.Credentials, len(sys.iamUsersMap))
|
||||||
|
// DN to ldap username mapping for each LDAP user
|
||||||
|
parentUserToLDAPUsernameMap := make(map[string]string, len(sys.iamUsersMap))
|
||||||
|
for _, cred := range sys.iamUsersMap {
|
||||||
|
if cred.IsServiceAccount() || cred.IsTemp() {
|
||||||
|
if globalLDAPConfig.IsLDAPUserDN(cred.ParentUser) {
|
||||||
|
// Check if this is the first time we are
|
||||||
|
// encountering this LDAP user.
|
||||||
|
if _, ok := parentUserToCredsMap[cred.ParentUser]; !ok {
|
||||||
|
// Try to find the ldapUsername for this
|
||||||
|
// parentUser by extracting JWT claims
|
||||||
|
jwtClaims, err := auth.ExtractClaims(cred.SessionToken, globalActiveCred.SecretKey)
|
||||||
|
if err != nil {
|
||||||
|
// skip this cred - session token seems
|
||||||
|
// invalid
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ldapUsername, ok := jwtClaims.Lookup(ldapUserN)
|
||||||
|
if !ok {
|
||||||
|
// skip this cred - we dont have the
|
||||||
|
// username info needed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect each new cred.ParentUser into parentUsers
|
||||||
|
parentUsers = append(parentUsers, cred.ParentUser)
|
||||||
|
|
||||||
|
// Update the ldapUsernameMap
|
||||||
|
parentUserToLDAPUsernameMap[cred.ParentUser] = ldapUsername
|
||||||
|
}
|
||||||
|
parentUserToCredsMap[cred.ParentUser] = append(parentUserToCredsMap[cred.ParentUser], cred)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sys.store.unlock()
|
||||||
|
|
||||||
|
// 2. Query LDAP server for groups of the LDAP users collected.
|
||||||
|
updatedGroups, err := globalLDAPConfig.LookupGroupMemberships(parentUsers, parentUserToLDAPUsernameMap)
|
||||||
|
if err != nil {
|
||||||
|
// Log and return on error - perhaps it'll work the next time.
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Update creds for those users whose groups are changed
|
||||||
|
sys.store.lock()
|
||||||
|
defer sys.store.unlock()
|
||||||
|
for _, parentUser := range parentUsers {
|
||||||
|
currGroupsSet := updatedGroups[parentUser]
|
||||||
|
currGroups := currGroupsSet.ToSlice()
|
||||||
|
for _, cred := range parentUserToCredsMap[parentUser] {
|
||||||
|
gSet := set.CreateStringSet(cred.Groups...)
|
||||||
|
if gSet.Equals(currGroupsSet) {
|
||||||
|
// No change to groups memberships for this
|
||||||
|
// credential.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cred.Groups = currGroups
|
||||||
|
userType := regUser
|
||||||
|
if cred.IsServiceAccount() {
|
||||||
|
userType = svcUser
|
||||||
|
} else if cred.IsTemp() {
|
||||||
|
userType = stsUser
|
||||||
|
}
|
||||||
|
// Overwrite the user identity here. As store should be
|
||||||
|
// atomic, it shouldn't cause any corruption.
|
||||||
|
if err := sys.store.saveUserIdentity(ctx, cred.AccessKey, userType, newUserIdentity(cred)); err != nil {
|
||||||
|
// Log and continue error - perhaps it'll work the next time.
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If we wrote the updated creds to IAM storage, we can
|
||||||
|
// update the in memory map.
|
||||||
|
sys.iamUsersMap[cred.AccessKey] = cred
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetUser - get user credentials
|
// GetUser - get user credentials
|
||||||
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
func (sys *IAMSys) GetUser(accessKey string) (cred auth.Credentials, ok bool) {
|
||||||
if !sys.Initialized() {
|
if !sys.Initialized() {
|
||||||
@@ -2153,20 +2300,15 @@ func (sys *IAMSys) IsAllowedServiceAccount(args iampolicy.Args, parentUser strin
|
|||||||
|
|
||||||
// IsAllowedLDAPSTS - checks for LDAP specific claims and values
|
// IsAllowedLDAPSTS - checks for LDAP specific claims and values
|
||||||
func (sys *IAMSys) IsAllowedLDAPSTS(args iampolicy.Args, parentUser string) bool {
|
func (sys *IAMSys) IsAllowedLDAPSTS(args iampolicy.Args, parentUser string) bool {
|
||||||
parentInClaimIface, ok := args.Claims[ldapUser]
|
// parentUser value must match the ldap user in the claim.
|
||||||
if ok {
|
if parentInClaimIface, ok := args.Claims[ldapUser]; !ok {
|
||||||
parentInClaim, ok := parentInClaimIface.(string)
|
// no ldapUser claim present reject it.
|
||||||
if !ok {
|
return false
|
||||||
// ldap parentInClaim name is not a string reject it.
|
} else if parentInClaim, ok := parentInClaimIface.(string); !ok {
|
||||||
return false
|
// not the right type, reject it.
|
||||||
}
|
return false
|
||||||
|
} else if parentInClaim != parentUser {
|
||||||
if parentInClaim != parentUser {
|
// ldap claim has been modified maliciously reject it.
|
||||||
// ldap claim has been modified maliciously reject it.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// no ldap parentInClaim claim present reject it.
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2203,6 +2345,11 @@ func (sys *IAMSys) IsAllowedLDAPSTS(args iampolicy.Args, parentUser string) bool
|
|||||||
availablePolicies[i].Statements...)
|
availablePolicies[i].Statements...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasSessionPolicy, isAllowedSP := isAllowedBySessionPolicy(args)
|
||||||
|
if hasSessionPolicy {
|
||||||
|
return isAllowedSP && combinedPolicy.IsAllowed(args)
|
||||||
|
}
|
||||||
|
|
||||||
return combinedPolicy.IsAllowed(args)
|
return combinedPolicy.IsAllowed(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2267,30 +2414,9 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
|
|||||||
args.ConditionValues["userid"] = []string{parentUser}
|
args.ConditionValues["userid"] = []string{parentUser}
|
||||||
|
|
||||||
// Now check if we have a sessionPolicy.
|
// Now check if we have a sessionPolicy.
|
||||||
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
|
hasSessionPolicy, isAllowedSP := isAllowedBySessionPolicy(args)
|
||||||
if ok {
|
if hasSessionPolicy {
|
||||||
spolicyStr, ok := spolicy.(string)
|
return isAllowedSP && combinedPolicy.IsAllowed(args)
|
||||||
if !ok {
|
|
||||||
// Sub policy if set, should be a string reject
|
|
||||||
// malformed/malicious requests.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if policy is parseable.
|
|
||||||
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
|
||||||
if err != nil {
|
|
||||||
// Log any error in input session policy config.
|
|
||||||
logger.LogIf(GlobalContext, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Policy without Version string value reject it.
|
|
||||||
if subPolicy.Version == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub policy is set and valid.
|
|
||||||
return combinedPolicy.IsAllowed(args) && subPolicy.IsAllowed(args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub policy not set, this is most common since subPolicy
|
// Sub policy not set, this is most common since subPolicy
|
||||||
@@ -2298,6 +2424,42 @@ func (sys *IAMSys) IsAllowedSTS(args iampolicy.Args, parentUser string) bool {
|
|||||||
return combinedPolicy.IsAllowed(args)
|
return combinedPolicy.IsAllowed(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isAllowedBySessionPolicy(args iampolicy.Args) (hasSessionPolicy bool, isAllowed bool) {
|
||||||
|
hasSessionPolicy = false
|
||||||
|
isAllowed = false
|
||||||
|
|
||||||
|
// Now check if we have a sessionPolicy.
|
||||||
|
spolicy, ok := args.Claims[iampolicy.SessionPolicyName]
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSessionPolicy = true
|
||||||
|
|
||||||
|
spolicyStr, ok := spolicy.(string)
|
||||||
|
if !ok {
|
||||||
|
// Sub policy if set, should be a string reject
|
||||||
|
// malformed/malicious requests.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if policy is parseable.
|
||||||
|
subPolicy, err := iampolicy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||||
|
if err != nil {
|
||||||
|
// Log any error in input session policy config.
|
||||||
|
logger.LogIf(GlobalContext, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Policy without Version string value reject it.
|
||||||
|
if subPolicy.Version == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub policy is set and valid.
|
||||||
|
return hasSessionPolicy, subPolicy.IsAllowed(args)
|
||||||
|
}
|
||||||
|
|
||||||
// GetCombinedPolicy returns a combined policy combining all policies
|
// GetCombinedPolicy returns a combined policy combining all policies
|
||||||
func (sys *IAMSys) GetCombinedPolicy(policies ...string) iampolicy.Policy {
|
func (sys *IAMSys) GetCombinedPolicy(policies ...string) iampolicy.Policy {
|
||||||
// Policies were found, evaluate all of them.
|
// Policies were found, evaluate all of them.
|
||||||
|
|||||||
@@ -600,7 +600,9 @@ func mergeEntryChannels(ctx context.Context, in []chan metaCacheEntry, out chan<
|
|||||||
out <- *best
|
out <- *best
|
||||||
last = best.name
|
last = best.name
|
||||||
} else {
|
} else {
|
||||||
console.Debugln("mergeEntryChannels: discarding duplicate", best.name, "<=", last)
|
if serverDebugLog {
|
||||||
|
console.Debugln("mergeEntryChannels: discarding duplicate", best.name, "<=", last)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Replace entry we just sent.
|
// Replace entry we just sent.
|
||||||
if err := selectFrom(bestIdx); err != nil {
|
if err := selectFrom(bestIdx); err != nil {
|
||||||
|
|||||||
+92
-47
@@ -351,18 +351,18 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
|||||||
// All operations are performed without locks, so we must be careful and allow for failures.
|
// All operations are performed without locks, so we must be careful and allow for failures.
|
||||||
// Read metadata associated with the object from a disk.
|
// Read metadata associated with the object from a disk.
|
||||||
if retries > 0 {
|
if retries > 0 {
|
||||||
disks := er.getOnlineDisks()
|
for _, disk := range er.getDisks() {
|
||||||
if len(disks) == 0 {
|
if disk == nil {
|
||||||
time.Sleep(retryDelay)
|
continue
|
||||||
retries++
|
}
|
||||||
continue
|
_, err := disk.ReadVersion(ctx, minioMetaBucket,
|
||||||
}
|
o.objectPath(0), "", false)
|
||||||
|
if err != nil {
|
||||||
_, err := disks[0].ReadVersion(ctx, minioMetaBucket, o.objectPath(0), "", false)
|
time.Sleep(retryDelay)
|
||||||
if err != nil {
|
retries++
|
||||||
time.Sleep(retryDelay)
|
continue
|
||||||
retries++
|
}
|
||||||
continue
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,20 +421,21 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
|||||||
|
|
||||||
if retries > 0 {
|
if retries > 0 {
|
||||||
// Load from one disk only
|
// Load from one disk only
|
||||||
disks := er.getOnlineDisks()
|
for _, disk := range er.getDisks() {
|
||||||
if len(disks) == 0 {
|
if disk == nil {
|
||||||
time.Sleep(retryDelay)
|
continue
|
||||||
retries++
|
}
|
||||||
continue
|
_, err := disk.ReadVersion(ctx, minioMetaBucket,
|
||||||
}
|
o.objectPath(partN), "", false)
|
||||||
|
if err != nil {
|
||||||
_, err := disks[0].ReadVersion(ctx, minioMetaBucket, o.objectPath(partN), "", false)
|
time.Sleep(retryDelay)
|
||||||
if err != nil {
|
retries++
|
||||||
time.Sleep(retryDelay)
|
continue
|
||||||
retries++
|
}
|
||||||
continue
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load first part metadata...
|
// Load first part metadata...
|
||||||
fi, metaArr, onlineDisks, err = er.getObjectFileInfo(ctx, minioMetaBucket, o.objectPath(partN), ObjectOptions{}, true)
|
fi, metaArr, onlineDisks, err = er.getObjectFileInfo(ctx, minioMetaBucket, o.objectPath(partN), ObjectOptions{}, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -512,7 +513,8 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions, resul
|
|||||||
|
|
||||||
askDisks := o.AskDisks
|
askDisks := o.AskDisks
|
||||||
listingQuorum := o.AskDisks - 1
|
listingQuorum := o.AskDisks - 1
|
||||||
disks := er.getOnlineDisks()
|
disks := er.getDisks()
|
||||||
|
var fallbackDisks []StorageAPI
|
||||||
|
|
||||||
// Special case: ask all disks if the drive count is 4
|
// Special case: ask all disks if the drive count is 4
|
||||||
if askDisks == -1 || er.setDriveCount == 4 {
|
if askDisks == -1 || er.setDriveCount == 4 {
|
||||||
@@ -527,6 +529,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions, resul
|
|||||||
rand.Shuffle(len(disks), func(i, j int) {
|
rand.Shuffle(len(disks), func(i, j int) {
|
||||||
disks[i], disks[j] = disks[j], disks[i]
|
disks[i], disks[j] = disks[j], disks[i]
|
||||||
})
|
})
|
||||||
|
fallbackDisks = disks[askDisks:]
|
||||||
disks = disks[:askDisks]
|
disks = disks[:askDisks]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,13 +542,14 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions, resul
|
|||||||
|
|
||||||
ctxDone := ctx.Done()
|
ctxDone := ctx.Done()
|
||||||
return listPathRaw(ctx, listPathRawOptions{
|
return listPathRaw(ctx, listPathRawOptions{
|
||||||
disks: disks,
|
disks: disks,
|
||||||
bucket: o.Bucket,
|
fallbackDisks: fallbackDisks,
|
||||||
path: o.BaseDir,
|
bucket: o.Bucket,
|
||||||
recursive: o.Recursive,
|
path: o.BaseDir,
|
||||||
filterPrefix: o.FilterPrefix,
|
recursive: o.Recursive,
|
||||||
minDisks: listingQuorum,
|
filterPrefix: o.FilterPrefix,
|
||||||
forwardTo: o.Marker,
|
minDisks: listingQuorum,
|
||||||
|
forwardTo: o.Marker,
|
||||||
agreed: func(entry metaCacheEntry) {
|
agreed: func(entry metaCacheEntry) {
|
||||||
select {
|
select {
|
||||||
case <-ctxDone:
|
case <-ctxDone:
|
||||||
@@ -710,9 +714,10 @@ func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCache
|
|||||||
}
|
}
|
||||||
|
|
||||||
type listPathRawOptions struct {
|
type listPathRawOptions struct {
|
||||||
disks []StorageAPI
|
disks []StorageAPI
|
||||||
bucket, path string
|
fallbackDisks []StorageAPI
|
||||||
recursive bool
|
bucket, path string
|
||||||
|
recursive bool
|
||||||
|
|
||||||
// Only return results with this prefix.
|
// Only return results with this prefix.
|
||||||
filterPrefix string
|
filterPrefix string
|
||||||
@@ -752,10 +757,18 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
|||||||
if len(disks) == 0 {
|
if len(disks) == 0 {
|
||||||
return fmt.Errorf("listPathRaw: 0 drives provided")
|
return fmt.Errorf("listPathRaw: 0 drives provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel upstream if we finish before we expect.
|
// Cancel upstream if we finish before we expect.
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
fallback := func(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return err.Error() == errUnformattedDisk.Error() ||
|
||||||
|
err.Error() == errVolumeNotFound.Error()
|
||||||
|
}
|
||||||
askDisks := len(disks)
|
askDisks := len(disks)
|
||||||
readers := make([]*metacacheReader, askDisks)
|
readers := make([]*metacacheReader, askDisks)
|
||||||
for i := range disks {
|
for i := range disks {
|
||||||
@@ -768,15 +781,44 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
|||||||
|
|
||||||
// Send request to each disk.
|
// Send request to each disk.
|
||||||
go func() {
|
go func() {
|
||||||
werr := d.WalkDir(ctx, WalkDirOptions{
|
var werr error
|
||||||
Bucket: opts.bucket,
|
if d == nil {
|
||||||
BaseDir: opts.path,
|
werr = errDiskNotFound
|
||||||
Recursive: opts.recursive,
|
} else {
|
||||||
ReportNotFound: opts.reportNotFound,
|
werr = d.WalkDir(ctx, WalkDirOptions{
|
||||||
FilterPrefix: opts.filterPrefix,
|
Bucket: opts.bucket,
|
||||||
ForwardTo: opts.forwardTo,
|
BaseDir: opts.path,
|
||||||
}, w)
|
Recursive: opts.recursive,
|
||||||
|
ReportNotFound: opts.reportNotFound,
|
||||||
|
FilterPrefix: opts.filterPrefix,
|
||||||
|
ForwardTo: opts.forwardTo,
|
||||||
|
}, w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback only when set.
|
||||||
|
if len(opts.fallbackDisks) > 0 && fallback(werr) {
|
||||||
|
// This fallback is only set when
|
||||||
|
// askDisks is less than total
|
||||||
|
// number of disks per set.
|
||||||
|
for _, fd := range opts.fallbackDisks {
|
||||||
|
if fd == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
werr = fd.WalkDir(ctx, WalkDirOptions{
|
||||||
|
Bucket: opts.bucket,
|
||||||
|
BaseDir: opts.path,
|
||||||
|
Recursive: opts.recursive,
|
||||||
|
ReportNotFound: opts.reportNotFound,
|
||||||
|
FilterPrefix: opts.filterPrefix,
|
||||||
|
ForwardTo: opts.forwardTo,
|
||||||
|
}, w)
|
||||||
|
if werr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
w.CloseWithError(werr)
|
w.CloseWithError(werr)
|
||||||
|
|
||||||
if werr != io.EOF && werr != nil &&
|
if werr != io.EOF && werr != nil &&
|
||||||
werr.Error() != errFileNotFound.Error() &&
|
werr.Error() != errFileNotFound.Error() &&
|
||||||
werr.Error() != errVolumeNotFound.Error() &&
|
werr.Error() != errVolumeNotFound.Error() &&
|
||||||
@@ -795,10 +837,8 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
|||||||
for i := range topEntries {
|
for i := range topEntries {
|
||||||
topEntries[i] = metaCacheEntry{}
|
topEntries[i] = metaCacheEntry{}
|
||||||
}
|
}
|
||||||
select {
|
if contextCanceled(ctx) {
|
||||||
case <-ctx.Done():
|
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
for i, r := range readers {
|
for i, r := range readers {
|
||||||
if errs[i] != nil {
|
if errs[i] != nil {
|
||||||
@@ -866,7 +906,12 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
|||||||
var combinedErr []string
|
var combinedErr []string
|
||||||
for i, err := range errs {
|
for i, err := range errs {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
combinedErr = append(combinedErr, fmt.Sprintf("disk %d returned: %s", i, err))
|
if disks[i] != nil {
|
||||||
|
combinedErr = append(combinedErr,
|
||||||
|
fmt.Sprintf("disk %s returned: %s", disks[i], err))
|
||||||
|
} else {
|
||||||
|
combinedErr = append(combinedErr, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New(strings.Join(combinedErr, ", "))
|
return errors.New(strings.Join(combinedErr, ", "))
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -66,8 +65,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stat a volume entry.
|
// Stat a volume entry.
|
||||||
_, err = os.Lstat(volumeDir)
|
if err = Access(volumeDir); err != nil {
|
||||||
if err != nil {
|
|
||||||
if osIsNotExist(err) {
|
if osIsNotExist(err) {
|
||||||
return errVolumeNotFound
|
return errVolumeNotFound
|
||||||
} else if isSysErrIO(err) {
|
} else if isSysErrIO(err) {
|
||||||
@@ -100,7 +98,8 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if st, err := Lstat(pathJoin(volumeDir, opts.BaseDir, xlStorageFormatFile)); err == nil && st.Mode().IsRegular() {
|
st, sterr := Lstat(pathJoin(volumeDir, opts.BaseDir, xlStorageFormatFile))
|
||||||
|
if sterr == nil && st.Mode().IsRegular() {
|
||||||
return errFileNotFound
|
return errFileNotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +117,6 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
forward = forward[:idx]
|
forward = forward[:idx]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if contextCanceled(ctx) {
|
if contextCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
@@ -134,6 +132,9 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
// Forward some errors?
|
// Forward some errors?
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
dirObjects := make(map[string]struct{})
|
dirObjects := make(map[string]struct{})
|
||||||
for i, entry := range entries {
|
for i, entry := range entries {
|
||||||
if len(prefix) > 0 && !strings.HasPrefix(entry, prefix) {
|
if len(prefix) > 0 && !strings.HasPrefix(entry, prefix) {
|
||||||
@@ -245,8 +246,6 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
case osIsNotExist(err):
|
case osIsNotExist(err):
|
||||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
|
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Maybe rename? Would make it inconsistent across disks though.
|
|
||||||
// os.Rename(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1), pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
|
||||||
// It was an object
|
// It was an object
|
||||||
out <- meta
|
out <- meta
|
||||||
continue
|
continue
|
||||||
@@ -265,6 +264,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
|||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If directory entry left on stack, pop it now.
|
// If directory entry left on stack, pop it now.
|
||||||
for len(dirStack) > 0 {
|
for len(dirStack) > 0 {
|
||||||
pop := dirStack[len(dirStack)-1]
|
pop := dirStack[len(dirStack)-1]
|
||||||
|
|||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||||
|
//
|
||||||
|
// This file is part of MinIO Object Storage stack
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/minio/madmin-go"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mrfHealingOpts = madmin.HealOpts{ScanMode: madmin.HealNormalScan, Remove: healDeleteDangling}
|
||||||
|
|
||||||
|
const (
|
||||||
|
mrfInfoResetInterval = 10 * time.Second
|
||||||
|
mrfOpsQueueSize = 10000
|
||||||
|
)
|
||||||
|
|
||||||
|
// partialOperation is a successful upload/delete of an object
|
||||||
|
// but not written in all disks (having quorum)
|
||||||
|
type partialOperation struct {
|
||||||
|
bucket string
|
||||||
|
object string
|
||||||
|
versionID string
|
||||||
|
size int64
|
||||||
|
setIndex int
|
||||||
|
poolIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
type setInfo struct {
|
||||||
|
index, pool int
|
||||||
|
}
|
||||||
|
|
||||||
|
// mrfState sncapsulates all the information
|
||||||
|
// related to the global background MRF.
|
||||||
|
type mrfState struct {
|
||||||
|
ready int32
|
||||||
|
|
||||||
|
ctx context.Context
|
||||||
|
objectAPI ObjectLayer
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
opCh chan partialOperation
|
||||||
|
pendingOps map[partialOperation]setInfo
|
||||||
|
setReconnectEvent chan setInfo
|
||||||
|
|
||||||
|
itemsHealed uint64
|
||||||
|
bytesHealed uint64
|
||||||
|
pendingItems uint64
|
||||||
|
pendingBytes uint64
|
||||||
|
|
||||||
|
triggeredAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize healing MRF subsystem
|
||||||
|
func (m *mrfState) init(ctx context.Context, objAPI ObjectLayer) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
m.ctx = ctx
|
||||||
|
m.objectAPI = objAPI
|
||||||
|
m.opCh = make(chan partialOperation, mrfOpsQueueSize)
|
||||||
|
m.pendingOps = make(map[partialOperation]setInfo)
|
||||||
|
m.setReconnectEvent = make(chan setInfo)
|
||||||
|
|
||||||
|
go globalMRFState.maintainMRFList()
|
||||||
|
go globalMRFState.healRoutine()
|
||||||
|
|
||||||
|
atomic.StoreInt32(&m.ready, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mrfState) initialized() bool {
|
||||||
|
return atomic.LoadInt32(&m.ready) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a partial S3 operation (put/delete) when one or more disks are offline.
|
||||||
|
func (m *mrfState) addPartialOp(op partialOperation) {
|
||||||
|
if !m.initialized() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case m.opCh <- op:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Receive the new set (disk) reconnection event
|
||||||
|
func (m *mrfState) newSetReconnected(pool, set int) {
|
||||||
|
if !m.initialized() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
idler := time.NewTimer(100 * time.Millisecond)
|
||||||
|
defer idler.Stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case m.setReconnectEvent <- setInfo{index: set, pool: pool}:
|
||||||
|
case <-idler.C:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current MRF stats of the last MRF activity
|
||||||
|
func (m *mrfState) getCurrentMRFRoundInfo() madmin.MRFStatus {
|
||||||
|
m.mu.Lock()
|
||||||
|
triggeredAt := m.triggeredAt
|
||||||
|
itemsHealed := m.itemsHealed
|
||||||
|
bytesHealed := m.bytesHealed
|
||||||
|
pendingItems := m.pendingItems
|
||||||
|
pendingBytes := m.pendingBytes
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
if pendingItems == 0 {
|
||||||
|
return madmin.MRFStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return madmin.MRFStatus{
|
||||||
|
Started: triggeredAt,
|
||||||
|
ItemsHealed: itemsHealed,
|
||||||
|
BytesHealed: bytesHealed,
|
||||||
|
TotalItems: itemsHealed + pendingItems,
|
||||||
|
TotalBytes: bytesHealed + pendingBytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maintainMRFList gathers the list of successful partial uploads
|
||||||
|
// from all underlying er.sets and puts them in a global map which
|
||||||
|
// should not have more than 10000 entries.
|
||||||
|
func (m *mrfState) maintainMRFList() {
|
||||||
|
for fOp := range m.opCh {
|
||||||
|
m.mu.Lock()
|
||||||
|
if len(m.pendingOps) > mrfOpsQueueSize {
|
||||||
|
m.mu.Unlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.pendingOps[fOp] = setInfo{index: fOp.setIndex, pool: fOp.poolIndex}
|
||||||
|
m.pendingItems++
|
||||||
|
if fOp.size > 0 {
|
||||||
|
m.pendingBytes += uint64(fOp.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset current MRF stats
|
||||||
|
func (m *mrfState) resetMRFInfoIfNoPendingOps() {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.pendingItems > 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.itemsHealed = 0
|
||||||
|
m.bytesHealed = 0
|
||||||
|
m.pendingItems = 0
|
||||||
|
m.pendingBytes = 0
|
||||||
|
m.triggeredAt = time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// healRoutine listens to new disks reconnection events and
|
||||||
|
// issues healing requests for queued objects belonging to the
|
||||||
|
// corresponding erasure set
|
||||||
|
func (m *mrfState) healRoutine() {
|
||||||
|
idler := time.NewTimer(mrfInfoResetInterval)
|
||||||
|
defer idler.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
idler.Reset(mrfInfoResetInterval)
|
||||||
|
select {
|
||||||
|
case <-m.ctx.Done():
|
||||||
|
return
|
||||||
|
case <-idler.C:
|
||||||
|
m.resetMRFInfoIfNoPendingOps()
|
||||||
|
case setInfo := <-m.setReconnectEvent:
|
||||||
|
// Get the list of objects related the er.set
|
||||||
|
// to which the connected disk belongs.
|
||||||
|
var mrfOperations []partialOperation
|
||||||
|
m.mu.Lock()
|
||||||
|
for k, v := range m.pendingOps {
|
||||||
|
if v == setInfo {
|
||||||
|
mrfOperations = append(mrfOperations, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
if len(mrfOperations) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
m.triggeredAt = time.Now().UTC()
|
||||||
|
m.mu.Unlock()
|
||||||
|
|
||||||
|
// Heal objects
|
||||||
|
for _, u := range mrfOperations {
|
||||||
|
waitForLowHTTPReq(globalHealConfig.IOCount, globalHealConfig.Sleep)
|
||||||
|
if _, err := m.objectAPI.HealObject(m.ctx, u.bucket, u.object, u.versionID, mrfHealingOpts); err != nil {
|
||||||
|
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||||
|
// If not deleted, assume they failed.
|
||||||
|
logger.LogIf(m.ctx, err)
|
||||||
|
} else {
|
||||||
|
m.mu.Lock()
|
||||||
|
m.itemsHealed++
|
||||||
|
m.pendingItems--
|
||||||
|
m.mu.Unlock()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.mu.Lock()
|
||||||
|
m.itemsHealed++
|
||||||
|
m.pendingItems--
|
||||||
|
m.bytesHealed += uint64(u.size)
|
||||||
|
m.pendingBytes -= uint64(u.size)
|
||||||
|
m.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
delete(m.pendingOps, u)
|
||||||
|
m.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize healing MRF
|
||||||
|
func initHealMRF(ctx context.Context, obj ObjectLayer) {
|
||||||
|
globalMRFState.init(ctx, obj)
|
||||||
|
}
|
||||||
@@ -157,6 +157,9 @@ func sortIPs(ipList []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getConsoleEndpoints() (consoleEndpoints []string) {
|
func getConsoleEndpoints() (consoleEndpoints []string) {
|
||||||
|
if globalBrowserRedirectURL != nil {
|
||||||
|
return []string{globalBrowserRedirectURL.String()}
|
||||||
|
}
|
||||||
var ipList []string
|
var ipList []string
|
||||||
if globalMinioConsoleHost == "" {
|
if globalMinioConsoleHost == "" {
|
||||||
ipList = sortIPs(mustGetLocalIP4().ToSlice())
|
ipList = sortIPs(mustGetLocalIP4().ToSlice())
|
||||||
|
|||||||
+41
-2
@@ -860,7 +860,7 @@ func (sys *NotificationSys) GetNetPerfInfo(ctx context.Context) madmin.NetPerfIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return madmin.NetPerfInfo{
|
return madmin.NetPerfInfo{
|
||||||
Addr: globalLocalNodeName,
|
NodeCommon: madmin.NodeCommon{Addr: globalLocalNodeName},
|
||||||
RemotePeers: netInfos,
|
RemotePeers: netInfos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -935,7 +935,7 @@ func (sys *NotificationSys) GetParallelNetPerfInfo(ctx context.Context) madmin.N
|
|||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
return madmin.NetPerfInfo{
|
return madmin.NetPerfInfo{
|
||||||
Addr: globalLocalNodeName,
|
NodeCommon: madmin.NodeCommon{Addr: globalLocalNodeName},
|
||||||
RemotePeers: netInfos,
|
RemotePeers: netInfos,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1427,3 +1427,42 @@ func (sys *NotificationSys) GetClusterMetrics(ctx context.Context) chan Metric {
|
|||||||
}(&wg, ch)
|
}(&wg, ch)
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Speedtest run GET/PUT tests at input concurrency for requested object size,
|
||||||
|
// optionally you can extend the tests longer with time.Duration.
|
||||||
|
func (sys *NotificationSys) Speedtest(ctx context.Context, size int, concurrent int, duration time.Duration) []madmin.SpeedtestResult {
|
||||||
|
results := make([]madmin.SpeedtestResult, len(sys.peerClients)+1)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for index := range sys.peerClients {
|
||||||
|
if sys.peerClients[index] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
wg.Add(1)
|
||||||
|
go func(index int) {
|
||||||
|
defer wg.Done()
|
||||||
|
r, err := sys.peerClients[index].Speedtest(ctx, size, concurrent, duration)
|
||||||
|
results[index].Endpoint = sys.peerClients[index].String()
|
||||||
|
results[index].Err = err
|
||||||
|
if err == nil {
|
||||||
|
results[index].Uploads = r.Uploads
|
||||||
|
results[index].Downloads = r.Downloads
|
||||||
|
}
|
||||||
|
}(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
r, err := selfSpeedtest(ctx, size, concurrent, duration)
|
||||||
|
results[len(results)-1].Endpoint = globalMinioEndpoint
|
||||||
|
results[len(results)-1].Err = err
|
||||||
|
if err == nil {
|
||||||
|
results[len(results)-1].Uploads = r.Uploads
|
||||||
|
results[len(results)-1].Downloads = r.Downloads
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|||||||
@@ -173,6 +173,8 @@ type ObjectInfo struct {
|
|||||||
|
|
||||||
Legacy bool // indicates object on disk is in legacy data format
|
Legacy bool // indicates object on disk is in legacy data format
|
||||||
|
|
||||||
|
Multipart bool // indicates if object is multipart object.
|
||||||
|
|
||||||
// backendType indicates which backend filled this structure
|
// backendType indicates which backend filled this structure
|
||||||
backendType BackendType
|
backendType BackendType
|
||||||
|
|
||||||
@@ -193,6 +195,7 @@ func (o ObjectInfo) Clone() (cinfo ObjectInfo) {
|
|||||||
Size: o.Size,
|
Size: o.Size,
|
||||||
IsDir: o.IsDir,
|
IsDir: o.IsDir,
|
||||||
ETag: o.ETag,
|
ETag: o.ETag,
|
||||||
|
Multipart: o.Multipart,
|
||||||
InnerETag: o.InnerETag,
|
InnerETag: o.InnerETag,
|
||||||
VersionID: o.VersionID,
|
VersionID: o.VersionID,
|
||||||
IsLatest: o.IsLatest,
|
IsLatest: o.IsLatest,
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ const (
|
|||||||
// MinIO tmp meta prefix.
|
// MinIO tmp meta prefix.
|
||||||
minioMetaTmpBucket = minioMetaBucket + "/tmp"
|
minioMetaTmpBucket = minioMetaBucket + "/tmp"
|
||||||
// MinIO tmp meta prefix for deleted objects.
|
// MinIO tmp meta prefix for deleted objects.
|
||||||
minioMetaTmpDeletedBucket = minioMetaTmpBucket + "/.trash"
|
minioMetaTmpDeletedBucket = minioMetaTmpBucket + "/.trash"
|
||||||
|
minioMetaSpeedTestBucket = minioMetaBucket + "/speedtest"
|
||||||
|
minioMetaSpeedTestBucketPrefix = "objects/"
|
||||||
|
|
||||||
// DNS separator (period), used for bucket name validation.
|
// DNS separator (period), used for bucket name validation.
|
||||||
dnsDelimiter = "."
|
dnsDelimiter = "."
|
||||||
|
|||||||
@@ -19,13 +19,11 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
|
||||||
xhttp "github.com/minio/minio/internal/http"
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -262,19 +260,7 @@ func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool) {
|
|||||||
|
|
||||||
if objInfo.Bucket != "" && objInfo.Name != "" {
|
if objInfo.Bucket != "" && objInfo.Name != "" {
|
||||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !delete {
|
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !delete {
|
||||||
ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
|
||||||
Name: objInfo.Name,
|
|
||||||
UserTags: objInfo.UserTags,
|
|
||||||
VersionID: objInfo.VersionID,
|
|
||||||
ModTime: objInfo.ModTime,
|
|
||||||
IsLatest: objInfo.IsLatest,
|
|
||||||
DeleteMarker: objInfo.DeleteMarker,
|
|
||||||
})
|
|
||||||
if !expiryTime.IsZero() {
|
|
||||||
w.Header()[xhttp.AmzExpiration] = []string{
|
|
||||||
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiryTime.Format(http.TimeFormat), ruleID),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -969,3 +969,21 @@ func (client *peerRESTClient) GetPeerMetrics(ctx context.Context) (<-chan Metric
|
|||||||
}(ch)
|
}(ch)
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client *peerRESTClient) Speedtest(ctx context.Context, size, concurrent int, duration time.Duration) (SpeedtestResult, error) {
|
||||||
|
values := make(url.Values)
|
||||||
|
values.Set(peerRESTSize, strconv.Itoa(size))
|
||||||
|
values.Set(peerRESTConcurrent, strconv.Itoa(concurrent))
|
||||||
|
values.Set(peerRESTDuration, duration.String())
|
||||||
|
|
||||||
|
respBody, err := client.callWithContext(context.Background(), peerRESTMethodSpeedtest, values, nil, -1)
|
||||||
|
if err != nil {
|
||||||
|
return SpeedtestResult{}, err
|
||||||
|
}
|
||||||
|
defer http.DrainBody(respBody)
|
||||||
|
|
||||||
|
dec := gob.NewDecoder(respBody)
|
||||||
|
var result SpeedtestResult
|
||||||
|
err = dec.Decode(&result)
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ const (
|
|||||||
peerRESTMethodUpdateMetacacheListing = "/updatemetacache"
|
peerRESTMethodUpdateMetacacheListing = "/updatemetacache"
|
||||||
peerRESTMethodGetPeerMetrics = "/peermetrics"
|
peerRESTMethodGetPeerMetrics = "/peermetrics"
|
||||||
peerRESTMethodLoadTransitionTierConfig = "/loadtransitiontierconfig"
|
peerRESTMethodLoadTransitionTierConfig = "/loadtransitiontierconfig"
|
||||||
|
peerRESTMethodSpeedtest = "/speedtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -81,6 +82,9 @@ const (
|
|||||||
peerRESTTraceS3 = "s3"
|
peerRESTTraceS3 = "s3"
|
||||||
peerRESTTraceOS = "os"
|
peerRESTTraceOS = "os"
|
||||||
peerRESTTraceThreshold = "threshold"
|
peerRESTTraceThreshold = "threshold"
|
||||||
|
peerRESTSize = "size"
|
||||||
|
peerRESTConcurrent = "concurrent"
|
||||||
|
peerRESTDuration = "duration"
|
||||||
|
|
||||||
peerRESTListenBucket = "bucket"
|
peerRESTListenBucket = "bucket"
|
||||||
peerRESTListenPrefix = "prefix"
|
peerRESTListenPrefix = "prefix"
|
||||||
|
|||||||
+181
-2
@@ -19,6 +19,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -27,12 +28,17 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
humanize "github.com/dustin/go-humanize"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/minio/madmin-go"
|
"github.com/minio/madmin-go"
|
||||||
b "github.com/minio/minio/internal/bucket/bandwidth"
|
b "github.com/minio/minio/internal/bucket/bandwidth"
|
||||||
"github.com/minio/minio/internal/event"
|
"github.com/minio/minio/internal/event"
|
||||||
|
"github.com/minio/minio/internal/hash"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/tinylib/msgp/msgp"
|
"github.com/tinylib/msgp/msgp"
|
||||||
)
|
)
|
||||||
@@ -129,8 +135,8 @@ func (s *peerRESTServer) LoadPolicyMappingHandler(w http.ResponseWriter, r *http
|
|||||||
s.writeErrorResponse(w, errors.New("user-or-group is missing"))
|
s.writeErrorResponse(w, errors.New("user-or-group is missing"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, isGroup := vars[peerRESTIsGroup]
|
|
||||||
|
|
||||||
|
_, isGroup := r.URL.Query()[peerRESTIsGroup]
|
||||||
if err := globalIAMSys.LoadPolicyMapping(objAPI, userOrGroup, isGroup); err != nil {
|
if err := globalIAMSys.LoadPolicyMapping(objAPI, userOrGroup, isGroup); err != nil {
|
||||||
s.writeErrorResponse(w, err)
|
s.writeErrorResponse(w, err)
|
||||||
return
|
return
|
||||||
@@ -384,7 +390,6 @@ func (s *peerRESTServer) NetInfoHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
if n != r.ContentLength {
|
if n != r.ContentLength {
|
||||||
err := fmt.Errorf("Subnet health: short read: expected %d found %d", r.ContentLength, n)
|
err := fmt.Errorf("Subnet health: short read: expected %d found %d", r.ContentLength, n)
|
||||||
|
|
||||||
logger.LogIf(ctx, err)
|
logger.LogIf(ctx, err)
|
||||||
w.Header().Set("FinalStatus", err.Error())
|
w.Header().Set("FinalStatus", err.Error())
|
||||||
return
|
return
|
||||||
@@ -1098,6 +1103,179 @@ func (s *peerRESTServer) GetPeerMetrics(w http.ResponseWriter, r *http.Request)
|
|||||||
w.(http.Flusher).Flush()
|
w.(http.Flusher).Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SpeedtestResult return value of the speedtest function
|
||||||
|
type SpeedtestResult struct {
|
||||||
|
Uploads uint64
|
||||||
|
Downloads uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpeedtestObject implements "random-read" object reader
|
||||||
|
type SpeedtestObject struct {
|
||||||
|
buf []byte
|
||||||
|
remaining int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bo *SpeedtestObject) Read(b []byte) (int, error) {
|
||||||
|
if bo.remaining == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if len(b) > len(bo.buf) {
|
||||||
|
b = b[:len(bo.buf)]
|
||||||
|
}
|
||||||
|
if len(b) > bo.remaining {
|
||||||
|
b = b[:bo.remaining]
|
||||||
|
}
|
||||||
|
copy(b, bo.buf)
|
||||||
|
bo.remaining -= len(b)
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runs the speedtest on local MinIO process.
|
||||||
|
func selfSpeedtest(ctx context.Context, size, concurrent int, duration time.Duration) (SpeedtestResult, error) {
|
||||||
|
var result SpeedtestResult
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
return result, errServerNotInitialized
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket := minioMetaSpeedTestBucket
|
||||||
|
|
||||||
|
buf := make([]byte, humanize.MiByte)
|
||||||
|
rand.Read(buf)
|
||||||
|
|
||||||
|
objCountPerThread := make([]uint64, concurrent)
|
||||||
|
|
||||||
|
var objUploadCount uint64
|
||||||
|
var objDownloadCount uint64
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
doneCh1 := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
time.Sleep(duration)
|
||||||
|
close(doneCh1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
objNamePrefix := minioMetaSpeedTestBucketPrefix + uuid.New().String()
|
||||||
|
|
||||||
|
wg.Add(concurrent)
|
||||||
|
for i := 0; i < concurrent; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
hashReader, err := hash.NewReader(&SpeedtestObject{buf, size},
|
||||||
|
int64(size), "", "", int64(size))
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
reader := NewPutObjReader(hashReader)
|
||||||
|
_, err = objAPI.PutObject(ctx, bucket, fmt.Sprintf("%s.%d.%d",
|
||||||
|
objNamePrefix, i, objCountPerThread[i]), reader, ObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
objCountPerThread[i]++
|
||||||
|
atomic.AddUint64(&objUploadCount, 1)
|
||||||
|
select {
|
||||||
|
case <-doneCh1:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
doneCh2 := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
time.Sleep(duration)
|
||||||
|
close(doneCh2)
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(concurrent)
|
||||||
|
for i := 0; i < concurrent; i++ {
|
||||||
|
go func(i int) {
|
||||||
|
defer wg.Done()
|
||||||
|
var j uint64
|
||||||
|
for {
|
||||||
|
if objCountPerThread[i] == j {
|
||||||
|
j = 0
|
||||||
|
}
|
||||||
|
r, err := objAPI.GetObjectNInfo(ctx, bucket, fmt.Sprintf("%s.%d.%d",
|
||||||
|
objNamePrefix, i, j), nil, nil, noLock, ObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err = io.Copy(ioutil.Discard, r)
|
||||||
|
r.Close()
|
||||||
|
if err != nil {
|
||||||
|
logger.LogIf(ctx, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j++
|
||||||
|
atomic.AddUint64(&objDownloadCount, 1)
|
||||||
|
select {
|
||||||
|
case <-doneCh2:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
return SpeedtestResult{objUploadCount, objDownloadCount}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *peerRESTServer) SpeedtestHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !s.IsValid(w, r) {
|
||||||
|
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||||
|
}
|
||||||
|
|
||||||
|
objAPI := newObjectLayerFn()
|
||||||
|
if objAPI == nil {
|
||||||
|
s.writeErrorResponse(w, errServerNotInitialized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sizeStr := r.URL.Query().Get(peerRESTSize)
|
||||||
|
durationStr := r.URL.Query().Get(peerRESTDuration)
|
||||||
|
concurrentStr := r.URL.Query().Get(peerRESTConcurrent)
|
||||||
|
|
||||||
|
size, err := strconv.Atoi(sizeStr)
|
||||||
|
if err != nil {
|
||||||
|
size = 64 * humanize.MiByte
|
||||||
|
}
|
||||||
|
|
||||||
|
concurrent, err := strconv.Atoi(concurrentStr)
|
||||||
|
if err != nil {
|
||||||
|
concurrent = 32
|
||||||
|
}
|
||||||
|
|
||||||
|
duration, err := time.ParseDuration(durationStr)
|
||||||
|
if err != nil {
|
||||||
|
duration = time.Second * 10
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := selfSpeedtest(r.Context(), size, concurrent, duration)
|
||||||
|
if err != nil {
|
||||||
|
s.writeErrorResponse(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := gob.NewEncoder(w)
|
||||||
|
if err := enc.Encode(result); err != nil {
|
||||||
|
s.writeErrorResponse(w, errors.New("Encoding report failed: "+err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.(http.Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
// registerPeerRESTHandlers - register peer rest router.
|
// registerPeerRESTHandlers - register peer rest router.
|
||||||
func registerPeerRESTHandlers(router *mux.Router) {
|
func registerPeerRESTHandlers(router *mux.Router) {
|
||||||
server := &peerRESTServer{}
|
server := &peerRESTServer{}
|
||||||
@@ -1140,4 +1318,5 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
|||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodUpdateMetacacheListing).HandlerFunc(httpTraceHdrs(server.UpdateMetacacheListingHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodUpdateMetacacheListing).HandlerFunc(httpTraceHdrs(server.UpdateMetacacheListingHandler))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetPeerMetrics).HandlerFunc(httpTraceHdrs(server.GetPeerMetrics))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetPeerMetrics).HandlerFunc(httpTraceHdrs(server.GetPeerMetrics))
|
||||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadTransitionTierConfig).HandlerFunc(httpTraceHdrs(server.LoadTransitionTierConfigHandler))
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadTransitionTierConfig).HandlerFunc(httpTraceHdrs(server.LoadTransitionTierConfigHandler))
|
||||||
|
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSpeedtest).HandlerFunc(httpTraceHdrs(server.SpeedtestHandler))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ var startsWithConds = map[string]bool{
|
|||||||
"$key": true,
|
"$key": true,
|
||||||
"$success_action_redirect": true,
|
"$success_action_redirect": true,
|
||||||
"$redirect": true,
|
"$redirect": true,
|
||||||
"$success_action_status": false,
|
"$success_action_status": true,
|
||||||
"$x-amz-algorithm": false,
|
"$x-amz-algorithm": false,
|
||||||
"$x-amz-credential": false,
|
"$x-amz-credential": false,
|
||||||
"$x-amz-date": false,
|
"$x-amz-date": false,
|
||||||
|
|||||||
+1
-8
@@ -459,14 +459,6 @@ func serverMain(ctx *cli.Context) {
|
|||||||
// Initialize all sub-systems
|
// Initialize all sub-systems
|
||||||
newAllSubsystems()
|
newAllSubsystems()
|
||||||
|
|
||||||
globalMinioEndpoint = func() string {
|
|
||||||
host := globalMinioHost
|
|
||||||
if host == "" {
|
|
||||||
host = sortIPs(localIP4.ToSlice())[0]
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s://%s", getURLScheme(globalIsTLS), net.JoinHostPort(host, globalMinioPort))
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Is distributed setup, error out if no certificates are found for HTTPS endpoints.
|
// Is distributed setup, error out if no certificates are found for HTTPS endpoints.
|
||||||
if globalIsDistErasure {
|
if globalIsDistErasure {
|
||||||
if globalEndpoints.HTTPS() && !globalIsTLS {
|
if globalEndpoints.HTTPS() && !globalIsTLS {
|
||||||
@@ -537,6 +529,7 @@ func serverMain(ctx *cli.Context) {
|
|||||||
// Enable background operations for erasure coding
|
// Enable background operations for erasure coding
|
||||||
if globalIsErasure {
|
if globalIsErasure {
|
||||||
initAutoHeal(GlobalContext, newObject)
|
initAutoHeal(GlobalContext, newObject)
|
||||||
|
initHealMRF(GlobalContext, newObject)
|
||||||
initBackgroundTransition(GlobalContext, newObject)
|
initBackgroundTransition(GlobalContext, newObject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,11 @@ func isNotIPv4(host string) bool {
|
|||||||
// port "80" and "443" before displaying on the startup
|
// port "80" and "443" before displaying on the startup
|
||||||
// banner. Returns a new list of API endpoints.
|
// banner. Returns a new list of API endpoints.
|
||||||
func stripStandardPorts(apiEndpoints []string, host string) (newAPIEndpoints []string) {
|
func stripStandardPorts(apiEndpoints []string, host string) (newAPIEndpoints []string) {
|
||||||
|
if len(apiEndpoints) == 1 && globalBrowserRedirectURL != nil {
|
||||||
|
if apiEndpoints[0] == globalBrowserRedirectURL.String() {
|
||||||
|
return []string{globalBrowserRedirectURL.String()}
|
||||||
|
}
|
||||||
|
}
|
||||||
newAPIEndpoints = make([]string, len(apiEndpoints))
|
newAPIEndpoints = make([]string, len(apiEndpoints))
|
||||||
// Check all API endpoints for standard ports and strip them.
|
// Check all API endpoints for standard ports and strip them.
|
||||||
for i, apiEndpoint := range apiEndpoints {
|
for i, apiEndpoint := range apiEndpoints {
|
||||||
|
|||||||
@@ -181,6 +181,17 @@ type FileInfo struct {
|
|||||||
SuccessorModTime time.Time
|
SuccessorModTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InlineData returns true if object contents are inlined alongside its metadata.
|
||||||
|
func (fi FileInfo) InlineData() bool {
|
||||||
|
_, ok := fi.Metadata[ReservedMetadataPrefixLower+"inline-data"]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetInlineData marks object (version) as inline.
|
||||||
|
func (fi FileInfo) SetInlineData() {
|
||||||
|
fi.Metadata[ReservedMetadataPrefixLower+"inline-data"] = "true"
|
||||||
|
}
|
||||||
|
|
||||||
// VersionPurgeStatusKey denotes purge status in metadata
|
// VersionPurgeStatusKey denotes purge status in metadata
|
||||||
const VersionPurgeStatusKey = "purgestatus"
|
const VersionPurgeStatusKey = "purgestatus"
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ type WebIdentityResult struct {
|
|||||||
PackedPolicySize int `xml:",omitempty"`
|
PackedPolicySize int `xml:",omitempty"`
|
||||||
|
|
||||||
// The issuing authority of the web identity token presented. For OpenID Connect
|
// The issuing authority of the web identity token presented. For OpenID Connect
|
||||||
// ID tokens, this contains the value of the iss field. For OAuth 2.0 access tokens,
|
// ID tokens, this contains the value of the iss field. For OAuth 2.0 id_tokens,
|
||||||
// this contains the value of the ProviderId parameter that was passed in the
|
// this contains the value of the ProviderId parameter that was passed in the
|
||||||
// AssumeRoleWithWebIdentity request.
|
// AssumeRoleWithWebIdentity request.
|
||||||
Provider string `xml:",omitempty"`
|
Provider string `xml:",omitempty"`
|
||||||
@@ -162,7 +162,7 @@ type ClientGrantsResult struct {
|
|||||||
PackedPolicySize int `xml:",omitempty"`
|
PackedPolicySize int `xml:",omitempty"`
|
||||||
|
|
||||||
// The issuing authority of the web identity token presented. For OpenID Connect
|
// The issuing authority of the web identity token presented. For OpenID Connect
|
||||||
// ID tokens, this contains the value of the iss field. For OAuth 2.0 access tokens,
|
// ID tokens, this contains the value of the iss field. For OAuth 2.0 id_tokens,
|
||||||
// this contains the value of the ProviderId parameter that was passed in the
|
// this contains the value of the ProviderId parameter that was passed in the
|
||||||
// AssumeRoleWithClientGrants request.
|
// AssumeRoleWithClientGrants request.
|
||||||
Provider string `xml:",omitempty"`
|
Provider string `xml:",omitempty"`
|
||||||
|
|||||||
+46
-2
@@ -58,6 +58,8 @@ const (
|
|||||||
// JWT claim keys
|
// JWT claim keys
|
||||||
expClaim = "exp"
|
expClaim = "exp"
|
||||||
subClaim = "sub"
|
subClaim = "sub"
|
||||||
|
audClaim = "aud"
|
||||||
|
azpClaim = "azp"
|
||||||
issClaim = "iss"
|
issClaim = "iss"
|
||||||
|
|
||||||
// JWT claim to check the parent user
|
// JWT claim to check the parent user
|
||||||
@@ -332,13 +334,50 @@ func (sts *stsAPIHandlers) AssumeRoleWithSSO(w http.ResponseWriter, r *http.Requ
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// REQUIRED. Audience(s) that this ID Token is intended for.
|
||||||
|
// It MUST contain the OAuth 2.0 client_id of the Relying Party
|
||||||
|
// as an audience value. It MAY also contain identifiers for
|
||||||
|
// other audiences. In the general case, the aud value is an
|
||||||
|
// array of case sensitive strings. In the common special case
|
||||||
|
// when there is one audience, the aud value MAY be a single
|
||||||
|
// case sensitive
|
||||||
|
audValues, ok := iampolicy.GetValuesFromClaims(m, audClaim)
|
||||||
|
if !ok {
|
||||||
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||||
|
errors.New("STS JWT Token has `aud` claim invalid, `aud` must match configured OpenID Client ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !audValues.Contains(globalOpenIDConfig.ClientID) {
|
||||||
|
// if audience claims is missing, look for "azp" claims.
|
||||||
|
// OPTIONAL. Authorized party - the party to which the ID
|
||||||
|
// Token was issued. If present, it MUST contain the OAuth
|
||||||
|
// 2.0 Client ID of this party. This Claim is only needed
|
||||||
|
// when the ID Token has a single audience value and that
|
||||||
|
// audience is different than the authorized party. It MAY
|
||||||
|
// be included even when the authorized party is the same
|
||||||
|
// as the sole audience. The azp value is a case sensitive
|
||||||
|
// string containing a StringOrURI value
|
||||||
|
azpValues, ok := iampolicy.GetValuesFromClaims(m, azpClaim)
|
||||||
|
if !ok {
|
||||||
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||||
|
errors.New("STS JWT Token has `aud` claim invalid, `aud` must match configured OpenID Client ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !azpValues.Contains(globalOpenIDConfig.ClientID) {
|
||||||
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||||
|
errors.New("STS JWT Token has `azp` claim invalid, `azp` must match configured OpenID Client ID"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var subFromToken string
|
var subFromToken string
|
||||||
if v, ok := m[subClaim]; ok {
|
if v, ok := m[subClaim]; ok {
|
||||||
subFromToken, _ = v.(string)
|
subFromToken, _ = v.(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if subFromToken == "" {
|
if subFromToken == "" {
|
||||||
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue,
|
||||||
|
errors.New("STS JWT Token has `sub` claim missing, `sub` claim is mandatory"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,7 +580,12 @@ func (sts *stsAPIHandlers) AssumeRoleWithLDAPIdentity(w http.ResponseWriter, r *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expiryDur := globalLDAPConfig.GetExpiryDuration()
|
expiryDur, err := globalLDAPConfig.GetExpiryDuration(r.Form.Get(stsDurationSeconds))
|
||||||
|
if err != nil {
|
||||||
|
writeSTSErrorResponse(ctx, w, true, ErrSTSInvalidParameterValue, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
m := map[string]interface{}{
|
m := map[string]interface{}{
|
||||||
expClaim: UTCNow().Add(expiryDur).Unix(),
|
expClaim: UTCNow().Add(expiryDur).Unix(),
|
||||||
ldapUser: ldapUserDN,
|
ldapUser: ldapUserDN,
|
||||||
|
|||||||
+25
-20
@@ -218,25 +218,27 @@ func contains(slice interface{}, elem interface{}) bool {
|
|||||||
// disk since the name of this latter is randomly generated.
|
// disk since the name of this latter is randomly generated.
|
||||||
type profilerWrapper struct {
|
type profilerWrapper struct {
|
||||||
// Profile recorded at start of benchmark.
|
// Profile recorded at start of benchmark.
|
||||||
base []byte
|
records map[string][]byte
|
||||||
stopFn func() ([]byte, error)
|
stopFn func() ([]byte, error)
|
||||||
ext string
|
ext string
|
||||||
}
|
}
|
||||||
|
|
||||||
// recordBase will record the profile and store it as the base.
|
// record will record the profile and store it as the base.
|
||||||
func (p *profilerWrapper) recordBase(name string, debug int) {
|
func (p *profilerWrapper) record(profileType string, debug int, recordName string) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
p.base = nil
|
if p.records == nil {
|
||||||
err := pprof.Lookup(name).WriteTo(&buf, debug)
|
p.records = make(map[string][]byte)
|
||||||
|
}
|
||||||
|
err := pprof.Lookup(profileType).WriteTo(&buf, debug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p.base = buf.Bytes()
|
p.records[recordName] = buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base returns the recorded base if any.
|
// Records returns the recorded profiling if any.
|
||||||
func (p profilerWrapper) Base() []byte {
|
func (p profilerWrapper) Records() map[string][]byte {
|
||||||
return p.base
|
return p.records
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the currently running benchmark.
|
// Stop the currently running benchmark.
|
||||||
@@ -268,9 +270,10 @@ func getProfileData() (map[string][]byte, error) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
dst[typ+"."+prof.Extension()] = buf
|
dst[typ+"."+prof.Extension()] = buf
|
||||||
}
|
}
|
||||||
buf = prof.Base()
|
for name, buf := range prof.Records() {
|
||||||
if len(buf) > 0 {
|
if len(buf) > 0 {
|
||||||
dst[typ+"-before"+"."+prof.Extension()] = buf
|
dst[typ+"-"+name+"."+prof.Extension()] = buf
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dst, nil
|
return dst, nil
|
||||||
@@ -314,7 +317,7 @@ func startProfiler(profilerType string) (minioProfiler, error) {
|
|||||||
}
|
}
|
||||||
case madmin.ProfilerMEM:
|
case madmin.ProfilerMEM:
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
prof.recordBase("heap", 0)
|
prof.record("heap", 0, "before")
|
||||||
prof.stopFn = func() ([]byte, error) {
|
prof.stopFn = func() ([]byte, error) {
|
||||||
runtime.GC()
|
runtime.GC()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@@ -330,7 +333,7 @@ func startProfiler(profilerType string) (minioProfiler, error) {
|
|||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
case madmin.ProfilerMutex:
|
case madmin.ProfilerMutex:
|
||||||
prof.recordBase("mutex", 0)
|
prof.record("mutex", 0, "before")
|
||||||
runtime.SetMutexProfileFraction(1)
|
runtime.SetMutexProfileFraction(1)
|
||||||
prof.stopFn = func() ([]byte, error) {
|
prof.stopFn = func() ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
@@ -339,7 +342,7 @@ func startProfiler(profilerType string) (minioProfiler, error) {
|
|||||||
return buf.Bytes(), err
|
return buf.Bytes(), err
|
||||||
}
|
}
|
||||||
case madmin.ProfilerThreads:
|
case madmin.ProfilerThreads:
|
||||||
prof.recordBase("threadcreate", 0)
|
prof.record("threadcreate", 0, "before")
|
||||||
prof.stopFn = func() ([]byte, error) {
|
prof.stopFn = func() ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := pprof.Lookup("threadcreate").WriteTo(&buf, 0)
|
err := pprof.Lookup("threadcreate").WriteTo(&buf, 0)
|
||||||
@@ -347,7 +350,8 @@ func startProfiler(profilerType string) (minioProfiler, error) {
|
|||||||
}
|
}
|
||||||
case madmin.ProfilerGoroutines:
|
case madmin.ProfilerGoroutines:
|
||||||
prof.ext = "txt"
|
prof.ext = "txt"
|
||||||
prof.recordBase("goroutine", 1)
|
prof.record("goroutine", 1, "before")
|
||||||
|
prof.record("goroutine", 2, "before,debug=2")
|
||||||
prof.stopFn = func() ([]byte, error) {
|
prof.stopFn = func() ([]byte, error) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err := pprof.Lookup("goroutine").WriteTo(&buf, 1)
|
err := pprof.Lookup("goroutine").WriteTo(&buf, 1)
|
||||||
@@ -386,8 +390,8 @@ func startProfiler(profilerType string) (minioProfiler, error) {
|
|||||||
|
|
||||||
// minioProfiler - minio profiler interface.
|
// minioProfiler - minio profiler interface.
|
||||||
type minioProfiler interface {
|
type minioProfiler interface {
|
||||||
// Return base profile. 'nil' if none.
|
// Return recorded profiles, each profile associated with a distinct generic name.
|
||||||
Base() []byte
|
Records() map[string][]byte
|
||||||
// Stop the profiler
|
// Stop the profiler
|
||||||
Stop() ([]byte, error)
|
Stop() ([]byte, error)
|
||||||
// Return extension of profile
|
// Return extension of profile
|
||||||
@@ -509,6 +513,7 @@ func newCustomHTTPProxyTransport(tlsConfig *tls.Config, dialTimeout time.Duratio
|
|||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
DialContext: xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
|
DialContext: xhttp.DialContextWithDNSCache(globalDNSCache, xhttp.NewInternodeDialContext(dialTimeout)),
|
||||||
MaxIdleConnsPerHost: 1024,
|
MaxIdleConnsPerHost: 1024,
|
||||||
|
MaxConnsPerHost: 1024,
|
||||||
WriteBufferSize: 16 << 10, // 16KiB moving up from 4KiB default
|
WriteBufferSize: 16 << 10, // 16KiB moving up from 4KiB default
|
||||||
ReadBufferSize: 16 << 10, // 16KiB moving up from 4KiB default
|
ReadBufferSize: 16 << 10, // 16KiB moving up from 4KiB default
|
||||||
IdleConnTimeout: 15 * time.Second,
|
IdleConnTimeout: 15 * time.Second,
|
||||||
|
|||||||
@@ -784,7 +784,9 @@ func (z *xlMetaV2) UpdateObjectVersion(fi FileInfo) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case DeleteType:
|
case DeleteType:
|
||||||
return errMethodNotAllowed
|
if version.DeleteMarker.VersionID == uv {
|
||||||
|
return errMethodNotAllowed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,7 +970,7 @@ func (j xlMetaV2DeleteMarker) ToFileInfo(volume, path string) (FileInfo, error)
|
|||||||
|
|
||||||
// UsesDataDir returns true if this object version uses its data directory for
|
// UsesDataDir returns true if this object version uses its data directory for
|
||||||
// its contents and false otherwise.
|
// its contents and false otherwise.
|
||||||
func (j *xlMetaV2Object) UsesDataDir() bool {
|
func (j xlMetaV2Object) UsesDataDir() bool {
|
||||||
// Skip if this version is not transitioned, i.e it uses its data directory.
|
// Skip if this version is not transitioned, i.e it uses its data directory.
|
||||||
if !bytes.Equal(j.MetaSys[ReservedMetadataPrefixLower+TransitionStatus], []byte(lifecycle.TransitionComplete)) {
|
if !bytes.Equal(j.MetaSys[ReservedMetadataPrefixLower+TransitionStatus], []byte(lifecycle.TransitionComplete)) {
|
||||||
return true
|
return true
|
||||||
@@ -978,6 +980,19 @@ func (j *xlMetaV2Object) UsesDataDir() bool {
|
|||||||
return isRestoredObjectOnDisk(j.MetaUser)
|
return isRestoredObjectOnDisk(j.MetaUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j *xlMetaV2Object) SetTransition(fi FileInfo) {
|
||||||
|
j.MetaSys[ReservedMetadataPrefixLower+TransitionStatus] = []byte(fi.TransitionStatus)
|
||||||
|
j.MetaSys[ReservedMetadataPrefixLower+TransitionedObjectName] = []byte(fi.TransitionedObjName)
|
||||||
|
j.MetaSys[ReservedMetadataPrefixLower+TransitionedVersionID] = []byte(fi.TransitionVersionID)
|
||||||
|
j.MetaSys[ReservedMetadataPrefixLower+TransitionTier] = []byte(fi.TransitionTier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *xlMetaV2Object) RemoveRestoreHdrs() {
|
||||||
|
delete(j.MetaUser, xhttp.AmzRestore)
|
||||||
|
delete(j.MetaUser, xhttp.AmzRestoreExpiryDays)
|
||||||
|
delete(j.MetaUser, xhttp.AmzRestoreRequestDate)
|
||||||
|
}
|
||||||
|
|
||||||
func (j xlMetaV2Object) ToFileInfo(volume, path string) (FileInfo, error) {
|
func (j xlMetaV2Object) ToFileInfo(volume, path string) (FileInfo, error) {
|
||||||
versionID := ""
|
versionID := ""
|
||||||
var uv uuid.UUID
|
var uv uuid.UUID
|
||||||
@@ -1209,14 +1224,11 @@ func (z *xlMetaV2) DeleteVersion(fi FileInfo) (string, bool, error) {
|
|||||||
if version.ObjectV2.VersionID == uv {
|
if version.ObjectV2.VersionID == uv {
|
||||||
switch {
|
switch {
|
||||||
case fi.ExpireRestored:
|
case fi.ExpireRestored:
|
||||||
delete(z.Versions[i].ObjectV2.MetaUser, xhttp.AmzRestore)
|
z.Versions[i].ObjectV2.RemoveRestoreHdrs()
|
||||||
delete(z.Versions[i].ObjectV2.MetaUser, xhttp.AmzRestoreExpiryDays)
|
|
||||||
delete(z.Versions[i].ObjectV2.MetaUser, xhttp.AmzRestoreRequestDate)
|
|
||||||
case fi.TransitionStatus == lifecycle.TransitionComplete:
|
case fi.TransitionStatus == lifecycle.TransitionComplete:
|
||||||
z.Versions[i].ObjectV2.MetaSys[ReservedMetadataPrefixLower+TransitionStatus] = []byte(fi.TransitionStatus)
|
z.Versions[i].ObjectV2.SetTransition(fi)
|
||||||
z.Versions[i].ObjectV2.MetaSys[ReservedMetadataPrefixLower+TransitionedObjectName] = []byte(fi.TransitionedObjName)
|
|
||||||
z.Versions[i].ObjectV2.MetaSys[ReservedMetadataPrefixLower+TransitionedVersionID] = []byte(fi.TransitionVersionID)
|
|
||||||
z.Versions[i].ObjectV2.MetaSys[ReservedMetadataPrefixLower+TransitionTier] = []byte(fi.TransitionTier)
|
|
||||||
default:
|
default:
|
||||||
z.Versions = append(z.Versions[:i], z.Versions[i+1:]...)
|
z.Versions = append(z.Versions[:i], z.Versions[i+1:]...)
|
||||||
// if uv has tiered content we add a
|
// if uv has tiered content we add a
|
||||||
|
|||||||
+49
-52
@@ -43,7 +43,6 @@ import (
|
|||||||
"github.com/minio/minio/internal/bucket/lifecycle"
|
"github.com/minio/minio/internal/bucket/lifecycle"
|
||||||
"github.com/minio/minio/internal/color"
|
"github.com/minio/minio/internal/color"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
|
||||||
"github.com/minio/minio/internal/disk"
|
"github.com/minio/minio/internal/disk"
|
||||||
xioutil "github.com/minio/minio/internal/ioutil"
|
xioutil "github.com/minio/minio/internal/ioutil"
|
||||||
"github.com/minio/minio/internal/logger"
|
"github.com/minio/minio/internal/logger"
|
||||||
@@ -292,7 +291,7 @@ func newXLStorage(ep Endpoint) (*xlStorage, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create all necessary bucket folders if possible.
|
// Create all necessary bucket folders if possible.
|
||||||
if err = p.MakeVolBulk(context.TODO(), minioMetaBucket, minioMetaTmpBucket, minioMetaMultipartBucket, dataUsageBucket); err != nil {
|
if err = p.MakeVolBulk(context.TODO(), minioMetaBucket, minioMetaTmpBucket, minioMetaMultipartBucket, dataUsageBucket, minioMetaSpeedTestBucket); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +518,6 @@ func (s *xlStorage) DiskInfo(context.Context) (info DiskInfo, err error) {
|
|||||||
dcinfo.UsedInodes = di.Files - di.Ffree
|
dcinfo.UsedInodes = di.Files - di.Ffree
|
||||||
dcinfo.FreeInodes = di.Ffree
|
dcinfo.FreeInodes = di.Ffree
|
||||||
dcinfo.FSType = di.FSType
|
dcinfo.FSType = di.FSType
|
||||||
|
|
||||||
diskID, err := s.GetDiskID()
|
diskID, err := s.GetDiskID()
|
||||||
if errors.Is(err, errUnformattedDisk) {
|
if errors.Is(err, errUnformattedDisk) {
|
||||||
// if we found an unformatted disk then
|
// if we found an unformatted disk then
|
||||||
@@ -530,7 +528,6 @@ func (s *xlStorage) DiskInfo(context.Context) (info DiskInfo, err error) {
|
|||||||
// returned any error other than fresh disk
|
// returned any error other than fresh disk
|
||||||
dcinfo.Healing = s.Healing() != nil
|
dcinfo.Healing = s.Healing() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dcinfo.ID = diskID
|
dcinfo.ID = diskID
|
||||||
return dcinfo, err
|
return dcinfo, err
|
||||||
}
|
}
|
||||||
@@ -553,28 +550,7 @@ func (s *xlStorage) getVolDir(volume string) (string, error) {
|
|||||||
return volumeDir, nil
|
return volumeDir, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDiskID - returns the cached disk uuid
|
func (s *xlStorage) checkFormatJSON() (os.FileInfo, error) {
|
||||||
func (s *xlStorage) GetDiskID() (string, error) {
|
|
||||||
s.RLock()
|
|
||||||
diskID := s.diskID
|
|
||||||
fileInfo := s.formatFileInfo
|
|
||||||
lastCheck := s.formatLastCheck
|
|
||||||
s.RUnlock()
|
|
||||||
|
|
||||||
// check if we have a valid disk ID that is less than 1 second old.
|
|
||||||
if fileInfo != nil && diskID != "" && time.Since(lastCheck) <= time.Second {
|
|
||||||
return diskID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Lock()
|
|
||||||
// If somebody else updated the disk ID and changed the time, return what they got.
|
|
||||||
if !lastCheck.IsZero() && !s.formatLastCheck.Equal(lastCheck) && diskID != "" {
|
|
||||||
s.Unlock()
|
|
||||||
// Somebody else got the lock first.
|
|
||||||
return diskID, nil
|
|
||||||
}
|
|
||||||
s.Unlock()
|
|
||||||
|
|
||||||
formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile)
|
formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile)
|
||||||
fi, err := Lstat(formatFile)
|
fi, err := Lstat(formatFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -582,20 +558,41 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
|||||||
if osIsNotExist(err) {
|
if osIsNotExist(err) {
|
||||||
if err = Access(s.diskPath); err == nil {
|
if err = Access(s.diskPath); err == nil {
|
||||||
// Disk is present but missing `format.json`
|
// Disk is present but missing `format.json`
|
||||||
return "", errUnformattedDisk
|
return nil, errUnformattedDisk
|
||||||
}
|
}
|
||||||
if osIsNotExist(err) {
|
if osIsNotExist(err) {
|
||||||
return "", errDiskNotFound
|
return nil, errDiskNotFound
|
||||||
} else if osIsPermission(err) {
|
} else if osIsPermission(err) {
|
||||||
return "", errDiskAccessDenied
|
return nil, errDiskAccessDenied
|
||||||
}
|
}
|
||||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||||
return "", errCorruptedFormat
|
return nil, errCorruptedFormat
|
||||||
} else if osIsPermission(err) {
|
} else if osIsPermission(err) {
|
||||||
return "", errDiskAccessDenied
|
return nil, errDiskAccessDenied
|
||||||
}
|
}
|
||||||
logger.LogIf(GlobalContext, err) // log unexpected errors
|
logger.LogIf(GlobalContext, err) // log unexpected errors
|
||||||
return "", errCorruptedFormat
|
return nil, errCorruptedFormat
|
||||||
|
}
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiskID - returns the cached disk uuid
|
||||||
|
func (s *xlStorage) GetDiskID() (string, error) {
|
||||||
|
s.RLock()
|
||||||
|
diskID := s.diskID
|
||||||
|
fileInfo := s.formatFileInfo
|
||||||
|
lastCheck := s.formatLastCheck
|
||||||
|
|
||||||
|
// check if we have a valid disk ID that is less than 1 second old.
|
||||||
|
if fileInfo != nil && diskID != "" && time.Since(lastCheck) <= time.Second {
|
||||||
|
s.RUnlock()
|
||||||
|
return diskID, nil
|
||||||
|
}
|
||||||
|
s.RUnlock()
|
||||||
|
|
||||||
|
fi, err := s.checkFormatJSON()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if xioutil.SameFile(fi, fileInfo) && diskID != "" {
|
if xioutil.SameFile(fi, fileInfo) && diskID != "" {
|
||||||
@@ -606,6 +603,7 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
|||||||
return diskID, nil
|
return diskID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
formatFile := pathJoin(s.diskPath, minioMetaBucket, formatConfigFile)
|
||||||
b, err := xioutil.ReadFile(formatFile)
|
b, err := xioutil.ReadFile(formatFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the disk is still not initialized.
|
// If the disk is still not initialized.
|
||||||
@@ -1099,8 +1097,8 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
|||||||
if readData {
|
if readData {
|
||||||
if len(fi.Data) > 0 || fi.Size == 0 {
|
if len(fi.Data) > 0 || fi.Size == 0 {
|
||||||
if len(fi.Data) > 0 {
|
if len(fi.Data) > 0 {
|
||||||
if _, ok := fi.Metadata[ReservedMetadataPrefixLower+"inline-data"]; !ok {
|
if !fi.InlineData() {
|
||||||
fi.Metadata[ReservedMetadataPrefixLower+"inline-data"] = "true"
|
fi.SetInlineData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fi, nil
|
return fi, nil
|
||||||
@@ -1110,11 +1108,12 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
|||||||
// - object has not yet transitioned
|
// - object has not yet transitioned
|
||||||
// - object size lesser than 128KiB
|
// - object size lesser than 128KiB
|
||||||
// - object has maximum of 1 parts
|
// - object has maximum of 1 parts
|
||||||
if fi.TransitionStatus == "" && fi.DataDir != "" && fi.Size <= smallFileThreshold && len(fi.Parts) == 1 {
|
if fi.TransitionStatus == "" &&
|
||||||
// Enable O_DIRECT optionally only if drive supports it.
|
fi.DataDir != "" && fi.Size <= smallFileThreshold &&
|
||||||
requireDirectIO := globalStorageClass.GetDMA() == storageclass.DMAReadWrite
|
len(fi.Parts) == 1 {
|
||||||
partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number)
|
partPath := fmt.Sprintf("part.%d", fi.Parts[0].Number)
|
||||||
fi.Data, err = s.readAllData(volumeDir, pathJoin(volumeDir, path, fi.DataDir, partPath), requireDirectIO)
|
dataPath := pathJoin(volumeDir, path, fi.DataDir, partPath)
|
||||||
|
fi.Data, err = s.readAllData(volumeDir, dataPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FileInfo{}, err
|
return FileInfo{}, err
|
||||||
}
|
}
|
||||||
@@ -1124,15 +1123,8 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
|||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *xlStorage) readAllData(volumeDir string, filePath string, requireDirectIO bool) (buf []byte, err error) {
|
func (s *xlStorage) readAllData(volumeDir string, filePath string) (buf []byte, err error) {
|
||||||
var r io.ReadCloser
|
f, err := OpenFileDirectIO(filePath, readMode, 0666)
|
||||||
if requireDirectIO {
|
|
||||||
var f *os.File
|
|
||||||
f, err = OpenFileDirectIO(filePath, readMode, 0666)
|
|
||||||
r = &odirectReader{f, nil, nil, true, true, s, nil}
|
|
||||||
} else {
|
|
||||||
r, err = OpenFile(filePath, readMode, 0)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if osIsNotExist(err) {
|
if osIsNotExist(err) {
|
||||||
// Check if the object doesn't exist because its bucket
|
// Check if the object doesn't exist because its bucket
|
||||||
@@ -1164,7 +1156,7 @@ func (s *xlStorage) readAllData(volumeDir string, filePath string, requireDirect
|
|||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
r := &odirectReader{f, nil, nil, true, true, s, nil}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
buf, err = ioutil.ReadAll(r)
|
buf, err = ioutil.ReadAll(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1192,8 +1184,7 @@ func (s *xlStorage) ReadAll(ctx context.Context, volume string, path string) (bu
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
requireDirectIO := globalStorageClass.GetDMA() == storageclass.DMAReadWrite
|
return s.readAllData(volumeDir, filePath)
|
||||||
return s.readAllData(volumeDir, filePath, requireDirectIO)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFile reads exactly len(buf) bytes into buf. It returns the
|
// ReadFile reads exactly len(buf) bytes into buf. It returns the
|
||||||
@@ -1416,7 +1407,7 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
|
|||||||
|
|
||||||
var file *os.File
|
var file *os.File
|
||||||
// O_DIRECT only supported if offset is zero
|
// O_DIRECT only supported if offset is zero
|
||||||
if offset == 0 && globalStorageClass.GetDMA() == storageclass.DMAReadWrite {
|
if offset == 0 {
|
||||||
file, err = OpenFileDirectIO(filePath, readMode, 0666)
|
file, err = OpenFileDirectIO(filePath, readMode, 0666)
|
||||||
} else {
|
} else {
|
||||||
// Open the file for reading.
|
// Open the file for reading.
|
||||||
@@ -1457,7 +1448,7 @@ func (s *xlStorage) ReadFileStream(ctx context.Context, volume, path string, off
|
|||||||
return nil, errIsNotRegular
|
return nil, errIsNotRegular
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset == 0 && globalStorageClass.GetDMA() == storageclass.DMAReadWrite {
|
if offset == 0 {
|
||||||
or := &odirectReader{file, nil, nil, true, false, s, nil}
|
or := &odirectReader{file, nil, nil, true, false, s, nil}
|
||||||
if length <= smallFileThreshold {
|
if length <= smallFileThreshold {
|
||||||
or = &odirectReader{file, nil, nil, true, true, s, nil}
|
or = &odirectReader{file, nil, nil, true, true, s, nil}
|
||||||
@@ -2055,6 +2046,12 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
|||||||
// Purge the destination path as we are not preserving anything
|
// Purge the destination path as we are not preserving anything
|
||||||
// versioned object was not requested.
|
// versioned object was not requested.
|
||||||
oldDstDataPath = pathJoin(dstVolumeDir, dstPath, ofi.DataDir)
|
oldDstDataPath = pathJoin(dstVolumeDir, dstPath, ofi.DataDir)
|
||||||
|
// if old destination path is same as new destination path
|
||||||
|
// there is nothing to purge, this is true in case of healing
|
||||||
|
// avoid setting oldDstDataPath at that point.
|
||||||
|
if oldDstDataPath == dstDataPath {
|
||||||
|
oldDstDataPath = ""
|
||||||
|
}
|
||||||
xlMeta.data.remove(nullVersionID, ofi.DataDir)
|
xlMeta.data.remove(nullVersionID, ofi.DataDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/minio/minio/internal/config/storageclass"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCheckPathLength(t *testing.T) {
|
func TestCheckPathLength(t *testing.T) {
|
||||||
@@ -1152,10 +1150,6 @@ func TestXLStorageReadFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for l := 0; l < 2; l++ {
|
for l := 0; l < 2; l++ {
|
||||||
// 1st loop tests with dma=write, 2nd loop tests with dma=read-write.
|
|
||||||
if l == 1 {
|
|
||||||
globalStorageClass.DMA = storageclass.DMAReadWrite
|
|
||||||
}
|
|
||||||
// Following block validates all ReadFile test cases.
|
// Following block validates all ReadFile test cases.
|
||||||
for i, testCase := range testCases {
|
for i, testCase := range testCases {
|
||||||
var n int64
|
var n int64
|
||||||
@@ -1213,9 +1207,6 @@ func TestXLStorageReadFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the flag.
|
|
||||||
globalStorageClass.DMA = storageclass.DMAWrite
|
|
||||||
|
|
||||||
// TestXLStorage for permission denied.
|
// TestXLStorage for permission denied.
|
||||||
if runtime.GOOS != globalWindowsOSName {
|
if runtime.GOOS != globalWindowsOSName {
|
||||||
permDeniedDir := createPermDeniedFile(t)
|
permDeniedDir := createPermDeniedFile(t)
|
||||||
|
|||||||
@@ -8,6 +8,20 @@ if [ "${1}" != "minio" ]; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
## look for specific a `config.env` file to load all the
|
||||||
|
## minio settings from
|
||||||
|
docker_minio_env() {
|
||||||
|
if [ -f "$MINIO_CONFIG_ENV_FILE" ]; then
|
||||||
|
config_env_file="${MINIO_CONFIG_ENV_FILE}"
|
||||||
|
else
|
||||||
|
config_env_file="/run/secrets/${MINIO_CONFIG_ENV_FILE}"
|
||||||
|
fi
|
||||||
|
if [ -f "$config_env_file" ]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "${config_env_file}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
## Look for docker secrets at given absolute path or in default documented location.
|
## Look for docker secrets at given absolute path or in default documented location.
|
||||||
docker_secrets_env_old() {
|
docker_secrets_env_old() {
|
||||||
if [ -f "$MINIO_ACCESS_KEY_FILE" ]; then
|
if [ -f "$MINIO_ACCESS_KEY_FILE" ]; then
|
||||||
@@ -73,15 +87,16 @@ docker_kms_secret_encryption_env() {
|
|||||||
|
|
||||||
# su-exec to requested user, if service cannot run exec will fail.
|
# su-exec to requested user, if service cannot run exec will fail.
|
||||||
docker_switch_user() {
|
docker_switch_user() {
|
||||||
if [ ! -z "${MINIO_USERNAME}" ] && [ ! -z "${MINIO_GROUPNAME}" ]; then
|
if [ -n "${MINIO_USERNAME}" ] && [ -n "${MINIO_GROUPNAME}" ]; then
|
||||||
if [ ! -z "${MINIO_UID}" ] && [ ! -z "${MINIO_GID}" ]; then
|
if [ -n "${MINIO_UID}" ] && [ -n "${MINIO_GID}" ]; then
|
||||||
groupadd -g "$MINIO_GID" "$MINIO_GROUPNAME" && \
|
groupadd -g "$MINIO_GID" "$MINIO_GROUPNAME" && \
|
||||||
useradd -u "$MINIO_UID" -g "$MINIO_GROUPNAME" "$MINIO_USERNAME"
|
useradd -u "$MINIO_UID" -g "$MINIO_GROUPNAME" "$MINIO_USERNAME"
|
||||||
else
|
else
|
||||||
groupadd "$MINIO_GROUPNAME" && \
|
groupadd "$MINIO_GROUPNAME" && \
|
||||||
useradd -g "$MINIO_GROUPNAME" "$MINIO_USERNAME"
|
useradd -g "$MINIO_GROUPNAME" "$MINIO_USERNAME"
|
||||||
fi
|
fi
|
||||||
exec setpriv --reuid="${MINIO_USERNAME}" --regid="${MINIO_GROUPNAME}" --keep-groups "$@"
|
exec setpriv --reuid="${MINIO_USERNAME}" \
|
||||||
|
--regid="${MINIO_GROUPNAME}" --keep-groups "$@"
|
||||||
else
|
else
|
||||||
exec "$@"
|
exec "$@"
|
||||||
fi
|
fi
|
||||||
@@ -96,5 +111,10 @@ docker_secrets_env
|
|||||||
## Set kms encryption from secrets if necessary. Override
|
## Set kms encryption from secrets if necessary. Override
|
||||||
docker_kms_secret_encryption_env
|
docker_kms_secret_encryption_env
|
||||||
|
|
||||||
|
## Set all config environment variables from 'config.env' if necessary.
|
||||||
|
## Overrides all previous settings and also overrides all
|
||||||
|
## environment values passed from 'podman run -e ENV=value'
|
||||||
|
docker_minio_env
|
||||||
|
|
||||||
## Switch to user if applicable.
|
## Switch to user if applicable.
|
||||||
docker_switch_user "$@"
|
docker_switch_user "$@"
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ sudo mount -o relatime /tmp/data /mnt/cache
|
|||||||
podman run --net=host -e MINIO_ROOT_USER={s3-access-key} -e MINIO_ROOT_PASSWORD={s3-secret-key} \
|
podman run --net=host -e MINIO_ROOT_USER={s3-access-key} -e MINIO_ROOT_PASSWORD={s3-secret-key} \
|
||||||
-e MINIO_CACHE_DRIVES=/cache -e MINIO_CACHE_QUOTA=99 -e MINIO_CACHE_AFTER=0 \
|
-e MINIO_CACHE_DRIVES=/cache -e MINIO_CACHE_QUOTA=99 -e MINIO_CACHE_AFTER=0 \
|
||||||
-e MINIO_CACHE_WATERMARK_LOW=90 -e MINIO_CACHE_WATERMARK_HIGH=95 \
|
-e MINIO_CACHE_WATERMARK_LOW=90 -e MINIO_CACHE_WATERMARK_HIGH=95 \
|
||||||
-v /mnt/cache:/cache minio/minio:latest gateway s3
|
-v /mnt/cache:/cache minio/minio:latest gateway s3 --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Assumptions
|
## Assumptions
|
||||||
|
|||||||
@@ -44,10 +44,13 @@ Example: Start MinIO server in a 12 drives setup, using MinIO binary.
|
|||||||
minio server /data{1...12}
|
minio server /data{1...12}
|
||||||
```
|
```
|
||||||
|
|
||||||
Example: Start MinIO server in a 8 drives setup, using MinIO Docker image.
|
Example: Start MinIO server in a 8 drives setup, using MinIO Docker image.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
podman run -p 9000:9000 --name minio \
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
|
--name minio \
|
||||||
-v /mnt/data1:/data1 \
|
-v /mnt/data1:/data1 \
|
||||||
-v /mnt/data2:/data2 \
|
-v /mnt/data2:/data2 \
|
||||||
-v /mnt/data3:/data3 \
|
-v /mnt/data3:/data3 \
|
||||||
@@ -56,7 +59,7 @@ podman run -p 9000:9000 --name minio \
|
|||||||
-v /mnt/data6:/data6 \
|
-v /mnt/data6:/data6 \
|
||||||
-v /mnt/data7:/data7 \
|
-v /mnt/data7:/data7 \
|
||||||
-v /mnt/data8:/data8 \
|
-v /mnt/data8:/data8 \
|
||||||
minio/minio server /data{1...8}
|
minio/minio server /data{1...8} --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Test your setup
|
### 3. Test your setup
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ MinIO Gateway adds Amazon S3 compatibility to Microsoft Azure Blob Storage.
|
|||||||
## Run MinIO Gateway for Microsoft Azure Blob Storage
|
## Run MinIO Gateway for Microsoft Azure Blob Storage
|
||||||
### Using Docker
|
### Using Docker
|
||||||
```
|
```
|
||||||
podman run -p 9000:9000 --name azure-s3 \
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
|
--name azure-s3 \
|
||||||
-e "MINIO_ROOT_USER=azurestorageaccountname" \
|
-e "MINIO_ROOT_USER=azurestorageaccountname" \
|
||||||
-e "MINIO_ROOT_PASSWORD=azurestorageaccountkey" \
|
-e "MINIO_ROOT_PASSWORD=azurestorageaccountkey" \
|
||||||
minio/minio gateway azure
|
minio/minio gateway azure --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Binary
|
### Using Binary
|
||||||
|
|||||||
+5
-2
@@ -21,12 +21,15 @@ MinIO GCS Gateway allows you to access Google Cloud Storage (GCS) with Amazon S3
|
|||||||
|
|
||||||
### 1.2 Run MinIO GCS Gateway Using Docker
|
### 1.2 Run MinIO GCS Gateway Using Docker
|
||||||
```sh
|
```sh
|
||||||
podman run -p 9000:9000 --name gcs-s3 \
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
|
--name gcs-s3 \
|
||||||
-v /path/to/credentials.json:/credentials.json \
|
-v /path/to/credentials.json:/credentials.json \
|
||||||
-e "GOOGLE_APPLICATION_CREDENTIALS=/credentials.json" \
|
-e "GOOGLE_APPLICATION_CREDENTIALS=/credentials.json" \
|
||||||
-e "MINIO_ROOT_USER=minioaccountname" \
|
-e "MINIO_ROOT_USER=minioaccountname" \
|
||||||
-e "MINIO_ROOT_PASSWORD=minioaccountkey" \
|
-e "MINIO_ROOT_PASSWORD=minioaccountkey" \
|
||||||
minio/minio gateway gcs yourprojectid
|
minio/minio gateway gcs yourprojectid --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.3 Run MinIO GCS Gateway Using the MinIO Binary
|
### 1.3 Run MinIO GCS Gateway Using the MinIO Binary
|
||||||
|
|||||||
@@ -23,11 +23,13 @@ minio gateway hdfs hdfs://namenode:8200
|
|||||||
### Using Docker
|
### Using Docker
|
||||||
Using docker is experimental, most Hadoop environments are not dockerized and may require additional steps in getting this to work properly. You are better off just using the binary in this situation.
|
Using docker is experimental, most Hadoop environments are not dockerized and may require additional steps in getting this to work properly. You are better off just using the binary in this situation.
|
||||||
```
|
```
|
||||||
podman run -p 9000:9000 \
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
--name hdfs-s3 \
|
--name hdfs-s3 \
|
||||||
-e "MINIO_ROOT_USER=minio" \
|
-e "MINIO_ROOT_USER=minio" \
|
||||||
-e "MINIO_ROOT_PASSWORD=minio123" \
|
-e "MINIO_ROOT_PASSWORD=minio123" \
|
||||||
minio/minio gateway hdfs hdfs://namenode:8200
|
minio/minio gateway hdfs hdfs://namenode:8200 --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setup Kerberos
|
### Setup Kerberos
|
||||||
|
|||||||
+5
-2
@@ -9,11 +9,14 @@ MinIO Gateway adds Amazon S3 compatibility to NAS storage. You may run multiple
|
|||||||
Please ensure to replace `/shared/nasvol` with actual mount path.
|
Please ensure to replace `/shared/nasvol` with actual mount path.
|
||||||
|
|
||||||
```
|
```
|
||||||
podman run -p 9000:9000 --name nas-s3 \
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
|
--name nas-s3 \
|
||||||
-e "MINIO_ROOT_USER=minio" \
|
-e "MINIO_ROOT_USER=minio" \
|
||||||
-e "MINIO_ROOT_PASSWORD=minio123" \
|
-e "MINIO_ROOT_PASSWORD=minio123" \
|
||||||
-v /shared/nasvol:/container/vol \
|
-v /shared/nasvol:/container/vol \
|
||||||
minio/minio gateway nas /container/vol
|
minio/minio gateway nas /container/vol --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Binary
|
### Using Binary
|
||||||
|
|||||||
+5
-2
@@ -7,10 +7,13 @@ As a prerequisite to run MinIO S3 gateway, you need valid AWS S3 access key and
|
|||||||
|
|
||||||
### Using Docker
|
### Using Docker
|
||||||
```
|
```
|
||||||
podman run -p 9000:9000 --name minio-s3 \
|
podman run \
|
||||||
|
-p 9000:9000 \
|
||||||
|
-p 9001:9001 \
|
||||||
|
--name minio-s3 \
|
||||||
-e "MINIO_ROOT_USER=aws_s3_access_key" \
|
-e "MINIO_ROOT_USER=aws_s3_access_key" \
|
||||||
-e "MINIO_ROOT_PASSWORD=aws_s3_secret_key" \
|
-e "MINIO_ROOT_PASSWORD=aws_s3_secret_key" \
|
||||||
minio/minio gateway s3
|
minio/minio gateway s3 --console-address ":9001"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using Binary
|
### Using Binary
|
||||||
|
|||||||
+80
-1
@@ -36,9 +36,11 @@ minio server /mnt/data
|
|||||||
|
|
||||||
## Audit Targets
|
## Audit Targets
|
||||||
Assuming `mc` is already [configured](https://docs.min.io/docs/minio-client-quickstart-guide.html)
|
Assuming `mc` is already [configured](https://docs.min.io/docs/minio-client-quickstart-guide.html)
|
||||||
|
|
||||||
|
### HTTP Target
|
||||||
```
|
```
|
||||||
mc admin config get myminio/ audit_webhook
|
mc admin config get myminio/ audit_webhook
|
||||||
audit_webhook:name1 enable=off endpoint= auth_token= client_cert= client_key=
|
audit_webhook:name1 enable=off endpoint= auth_token= client_cert= client_key=
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -119,6 +121,83 @@ NOTE:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Kafka Target
|
||||||
|
Assuming that you already have Apache Kafka configured and running.
|
||||||
|
```
|
||||||
|
mc admin config set myminio/ audit_kafka
|
||||||
|
KEY:
|
||||||
|
audit_kafka[:name] send audit logs to kafka endpoints
|
||||||
|
|
||||||
|
ARGS:
|
||||||
|
brokers* (csv) comma separated list of Kafka broker addresses
|
||||||
|
topic (string) Kafka topic used for bucket notifications
|
||||||
|
sasl_username (string) username for SASL/PLAIN or SASL/SCRAM authentication
|
||||||
|
sasl_password (string) password for SASL/PLAIN or SASL/SCRAM authentication
|
||||||
|
sasl_mechanism (string) sasl authentication mechanism, default 'plain'
|
||||||
|
tls_client_auth (string) clientAuth determines the Kafka server's policy for TLS client auth
|
||||||
|
sasl (on|off) set to 'on' to enable SASL authentication
|
||||||
|
tls (on|off) set to 'on' to enable TLS
|
||||||
|
tls_skip_verify (on|off) trust server TLS without verification, defaults to "on" (verify)
|
||||||
|
client_tls_cert (path) path to client certificate for mTLS auth
|
||||||
|
client_tls_key (path) path to client key for mTLS auth
|
||||||
|
version (string) specify the version of the Kafka cluster
|
||||||
|
comment (sentence) optionally add a comment to this setting
|
||||||
|
```
|
||||||
|
|
||||||
|
Configure MinIO to send audit logs to locally running Kafka brokers
|
||||||
|
```
|
||||||
|
mc admin config set myminio/ audit_kafka:target1 brokers=localhost:29092 topic=auditlog
|
||||||
|
mc admin service restart myminio/
|
||||||
|
```
|
||||||
|
|
||||||
|
On another terminal assuming you have `kafkacat` installed
|
||||||
|
|
||||||
|
```
|
||||||
|
kafkacat -b localhost:29092 -t auditlog -C
|
||||||
|
|
||||||
|
{"version":"1","deploymentid":"8a1d8091-b874-45df-b9ea-e044eede6ace","time":"2021-07-13T02:00:47.020547414Z","trigger":"incoming","api":{"name":"ListBuckets","status":"OK","statusCode":200,"timeToFirstByte":"261795ns","timeToResponse":"312490ns"},"remotehost":"127.0.0.1","requestID":"16913736591C237F","userAgent":"MinIO (linux; amd64) minio-go/v7.0.11 mc/DEVELOPMENT.2021-07-09T02-22-26Z","requestHeader":{"Authorization":"AWS4-HMAC-SHA256 Credential=minio/20210713/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=7fe65c5467e05ca21de64094688da43f96f34fec82e8955612827079f4600527","User-Agent":"MinIO (linux; amd64) minio-go/v7.0.11 mc/DEVELOPMENT.2021-07-09T02-22-26Z","X-Amz-Content-Sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","X-Amz-Date":"20210713T020047Z"},"responseHeader":{"Accept-Ranges":"bytes","Content-Length":"547","Content-Security-Policy":"block-all-mixed-content","Content-Type":"application/xml","Server":"MinIO","Vary":"Origin,Accept-Encoding","X-Amz-Request-Id":"16913736591C237F","X-Xss-Protection":"1; mode=block"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
MinIO also honors environment variable for Kafka target Audit logging as shown below, this setting will override the endpoint settings in the MinIO server config.
|
||||||
|
|
||||||
|
```
|
||||||
|
mc admin config set myminio/ audit_kafka --env
|
||||||
|
KEY:
|
||||||
|
audit_kafka[:name] send audit logs to kafka endpoints
|
||||||
|
|
||||||
|
ARGS:
|
||||||
|
MINIO_AUDIT_KAFKA_ENABLE* (on|off) enable audit_kafka target, default is 'off'
|
||||||
|
MINIO_AUDIT_KAFKA_BROKERS* (csv) comma separated list of Kafka broker addresses
|
||||||
|
MINIO_AUDIT_KAFKA_TOPIC (string) Kafka topic used for bucket notifications
|
||||||
|
MINIO_AUDIT_KAFKA_SASL_USERNAME (string) username for SASL/PLAIN or SASL/SCRAM authentication
|
||||||
|
MINIO_AUDIT_KAFKA_SASL_PASSWORD (string) password for SASL/PLAIN or SASL/SCRAM authentication
|
||||||
|
MINIO_AUDIT_KAFKA_SASL_MECHANISM (string) sasl authentication mechanism, default 'plain'
|
||||||
|
MINIO_AUDIT_KAFKA_TLS_CLIENT_AUTH (string) clientAuth determines the Kafka server's policy for TLS client auth
|
||||||
|
MINIO_AUDIT_KAFKA_SASL (on|off) set to 'on' to enable SASL authentication
|
||||||
|
MINIO_AUDIT_KAFKA_TLS (on|off) set to 'on' to enable TLS
|
||||||
|
MINIO_AUDIT_KAFKA_TLS_SKIP_VERIFY (on|off) trust server TLS without verification, defaults to "on" (verify)
|
||||||
|
MINIO_AUDIT_KAFKA_CLIENT_TLS_CERT (path) path to client certificate for mTLS auth
|
||||||
|
MINIO_AUDIT_KAFKA_CLIENT_TLS_KEY (path) path to client key for mTLS auth
|
||||||
|
MINIO_AUDIT_KAFKA_VERSION (string) specify the version of the Kafka cluster
|
||||||
|
MINIO_AUDIT_KAFKA_COMMENT (sentence) optionally add a comment to this setting
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
export MINIO_AUDIT_KAFKA_ENABLE_target1="on"
|
||||||
|
export MINIO_AUDIT_KAFKA_BROKERS_target1="localhost:29092"
|
||||||
|
export MINIO_AUDIT_KAFKA_TOPIC_target1="auditlog"
|
||||||
|
minio server /mnt/data
|
||||||
|
```
|
||||||
|
|
||||||
|
Setting this environment variable automatically enables audit logging to the Kafka target. The audit logging is in JSON format as described below.
|
||||||
|
|
||||||
|
NOTE:
|
||||||
|
- `timeToFirstByte` and `timeToResponse` will be expressed in Nanoseconds.
|
||||||
|
- Additionally in the case of the erasure coded setup `tags.objectErasureMap` provides per object details about
|
||||||
|
- Pool number the object operation was performed on.
|
||||||
|
- Set number the object operation was performed on.
|
||||||
|
- The list of disks participating in this operation belong to the set.
|
||||||
|
|
||||||
## Explore Further
|
## Explore Further
|
||||||
* [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide)
|
* [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide)
|
||||||
* [Configure MinIO Server with TLS](https://docs.min.io/docs/how-to-secure-access-to-minio-server-with-tls)
|
* [Configure MinIO Server with TLS](https://docs.min.io/docs/how-to-secure-access-to-minio-server-with-tls)
|
||||||
|
|||||||
@@ -1,84 +1,52 @@
|
|||||||
version: '3.7'
|
version: '3.7'
|
||||||
|
|
||||||
|
# Settings and configurations that are common for all containers
|
||||||
|
x-minio-common: &minio-common
|
||||||
|
image: minio/minio:RELEASE.2021-07-27T02-40-15Z
|
||||||
|
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||||
|
expose:
|
||||||
|
- "9000"
|
||||||
|
- "9001"
|
||||||
|
environment:
|
||||||
|
MINIO_ROOT_USER: minio
|
||||||
|
MINIO_ROOT_PASSWORD: minio123
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
# starts 4 docker containers running minio server instances.
|
# starts 4 docker containers running minio server instances.
|
||||||
# using nginx reverse proxy, load balancing, you can access
|
# using nginx reverse proxy, load balancing, you can access
|
||||||
# it through port 9000.
|
# it through port 9000.
|
||||||
services:
|
services:
|
||||||
minio1:
|
minio1:
|
||||||
image: minio/minio:RELEASE.2021-07-08T19-43-25Z
|
<<: *minio-common
|
||||||
hostname: minio1
|
hostname: minio1
|
||||||
volumes:
|
volumes:
|
||||||
- data1-1:/data1
|
- data1-1:/data1
|
||||||
- data1-2:/data2
|
- data1-2:/data2
|
||||||
expose:
|
|
||||||
- "9000"
|
|
||||||
- "9001"
|
|
||||||
environment:
|
|
||||||
MINIO_ROOT_USER: minio
|
|
||||||
MINIO_ROOT_PASSWORD: minio123
|
|
||||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 20s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
minio2:
|
minio2:
|
||||||
image: minio/minio:RELEASE.2021-07-08T19-43-25Z
|
<<: *minio-common
|
||||||
hostname: minio2
|
hostname: minio2
|
||||||
volumes:
|
volumes:
|
||||||
- data2-1:/data1
|
- data2-1:/data1
|
||||||
- data2-2:/data2
|
- data2-2:/data2
|
||||||
expose:
|
|
||||||
- "9000"
|
|
||||||
- "9001"
|
|
||||||
environment:
|
|
||||||
MINIO_ROOT_USER: minio
|
|
||||||
MINIO_ROOT_PASSWORD: minio123
|
|
||||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 20s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
minio3:
|
minio3:
|
||||||
image: minio/minio:RELEASE.2021-07-08T19-43-25Z
|
<<: *minio-common
|
||||||
hostname: minio3
|
hostname: minio3
|
||||||
volumes:
|
volumes:
|
||||||
- data3-1:/data1
|
- data3-1:/data1
|
||||||
- data3-2:/data2
|
- data3-2:/data2
|
||||||
expose:
|
|
||||||
- "9000"
|
|
||||||
- "9001"
|
|
||||||
environment:
|
|
||||||
MINIO_ROOT_USER: minio
|
|
||||||
MINIO_ROOT_PASSWORD: minio123
|
|
||||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 20s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
minio4:
|
minio4:
|
||||||
image: minio/minio:RELEASE.2021-07-08T19-43-25Z
|
<<: *minio-common
|
||||||
hostname: minio4
|
hostname: minio4
|
||||||
volumes:
|
volumes:
|
||||||
- data4-1:/data1
|
- data4-1:/data1
|
||||||
- data4-2:/data2
|
- data4-2:/data2
|
||||||
expose:
|
|
||||||
- "9000"
|
|
||||||
- "9001"
|
|
||||||
environment:
|
|
||||||
MINIO_ROOT_USER: minio
|
|
||||||
MINIO_ROOT_PASSWORD: minio123
|
|
||||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
|
||||||
interval: 30s
|
|
||||||
timeout: 20s
|
|
||||||
retries: 3
|
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: nginx:1.19.2-alpine
|
image: nginx:1.19.2-alpine
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ To enable Parquet set the environment variable `MINIO_API_SELECT_PARQUET=on`.
|
|||||||
# Example using Python API
|
# Example using Python API
|
||||||
|
|
||||||
## 1. Prerequisites
|
## 1. Prerequisites
|
||||||
- Install MinIO Server from [here](http://docs.min.io/docs/minio-quickstart-guide).
|
- Install MinIO Server from [here](https://docs.min.io/docs/minio-quickstart-guide).
|
||||||
- Familiarity with AWS S3 API.
|
- Familiarity with AWS S3 API.
|
||||||
- Familiarity with Python and installing dependencies.
|
- Familiarity with Python and installing dependencies.
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -11,12 +11,11 @@ Following are advantages for using temporary credentials:
|
|||||||
- Temporary credentials have a limited lifetime, there is no need to rotate them or explicitly revoke them. Expired temporary credentials cannot be reused.
|
- Temporary credentials have a limited lifetime, there is no need to rotate them or explicitly revoke them. Expired temporary credentials cannot be reused.
|
||||||
|
|
||||||
## Identity Federation
|
## Identity Federation
|
||||||
|AuthN | Description |
|
| AuthN | Description |
|
||||||
| :---------------------- | ------------------------------------------ |
|
| :---------------------- | ------------------------------------------ |
|
||||||
| [**Client grants**](https://github.com/minio/minio/blob/master/docs/sts/client-grants.md) | Let applications request `client_grants` using any well-known third party identity provider such as KeyCloak, Okta. This is known as the client grants approach to temporary access. Using this approach helps clients keep MinIO credentials to be secured. MinIO STS supports client grants, tested against identity providers such as KeyCloak, Okta. |
|
|
||||||
| [**WebIdentity**](https://github.com/minio/minio/blob/master/docs/sts/web-identity.md) | Let users request temporary credentials using any OpenID(OIDC) compatible web identity providers such as KeyCloak, Dex, Facebook, Google etc. |
|
| [**WebIdentity**](https://github.com/minio/minio/blob/master/docs/sts/web-identity.md) | Let users request temporary credentials using any OpenID(OIDC) compatible web identity providers such as KeyCloak, Dex, Facebook, Google etc. |
|
||||||
| [**AssumeRole**](https://github.com/minio/minio/blob/master/docs/sts/assume-role.md) | Let MinIO users request temporary credentials using user access and secret keys. |
|
| [**AD/LDAP**](https://github.com/minio/minio/blob/master/docs/sts/ldap.md) | Let AD/LDAP users request temporary credentials using AD/LDAP username and password. |
|
||||||
| [**AD/LDAP**](https://github.com/minio/minio/blob/master/docs/sts/ldap.md) | Let AD/LDAP users request temporary credentials using AD/LDAP username and password. |
|
| [**AssumeRole**](https://github.com/minio/minio/blob/master/docs/sts/assume-role.md) | Let MinIO users request temporary credentials using user access and secret keys. |
|
||||||
|
|
||||||
### Understanding JWT Claims
|
### Understanding JWT Claims
|
||||||
> NOTE: JWT claims are only meant for WebIdentity and ClientGrants.
|
> NOTE: JWT claims are only meant for WebIdentity and ClientGrants.
|
||||||
@@ -24,7 +23,7 @@ Following are advantages for using temporary credentials:
|
|||||||
> - [**AssumeRole**](https://github.com/minio/minio/blob/master/docs/sts/assume-role.md)
|
> - [**AssumeRole**](https://github.com/minio/minio/blob/master/docs/sts/assume-role.md)
|
||||||
> - [**AD/LDAP**](https://github.com/minio/minio/blob/master/docs/sts/ldap.md)
|
> - [**AD/LDAP**](https://github.com/minio/minio/blob/master/docs/sts/ldap.md)
|
||||||
|
|
||||||
The access token received is a signed JSON Web Token (JWT). Use a JWT decoder to decode the access token to access the payload of the token that includes following JWT claims, `policy` claim is mandatory and should be present as part of your JWT claim. Without this claim the generated credentials will not have access to any resources on the server, using these credentials application would receive 'Access Denied' errors.
|
The id_token received is a signed JSON Web Token (JWT). Use a JWT decoder to decode the id_token to access the payload of the token that includes following JWT claims, `policy` claim is mandatory and should be present as part of your JWT claim. Without this claim the generated credentials will not have access to any resources on the server, using these credentials application would receive 'Access Denied' errors.
|
||||||
|
|
||||||
| Claim Name | Type | Claim Value |
|
| Claim Name | Type | Claim Value |
|
||||||
|:----------:|:-------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
|:----------:|:-------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||||
@@ -65,7 +64,7 @@ minio gateway s3
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Using WebIdentiy API
|
### Using WebIdentiy API
|
||||||
On another terminal run `web-identity.go` a sample client application which obtains JWT access tokens from an identity provider, in our case its Keycloak. Uses the returned access token response to get new temporary credentials from the MinIO server using the STS API call `AssumeRoleWithWebIdentity`.
|
On another terminal run `web-identity.go` a sample client application which obtains JWT id_tokens from an identity provider, in our case its Keycloak. Uses the returned id_token response to get new temporary credentials from the MinIO server using the STS API call `AssumeRoleWithWebIdentity`.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go run docs/sts/web-identity.go -cid account -csec 072e7f00-4289-469c-9ab2-bbe843c7f5a8 -config-ep "http://localhost:8080/auth/realms/demo/.well-known/openid-configuration" -port 8888
|
$ go run docs/sts/web-identity.go -cid account -csec 072e7f00-4289-469c-9ab2-bbe843c7f5a8 -config-ep "http://localhost:8080/auth/realms/demo/.well-known/openid-configuration" -port 8888
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Returns a set of temporary security credentials that you can use to access MinIO
|
|||||||
- To be able to reliably use S3 multipart APIs feature of the SDKs without re-inventing the wheel of pre-signing the each URL in multipart API. This is very tedious to implement with all the scenarios of fault tolerance that's already implemented by the client SDK. The general client SDKs don't support multipart with presigned URLs.
|
- To be able to reliably use S3 multipart APIs feature of the SDKs without re-inventing the wheel of pre-signing the each URL in multipart API. This is very tedious to implement with all the scenarios of fault tolerance that's already implemented by the client SDK. The general client SDKs don't support multipart with presigned URLs.
|
||||||
- To be able to easily get the temporary credentials to upload to a prefix. Make it possible for a client to upload a whole folder using the session. The server side applications need not create a presigned URL and serve to the client for each file. Since, the client would have the session it can do it by itself.
|
- To be able to easily get the temporary credentials to upload to a prefix. Make it possible for a client to upload a whole folder using the session. The server side applications need not create a presigned URL and serve to the client for each file. Since, the client would have the session it can do it by itself.
|
||||||
|
|
||||||
The temporary security credentials returned by this API consists of an access key, a secret key, and a security token. Applications can use these temporary security credentials to sign calls to MinIO API operations. The policy applied to these temporary credentials is inherited from the MinIO user credentials. By default, the temporary security credentials created by AssumeRole last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 7 days.
|
The temporary security credentials returned by this API consists of an access key, a secret key, and a security token. Applications can use these temporary security credentials to sign calls to MinIO API operations. The policy applied to these temporary credentials is inherited from the MinIO user credentials. By default, the temporary security credentials created by AssumeRole last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 365 days.
|
||||||
|
|
||||||
## API Request Parameters
|
## API Request Parameters
|
||||||
### Version
|
### Version
|
||||||
@@ -22,13 +22,13 @@ Indicates STS API version information, the only supported value is '2011-06-15'.
|
|||||||
Indicates STS API Authorization information. If you are familiar with AWS Signature V4 Authorization header, this STS API supports signature V4 authorization as mentioned [here](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
|
Indicates STS API Authorization information. If you are familiar with AWS Signature V4 Authorization header, this STS API supports signature V4 authorization as mentioned [here](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html)
|
||||||
|
|
||||||
### DurationSeconds
|
### DurationSeconds
|
||||||
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 7 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
|
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 365 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
|
||||||
|
|
||||||
| Params | Value |
|
| Params | Value |
|
||||||
| :-- | :-- |
|
| :-- | :-- |
|
||||||
| *Type* | *Integer* |
|
| *Type* | *Integer* |
|
||||||
| *Valid Range* | *Minimum value of 900. Maximum value of 604800.* |
|
| *Valid Range* | *Minimum value of 900. Maximum value of 31536000.* |
|
||||||
| *Required* | *No* |
|
| *Required* | *No* |
|
||||||
|
|
||||||
### Policy
|
### Policy
|
||||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import (
|
|||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWTToken - parses the output from IDP access token.
|
// JWTToken - parses the output from IDP id_token.
|
||||||
type JWTToken struct {
|
type JWTToken struct {
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
Expiry int `json:"expires_in"`
|
Expiry int `json:"expires_in"`
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Returns a set of temporary security credentials for applications/clients who hav
|
|||||||
|
|
||||||
Calling AssumeRoleWithClientGrants does not require the use of MinIO default credentials. Therefore, client application can be distributed that requests temporary security credentials without including MinIO default credentials. Instead, the identity of the caller is validated by using a JWT access token from the identity provider. The temporary security credentials returned by this API consists of an access key, a secret key, and a security token. Applications can use these temporary security credentials to sign calls to MinIO API operations.
|
Calling AssumeRoleWithClientGrants does not require the use of MinIO default credentials. Therefore, client application can be distributed that requests temporary security credentials without including MinIO default credentials. Instead, the identity of the caller is validated by using a JWT access token from the identity provider. The temporary security credentials returned by this API consists of an access key, a secret key, and a security token. Applications can use these temporary security credentials to sign calls to MinIO API operations.
|
||||||
|
|
||||||
By default, the temporary security credentials created by AssumeRoleWithClientGrants last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 7 days.
|
By default, the temporary security credentials created by AssumeRoleWithClientGrants last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 365 days.
|
||||||
|
|
||||||
## API Request Parameters
|
## API Request Parameters
|
||||||
### Token
|
### Token
|
||||||
@@ -27,13 +27,13 @@ Indicates STS API version information, the only supported value is '2011-06-15'.
|
|||||||
| *Required* | *Yes* |
|
| *Required* | *Yes* |
|
||||||
|
|
||||||
### DurationSeconds
|
### DurationSeconds
|
||||||
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 7 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds. If no *DurationSeconds* is specified expiry seconds is obtained from *Token*.
|
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 365 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds. If no *DurationSeconds* is specified expiry seconds is obtained from *Token*.
|
||||||
|
|
||||||
| Params | Value |
|
| Params | Value |
|
||||||
| :-- | :-- |
|
| :-- | :-- |
|
||||||
| *Type* | *Integer* |
|
| *Type* | *Integer* |
|
||||||
| *Valid Range* | *Minimum value of 900. Maximum value of 604800.* |
|
| *Valid Range* | *Minimum value of 900. Maximum value of 31536000.* |
|
||||||
| *Required* | *No* |
|
| *Required* | *No* |
|
||||||
|
|
||||||
### Policy
|
### Policy
|
||||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ etcd uses [gcr.io/etcd-development/etcd](https://console.cloud.google.com/gcr/im
|
|||||||
|
|
||||||
```
|
```
|
||||||
rm -rf /tmp/etcd-data.tmp && mkdir -p /tmp/etcd-data.tmp && \
|
rm -rf /tmp/etcd-data.tmp && mkdir -p /tmp/etcd-data.tmp && \
|
||||||
docker rmi gcr.io/etcd-development/etcd:v3.3.9 || true && \
|
podman rmi gcr.io/etcd-development/etcd:v3.3.9 || true && \
|
||||||
podman run \
|
podman run \
|
||||||
-p 2379:2379 \
|
-p 2379:2379 \
|
||||||
-p 2380:2380 \
|
-p 2380:2380 \
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ Before being able to authenticate against the Admin REST API using a client_id a
|
|||||||
|
|
||||||
- Check that `account` client_id has the role 'admin' assigned in the "Service Account Roles" tab.
|
- Check that `account` client_id has the role 'admin' assigned in the "Service Account Roles" tab.
|
||||||
|
|
||||||
After that, you will be able to obtain an access token for the Admin REST API using client_id and client_secret:
|
After that, you will be able to obtain an id_token for the Admin REST API using client_id and client_secret:
|
||||||
|
|
||||||
```
|
```
|
||||||
curl \
|
curl \
|
||||||
|
|||||||
+62
-10
@@ -23,8 +23,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/minio/minio-go/v7"
|
"github.com/minio/minio-go/v7"
|
||||||
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
cr "github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
@@ -37,12 +40,28 @@ var (
|
|||||||
// LDAP credentials
|
// LDAP credentials
|
||||||
ldapUsername string
|
ldapUsername string
|
||||||
ldapPassword string
|
ldapPassword string
|
||||||
|
|
||||||
|
// Display credentials flag
|
||||||
|
displayCreds bool
|
||||||
|
|
||||||
|
// Credential expiry duration
|
||||||
|
expiryDuration time.Duration
|
||||||
|
|
||||||
|
// Bucket to list
|
||||||
|
bucketToList string
|
||||||
|
|
||||||
|
// Session policy file
|
||||||
|
sessionPolicyFile string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.StringVar(&stsEndpoint, "sts-ep", "http://localhost:9000", "STS endpoint")
|
flag.StringVar(&stsEndpoint, "sts-ep", "http://localhost:9000", "STS endpoint")
|
||||||
flag.StringVar(&ldapUsername, "u", "", "AD/LDAP Username")
|
flag.StringVar(&ldapUsername, "u", "", "AD/LDAP Username")
|
||||||
flag.StringVar(&ldapPassword, "p", "", "AD/LDAP Password")
|
flag.StringVar(&ldapPassword, "p", "", "AD/LDAP Password")
|
||||||
|
flag.BoolVar(&displayCreds, "d", false, "Only show generated credentials")
|
||||||
|
flag.DurationVar(&expiryDuration, "e", 0, "Request a duration of validity for the generated credential")
|
||||||
|
flag.StringVar(&bucketToList, "b", "", "Bucket to list (defaults to ldap username)")
|
||||||
|
flag.StringVar(&sessionPolicyFile, "s", "", "File containing session policy to apply to the STS request")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -56,11 +75,31 @@ func main() {
|
|||||||
// LDAP STS API.
|
// LDAP STS API.
|
||||||
|
|
||||||
// Initialize LDAP credentials
|
// Initialize LDAP credentials
|
||||||
li, _ := cr.NewLDAPIdentity(stsEndpoint, ldapUsername, ldapPassword)
|
var ldapOpts []cr.LDAPIdentityOpt
|
||||||
|
if sessionPolicyFile != "" {
|
||||||
|
var policy string
|
||||||
|
if f, err := os.Open(sessionPolicyFile); err != nil {
|
||||||
|
log.Fatalf("Unable to open session policy file: %v", sessionPolicyFile, err)
|
||||||
|
} else {
|
||||||
|
bs, err := ioutil.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error reading session policy file: %v", err)
|
||||||
|
}
|
||||||
|
policy = string(bs)
|
||||||
|
}
|
||||||
|
ldapOpts = append(ldapOpts, cr.LDAPIdentityPolicyOpt(policy))
|
||||||
|
}
|
||||||
|
if expiryDuration != 0 {
|
||||||
|
ldapOpts = append(ldapOpts, cr.LDAPIdentityExpiryOpt(expiryDuration))
|
||||||
|
}
|
||||||
|
li, err := cr.NewLDAPIdentity(stsEndpoint, ldapUsername, ldapPassword, ldapOpts...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error initializing LDAP Identity: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
stsEndpointURL, err := url.Parse(stsEndpoint)
|
stsEndpointURL, err := url.Parse(stsEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Err: %v", err)
|
log.Fatalf("Error parsing sts endpoint: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := &minio.Options{
|
opts := &minio.Options{
|
||||||
@@ -68,22 +107,35 @@ func main() {
|
|||||||
Secure: stsEndpointURL.Scheme == "https",
|
Secure: stsEndpointURL.Scheme == "https",
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(li.Get())
|
v, err := li.Get()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error retrieving STS credentials: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if displayCreds {
|
||||||
|
fmt.Println("Only displaying credentials:")
|
||||||
|
fmt.Println("AccessKeyID:", v.AccessKeyID)
|
||||||
|
fmt.Println("SecretAccessKey:", v.SecretAccessKey)
|
||||||
|
fmt.Println("SessionToken:", v.SessionToken)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Use generated credentials to authenticate with MinIO server
|
// Use generated credentials to authenticate with MinIO server
|
||||||
minioClient, err := minio.New(stsEndpointURL.Host, opts)
|
minioClient, err := minio.New(stsEndpointURL.Host, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalf("Error initializing client: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use minIO Client object normally like the regular client.
|
// Use minIO Client object normally like the regular client.
|
||||||
fmt.Println("Calling list objects with temp creds: ")
|
if bucketToList == "" {
|
||||||
objCh := minioClient.ListObjects(context.Background(), ldapUsername, minio.ListObjectsOptions{})
|
bucketToList = ldapUsername
|
||||||
|
}
|
||||||
|
fmt.Printf("Calling list objects on bucket named `%s` with temp creds:\n===\n", bucketToList)
|
||||||
|
objCh := minioClient.ListObjects(context.Background(), bucketToList, minio.ListObjectsOptions{})
|
||||||
for obj := range objCh {
|
for obj := range objCh {
|
||||||
if obj.Err != nil {
|
if obj.Err != nil {
|
||||||
if err != nil {
|
log.Fatalf("Listing error: %v", obj.Err)
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fmt.Println(obj)
|
fmt.Printf("Key: %s\nSize: %d\nLast Modified: %s\n===\n", obj.Key, obj.Size, obj.LastModified)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-4
@@ -1,5 +1,33 @@
|
|||||||
# AssumeRoleWithLDAPIdentity [](https://slack.min.io)
|
# AssumeRoleWithLDAPIdentity [](https://slack.min.io)
|
||||||
|
|
||||||
|
<!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc-refresh-toc -->
|
||||||
|
**Table of Contents**
|
||||||
|
|
||||||
|
- [AssumeRoleWithLDAPIdentity [](https://slack.min.io)](#assumerolewithldapidentity-slackhttpsslackminioslacktypesvghttpsslackminio)
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Configuring AD/LDAP on MinIO](#configuring-adldap-on-minio)
|
||||||
|
- [Supported modes of operation](#supported-modes-of-operation)
|
||||||
|
- [Lookup-Bind Mode](#lookup-bind-mode)
|
||||||
|
- [Username-Format Mode](#username-format-mode)
|
||||||
|
- [Group membership search](#group-membership-search)
|
||||||
|
- [Variable substitution in AD/LDAP configuration strings](#variable-substitution-in-adldap-configuration-strings)
|
||||||
|
- [Managing User/Group Access Policy](#managing-usergroup-access-policy)
|
||||||
|
- [API Request Parameters](#api-request-parameters)
|
||||||
|
- [LDAPUsername](#ldapusername)
|
||||||
|
- [LDAPPassword](#ldappassword)
|
||||||
|
- [Version](#version)
|
||||||
|
- [DurationSeconds](#durationseconds)
|
||||||
|
- [Policy](#policy)
|
||||||
|
- [Response Elements](#response-elements)
|
||||||
|
- [Errors](#errors)
|
||||||
|
- [Sample `POST` Request](#sample-post-request)
|
||||||
|
- [Sample Response](#sample-response)
|
||||||
|
- [Using LDAP STS API](#using-ldap-sts-api)
|
||||||
|
- [Explore Further](#explore-further)
|
||||||
|
|
||||||
|
<!-- markdown-toc end -->
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
MinIO provides a custom STS API that allows integration with LDAP based corporate environments including Microsoft Active Directory. The MinIO server can be configured in two possible modes: either using a LDAP separate service account, called lookup-bind mode or in username-format mode. In either case the login flow for a user is the same as the STS flow:
|
MinIO provides a custom STS API that allows integration with LDAP based corporate environments including Microsoft Active Directory. The MinIO server can be configured in two possible modes: either using a LDAP separate service account, called lookup-bind mode or in username-format mode. In either case the login flow for a user is the same as the STS flow:
|
||||||
@@ -26,7 +54,6 @@ identity_ldap enable LDAP SSO support
|
|||||||
|
|
||||||
ARGS:
|
ARGS:
|
||||||
MINIO_IDENTITY_LDAP_SERVER_ADDR* (address) AD/LDAP server address e.g. "myldapserver.com:636"
|
MINIO_IDENTITY_LDAP_SERVER_ADDR* (address) AD/LDAP server address e.g. "myldapserver.com:636"
|
||||||
MINIO_IDENTITY_LDAP_STS_EXPIRY (duration) temporary credentials validity duration in s,m,h,d. Default is "1h"
|
|
||||||
MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN (string) DN for LDAP read-only service account used to perform DN and group lookups
|
MINIO_IDENTITY_LDAP_LOOKUP_BIND_DN (string) DN for LDAP read-only service account used to perform DN and group lookups
|
||||||
MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD (string) Password for LDAP read-only service account used to perform DN and group lookups
|
MINIO_IDENTITY_LDAP_LOOKUP_BIND_PASSWORD (string) Password for LDAP read-only service account used to perform DN and group lookups
|
||||||
MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN (string) Base LDAP DN to search for user DN
|
MINIO_IDENTITY_LDAP_USER_DN_SEARCH_BASE_DN (string) Base LDAP DN to search for user DN
|
||||||
@@ -63,9 +90,18 @@ MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER (string) Search filter to lookup
|
|||||||
|
|
||||||
If you set an empty lookup bind password, the lookup bind will use the unauthenticated authentication mechanism, as described in [RFC 4513 Section 5.1.2](https://tools.ietf.org/html/rfc4513#section-5.1.2).
|
If you set an empty lookup bind password, the lookup bind will use the unauthenticated authentication mechanism, as described in [RFC 4513 Section 5.1.2](https://tools.ietf.org/html/rfc4513#section-5.1.2).
|
||||||
|
|
||||||
|
**Automatic LDAP sync:** In Lookup-Bind mode, the server periodically queries the LDAP service to:
|
||||||
|
|
||||||
|
1. find accounts (user DNs) that have been removed;
|
||||||
|
2. find accounts whose group memberships have changed.
|
||||||
|
|
||||||
|
In case 1 above, any active STS credentials or MinIO service accounts belonging to these users are purged.
|
||||||
|
|
||||||
|
In case 2 above, access policies available to the credential are updated to reflect the change - i.e. they will lose any privileges associated with a group they are removed from, and gain any privileges associated with a group they are added to.
|
||||||
|
|
||||||
#### Username-Format Mode ####
|
#### Username-Format Mode ####
|
||||||
|
|
||||||
In this mode, the server does not use a separate LDAP service account. Instead, the username and password provided in the STS API call are used to login to the LDAP server and also to lookup the user's groups. This mode preserves older behavior for compatibility, but users are encouraged to use the Lookup-Bind mode.
|
In this mode, the server does not use a separate LDAP service account. Instead, the username and password provided in the STS API call are used to login to the LDAP server and also to lookup the user's groups. This mode preserves older behavior for compatibility, but users are encouraged to use the Lookup-Bind mode. Some newer features may not be available in this mode of operation.
|
||||||
|
|
||||||
The DN to use to login to LDAP is computed from a username format configuration parameter. This is a list of possible DN templates to be used. For each such template, the username is substituted and the DN is generated. Each generated DN is tried by the MinIO server to login to LDAP. If exactly one successful DN is found, it is used to perform the groups lookup as well.
|
The DN to use to login to LDAP is computed from a username format configuration parameter. This is a list of possible DN templates to be used. For each such template, the username is substituted and the DN is generated. Each generated DN is tried by the MinIO server to login to LDAP. If exactly one successful DN is found, it is used to perform the groups lookup as well.
|
||||||
|
|
||||||
@@ -95,7 +131,6 @@ export MINIO_IDENTITY_LDAP_SERVER_ADDR=myldapserver.com:636
|
|||||||
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT="uid=%s,cn=accounts,dc=myldapserver,dc=com"
|
export MINIO_IDENTITY_LDAP_USERNAME_FORMAT="uid=%s,cn=accounts,dc=myldapserver,dc=com"
|
||||||
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="dc=myldapserver,dc=com"
|
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_BASE_DN="dc=myldapserver,dc=com"
|
||||||
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(memberUid=%s)$)"
|
export MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER="(&(objectclass=groupOfNames)(memberUid=%s)$)"
|
||||||
export MINIO_IDENTITY_LDAP_STS_EXPIRY=60h
|
|
||||||
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
|
export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -163,6 +198,15 @@ Indicates STS API version information, the only supported value is '2011-06-15'.
|
|||||||
| *Type* | *String* |
|
| *Type* | *String* |
|
||||||
| *Required* | *Yes* |
|
| *Required* | *Yes* |
|
||||||
|
|
||||||
|
### DurationSeconds
|
||||||
|
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 365 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds.
|
||||||
|
|
||||||
|
| Params | Value |
|
||||||
|
| :-- | :-- |
|
||||||
|
| *Type* | *Integer* |
|
||||||
|
| *Valid Range* | *Minimum value of 900. Maximum value of 31536000.* |
|
||||||
|
| *Required* | *No* |
|
||||||
|
|
||||||
### Policy
|
### Policy
|
||||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||||
|
|
||||||
@@ -180,7 +224,7 @@ XML error response for this API is similar to [AWS STS AssumeRoleWithWebIdentity
|
|||||||
|
|
||||||
## Sample `POST` Request
|
## Sample `POST` Request
|
||||||
```
|
```
|
||||||
http://minio.cluster:9000?Action=AssumeRoleWithLDAPIdentity&LDAPUsername=foouser&LDAPPassword=foouserpassword&Version=2011-06-15
|
http://minio.cluster:9000?Action=AssumeRoleWithLDAPIdentity&LDAPUsername=foouser&LDAPPassword=foouserpassword&Version=2011-06-15&DurationSeconds=7200
|
||||||
```
|
```
|
||||||
|
|
||||||
## Sample Response
|
## Sample Response
|
||||||
|
|||||||
+11
-11
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
Calling AssumeRoleWithWebIdentity does not require the use of MinIO default credentials. Therefore, you can distribute an application (for example, on mobile devices) that requests temporary security credentials without including MinIO default credentials in the application. Instead, the identity of the caller is validated by using a JWT access token from the web identity provider. The temporary security credentials returned by this API consists of an access key, a secret key, and a security token. Applications can use these temporary security credentials to sign calls to MinIO API operations.
|
Calling AssumeRoleWithWebIdentity does not require the use of MinIO default credentials. Therefore, you can distribute an application (for example, on mobile devices) that requests temporary security credentials without including MinIO default credentials in the application. Instead, the identity of the caller is validated by using a JWT id_token from the web identity provider. The temporary security credentials returned by this API consists of an access key, a secret key, and a security token. Applications can use these temporary security credentials to sign calls to MinIO API operations.
|
||||||
|
|
||||||
By default, the temporary security credentials created by AssumeRoleWithWebIdentity last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 7 days.
|
By default, the temporary security credentials created by AssumeRoleWithWebIdentity last for one hour. However, use the optional DurationSeconds parameter to specify the duration of the credentials. This value varies from 900 seconds (15 minutes) up to the maximum session duration of 365 days.
|
||||||
|
|
||||||
## API Request Parameters
|
## API Request Parameters
|
||||||
### WebIdentityToken
|
### WebIdentityToken
|
||||||
The OAuth 2.0 access token that is provided by the web identity provider. Application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.
|
The OAuth 2.0 id_token that is provided by the web identity provider. Application must get this token by authenticating the user who is using your application with a web identity provider before the application makes an AssumeRoleWithWebIdentity call.
|
||||||
|
|
||||||
| Params | Value |
|
| Params | Value |
|
||||||
| :-- | :-- |
|
| :-- | :-- |
|
||||||
@@ -25,13 +25,13 @@ Indicates STS API version information, the only supported value is '2011-06-15'.
|
|||||||
| *Required* | *Yes* |
|
| *Required* | *Yes* |
|
||||||
|
|
||||||
### DurationSeconds
|
### DurationSeconds
|
||||||
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 7 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds. If no *DurationSeconds* is specified expiry seconds is obtained from *WebIdentityToken*.
|
The duration, in seconds. The value can range from 900 seconds (15 minutes) up to 365 days. If value is higher than this setting, then operation fails. By default, the value is set to 3600 seconds. If no *DurationSeconds* is specified expiry seconds is obtained from *WebIdentityToken*.
|
||||||
|
|
||||||
| Params | Value |
|
| Params | Value |
|
||||||
| :-- | :-- |
|
| :-- | :-- |
|
||||||
| *Type* | *Integer* |
|
| *Type* | *Integer* |
|
||||||
| *Valid Range* | *Minimum value of 900. Maximum value of 604800.* |
|
| *Valid Range* | *Minimum value of 900. Maximum value of 31536000.* |
|
||||||
| *Required* | *No* |
|
| *Required* | *No* |
|
||||||
|
|
||||||
### Policy
|
### Policy
|
||||||
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
An IAM policy in JSON format that you want to use as an inline session policy. This parameter is optional. Passing policies to this operation returns new temporary credentials. The resulting session's permissions are the intersection of the canned policy name and the policy set here. You cannot use this policy to grant more permissions than those allowed by the canned policy name being assumed.
|
||||||
@@ -120,8 +120,8 @@ $ go run web-identity.go -cid 204367807228-ok7601k6gj1pgge7m09h7d79co8p35xx.apps
|
|||||||
|
|
||||||
- Visit http://localhost:8080, login will direct the user to the Google OAuth2 Auth URL to obtain a permission grant.
|
- Visit http://localhost:8080, login will direct the user to the Google OAuth2 Auth URL to obtain a permission grant.
|
||||||
- The redirection URI (callback handler) receives the OAuth2 callback, verifies the state parameter, and obtains a Token.
|
- The redirection URI (callback handler) receives the OAuth2 callback, verifies the state parameter, and obtains a Token.
|
||||||
- Using the access token the callback handler further talks to Google OAuth2 Token URL to obtain an JWT id_token.
|
- Using the id_token the callback handler further talks to Google OAuth2 Token URL to obtain an JWT id_token.
|
||||||
- Once obtained the JWT id_token is further sent to STS endpoint i.e MinIO to retrive temporary credentials.
|
- Once obtained the JWT id_token is further sent to STS endpoint i.e MinIO to retrieve temporary credentials.
|
||||||
- Temporary credentials are displayed on the browser upon successful retrieval.
|
- Temporary credentials are displayed on the browser upon successful retrieval.
|
||||||
|
|
||||||
## Using MinIO Console
|
## Using MinIO Console
|
||||||
|
|||||||
@@ -64,19 +64,20 @@ def callback():
|
|||||||
|
|
||||||
data = {'grant_type': 'authorization_code',
|
data = {'grant_type': 'authorization_code',
|
||||||
'code': authorization_code, 'redirect_uri': callback_uri}
|
'code': authorization_code, 'redirect_uri': callback_uri}
|
||||||
access_token_response = requests.post(
|
id_token_response = requests.post(
|
||||||
token_url, data=data, verify=False, allow_redirects=False, auth=(client_id, client_secret))
|
token_url, data=data, verify=False,
|
||||||
|
allow_redirects=False, auth=(client_id, client_secret))
|
||||||
|
|
||||||
print('body: ' + access_token_response.text)
|
print('body: ' + id_token_response.text)
|
||||||
|
|
||||||
# we can now use the access_token as much as we want to access protected resources.
|
# we can now use the id_token as much as we want to access protected resources.
|
||||||
tokens = json.loads(access_token_response.text)
|
tokens = json.loads(id_token_response.text)
|
||||||
access_token = tokens['access_token']
|
id_token = tokens['id_token']
|
||||||
|
|
||||||
response = sts_client.assume_role_with_web_identity(
|
response = sts_client.assume_role_with_web_identity(
|
||||||
RoleArn='arn:aws:iam::123456789012:user/svc-internal-api',
|
RoleArn='arn:aws:iam::123456789012:user/svc-internal-api',
|
||||||
RoleSessionName='test',
|
RoleSessionName='test',
|
||||||
WebIdentityToken=access_token,
|
WebIdentityToken=id_token,
|
||||||
DurationSeconds=3600
|
DurationSeconds=3600
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+18
-18
@@ -7,15 +7,15 @@ WSO2 is an Identity Server open source and is released under Apache Software Lic
|
|||||||
- Download WSO2 follow their [installation guide](https://docs.wso2.com/display/IS540/Installation+Guide).
|
- Download WSO2 follow their [installation guide](https://docs.wso2.com/display/IS540/Installation+Guide).
|
||||||
|
|
||||||
### 2. Configure WSO2
|
### 2. Configure WSO2
|
||||||
Once WSO2 is up and running, configure WSO2 to generate Self contained access tokens. In OAuth 2.0 specification there are primarily two ways to provide access tokens
|
Once WSO2 is up and running, configure WSO2 to generate Self contained id_tokens. In OAuth 2.0 specification there are primarily two ways to provide id_tokens
|
||||||
|
|
||||||
1. The access token is an identifier that is hard to guess. For example, a randomly generated string of sufficient length, that the server handling the protected resource can use to lookup the associated authorization information.
|
1. The id_token is an identifier that is hard to guess. For example, a randomly generated string of sufficient length, that the server handling the protected resource can use to lookup the associated authorization information.
|
||||||
2. The access token self-contains the authorization information in a manner that can be verified. For example, by encoding authorization information along with a signature into the token.
|
2. The id_token self-contains the authorization information in a manner that can be verified. For example, by encoding authorization information along with a signature into the token.
|
||||||
|
|
||||||
WSO2 generates tokens in first style by default, but if to be used with MinIO we should configure WSO2 to provide JWT tokens instead.
|
WSO2 generates tokens in first style by default, but if to be used with MinIO we should configure WSO2 to provide JWT tokens instead.
|
||||||
|
|
||||||
### 3. Generate Self-contained Access Tokens
|
### 3. Generate Self-contained Access Tokens
|
||||||
By default, a UUID is issued as an access token in WSO2 Identity Server, which is of the first type above. But, it also can be configured to issue a self-contained access token (JWT), which is of the second type above.
|
By default, a UUID is issued as an id_token in WSO2 Identity Server, which is of the first type above. But, it also can be configured to issue a self-contained id_token (JWT), which is of the second type above.
|
||||||
|
|
||||||
- Open the `<IS_HOME>/repository/conf/identity/identity.xml` file and uncomment the following entry under `<OAuth>` element.
|
- Open the `<IS_HOME>/repository/conf/identity/identity.xml` file and uncomment the following entry under `<OAuth>` element.
|
||||||
```
|
```
|
||||||
@@ -23,7 +23,7 @@ By default, a UUID is issued as an access token in WSO2 Identity Server, which i
|
|||||||
```
|
```
|
||||||
- Restart the server.
|
- Restart the server.
|
||||||
- Configure an [OAuth service provider](https://docs.wso2.com/display/IS540/Adding+and+Configuring+a+Service+Provider).
|
- Configure an [OAuth service provider](https://docs.wso2.com/display/IS540/Adding+and+Configuring+a+Service+Provider).
|
||||||
- Initiate an access token request to the WSO2 Identity Server, over a known [grant type](https://docs.wso2.com/display/IS540/OAuth+2.0+Grant+Types). For example, the following cURL command illustrates the syntax of an access token request that can be initiated over the [Client Credentials Grant](https://docs.wso2.com/display/IS540/Client+Credentials+Grant) grant type.
|
- Initiate an id_token request to the WSO2 Identity Server, over a known [grant type](https://docs.wso2.com/display/IS540/OAuth+2.0+Grant+Types). For example, the following cURL command illustrates the syntax of an id_token request that can be initiated over the [Client Credentials Grant](https://docs.wso2.com/display/IS540/Client+Credentials+Grant) grant type.
|
||||||
- Navigate to service provider section, expand Inbound Authentication Configurations and expand OAuth/OpenID Connect Configuration.
|
- Navigate to service provider section, expand Inbound Authentication Configurations and expand OAuth/OpenID Connect Configuration.
|
||||||
- Copy the OAuth Client Key as the value for `<CLIENT_ID>`.
|
- Copy the OAuth Client Key as the value for `<CLIENT_ID>`.
|
||||||
- Copy the OAuth Client Secret as the value for `<CLIENT_SECRET>`.
|
- Copy the OAuth Client Secret as the value for `<CLIENT_SECRET>`.
|
||||||
@@ -40,29 +40,29 @@ Example:
|
|||||||
curl -u PoEgXP6uVO45IsENRngDXj5Au5Ya:eKsw6z8CtOJVBtrOWvhRWL4TUCga -k -d "grant_type=client_credentials" -H "Content-Type:application/x-www-form-urlencoded" https://localhost:9443/oauth2/token
|
curl -u PoEgXP6uVO45IsENRngDXj5Au5Ya:eKsw6z8CtOJVBtrOWvhRWL4TUCga -k -d "grant_type=client_credentials" -H "Content-Type:application/x-www-form-urlencoded" https://localhost:9443/oauth2/token
|
||||||
```
|
```
|
||||||
|
|
||||||
In response, the self-contained JWT access token will be returned as shown below.
|
In response, the self-contained JWT id_token will be returned as shown below.
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
"access_token": "eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTUzNDg5MTc3OCwiaWF0IjoxNTM0ODg4MTc4LCJqdGkiOiIxODQ0MzI5Yy1kNjVhLTQ4YTMtODIyOC05ZGY3M2ZlODNkNTYifQ.ELZ8ujk2Xp9xTGgMqnCa5ehuimaAPXWlSCW5QeBbTJIT4M5OB_2XEVIV6p89kftjUdKu50oiYe4SbfrxmLm6NGSGd2qxkjzJK3SRKqsrmVWEn19juj8fz1neKtUdXVHuSZu6ws_bMDy4f_9hN2Jv9dFnkoyeNT54r4jSTJ4A2FzN2rkiURheVVsc8qlm8O7g64Az-5h4UGryyXU4zsnjDCBKYk9jdbEpcUskrFMYhuUlj1RWSASiGhHHHDU5dTRqHkVLIItfG48k_fb-ehU60T7EFWH1JBdNjOxM9oN_yb0hGwOjLUyCUJO_Y7xcd5F4dZzrBg8LffFmvJ09wzHNtQ",
|
"id_token": "eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJQb0VnWFA2dVZPNDVJc0VOUm5nRFhqNUF1NVlhIiwiYXpwIjoiUG9FZ1hQNnVWTzQ1SXNFTlJuZ0RYajVBdTVZYSIsImlzcyI6Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTUzNDg5MTc3OCwiaWF0IjoxNTM0ODg4MTc4LCJqdGkiOiIxODQ0MzI5Yy1kNjVhLTQ4YTMtODIyOC05ZGY3M2ZlODNkNTYifQ.ELZ8ujk2Xp9xTGgMqnCa5ehuimaAPXWlSCW5QeBbTJIT4M5OB_2XEVIV6p89kftjUdKu50oiYe4SbfrxmLm6NGSGd2qxkjzJK3SRKqsrmVWEn19juj8fz1neKtUdXVHuSZu6ws_bMDy4f_9hN2Jv9dFnkoyeNT54r4jSTJ4A2FzN2rkiURheVVsc8qlm8O7g64Az-5h4UGryyXU4zsnjDCBKYk9jdbEpcUskrFMYhuUlj1RWSASiGhHHHDU5dTRqHkVLIItfG48k_fb-ehU60T7EFWH1JBdNjOxM9oN_yb0hGwOjLUyCUJO_Y7xcd5F4dZzrBg8LffFmvJ09wzHNtQ",
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
"expires_in": 3600
|
"expires_in": 3600
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. JWT Claims
|
### 4. JWT Claims
|
||||||
The access token received is a signed JSON Web Token (JWT). Use a JWT decoder to decode the access token to access the payload of the token that includes following JWT claims:
|
The id_token received is a signed JSON Web Token (JWT). Use a JWT decoder to decode the id_token to access the payload of the token that includes following JWT claims:
|
||||||
|
|
||||||
|Claim Name|Type|Claim Value|
|
| Claim Name | Type | Claim Value |
|
||||||
|:--:|:--:|:--:|
|
|:----------:|:--------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
|
||||||
|iss| _string_ | The issuer of the JWT. The '> Identity Provider Entity Id ' value of the OAuth2/OpenID Connect Inbound Authentication configuration of the Resident Identity Provider is returned here. |
|
| iss | _string_ | The issuer of the JWT. The '> Identity Provider Entity Id ' value of the OAuth2/OpenID Connect Inbound Authentication configuration of the Resident Identity Provider is returned here. |
|
||||||
|aud| _string array_ | The token audience list. The client identifier of the OAuth clients that the JWT is intended for, is sent herewith. |
|
| aud | _string array_ | The token audience list. The client identifier of the OAuth clients that the JWT is intended for, is sent herewith. |
|
||||||
|azp| _string_ | The authorized party for which the token is issued to. The client identifier of the OAuth client that the token is issued for, is sent herewith. |
|
| azp | _string_ | The authorized party for which the token is issued to. The client identifier of the OAuth client that the token is issued for, is sent herewith. |
|
||||||
|iat| _integer_ | The token issue time. |
|
| iat | _integer_ | The token issue time. |
|
||||||
|exp| _integer_ | The token expiration time. |
|
| exp | _integer_ | The token expiration time. |
|
||||||
|jti| _string_ | Unique identifier for the JWT token. |
|
| jti | _string_ | Unique identifier for the JWT token. |
|
||||||
|policy| _string_ | Canned policy name to be applied for STS credentials. (Recommended) |
|
| policy | _string_ | Canned policy name to be applied for STS credentials. (Recommended) |
|
||||||
|
|
||||||
Using the above `access_token` we can perform an STS request to MinIO to get temporary credentials for MinIO API operations. MinIO STS API uses [JSON Web Key Set Endpoint](https://docs.wso2.com/display/IS541/JSON+Web+Key+Set+Endpoint) to validate if JWT is valid and is properly signed.
|
Using the above `id_token` we can perform an STS request to MinIO to get temporary credentials for MinIO API operations. MinIO STS API uses [JSON Web Key Set Endpoint](https://docs.wso2.com/display/IS541/JSON+Web+Key+Set+Endpoint) to validate if JWT is valid and is properly signed.
|
||||||
|
|
||||||
**We recommend setting `policy` as a custom claim for the JWT service provider follow [here](https://docs.wso2.com/display/IS550/Configuring+Claims+for+a+Service+Provider) and [here](https://docs.wso2.com/display/IS550/Handling+Custom+Claims+with+the+JWT+Bearer+Grant+Type) for relevant docs on how to configure claims for a service provider.**
|
**We recommend setting `policy` as a custom claim for the JWT service provider follow [here](https://docs.wso2.com/display/IS550/Configuring+Claims+for+a+Service+Provider) and [here](https://docs.wso2.com/display/IS550/Handling+Custom+Claims+with+the+JWT+Bearer+Grant+Type) for relevant docs on how to configure claims for a service provider.**
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ This guide explains how to configure MinIO Server with TLS certificates on Linux
|
|||||||
|
|
||||||
## <a name="install-minio-server"></a>1. Install MinIO Server
|
## <a name="install-minio-server"></a>1. Install MinIO Server
|
||||||
|
|
||||||
Install MinIO Server using the instructions in the [MinIO Quickstart Guide](http://docs.min.io/docs/minio-quickstart-guide).
|
Install MinIO Server using the instructions in the [MinIO Quickstart Guide](https://docs.min.io/docs/minio-quickstart-guide).
|
||||||
|
|
||||||
## <a name="use-an-existing-key-and-certificate-with-minio"></a>2. Use an Existing Key and Certificate with MinIO
|
## <a name="use-an-existing-key-and-certificate-with-minio"></a>2. Use an Existing Key and Certificate with MinIO
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.0
|
github.com/gorilla/mux v1.8.0
|
||||||
github.com/jcmturner/gokrb5/v8 v8.4.2
|
github.com/jcmturner/gokrb5/v8 v8.4.2
|
||||||
github.com/json-iterator/go v1.1.11
|
github.com/json-iterator/go v1.1.11
|
||||||
github.com/klauspost/compress v1.12.2
|
github.com/klauspost/compress v1.13.1
|
||||||
github.com/klauspost/cpuid/v2 v2.0.4
|
github.com/klauspost/cpuid/v2 v2.0.4
|
||||||
github.com/klauspost/pgzip v1.2.5
|
github.com/klauspost/pgzip v1.2.5
|
||||||
github.com/klauspost/readahead v1.3.1
|
github.com/klauspost/readahead v1.3.1
|
||||||
@@ -41,15 +41,14 @@ require (
|
|||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/miekg/dns v1.1.35
|
github.com/miekg/dns v1.1.35
|
||||||
github.com/minio/cli v1.22.0
|
github.com/minio/cli v1.22.0
|
||||||
github.com/minio/console v0.7.6-0.20210710191111-445c0be5b1cf
|
github.com/minio/console v0.8.1
|
||||||
github.com/minio/csvparser v1.0.0
|
github.com/minio/csvparser v1.0.0
|
||||||
github.com/minio/highwayhash v1.0.2
|
github.com/minio/highwayhash v1.0.2
|
||||||
github.com/minio/kes v0.14.0
|
github.com/minio/kes v0.14.0
|
||||||
github.com/minio/madmin-go v1.0.13
|
github.com/minio/madmin-go v1.0.20
|
||||||
github.com/minio/minio-go/v7 v7.0.13-0.20210706013812-337aa536abe2
|
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e
|
||||||
github.com/minio/parquet-go v1.0.0
|
github.com/minio/parquet-go v1.0.0
|
||||||
github.com/minio/pkg v1.0.10
|
github.com/minio/pkg v1.0.11
|
||||||
github.com/minio/rpc v1.0.0
|
|
||||||
github.com/minio/selfupdate v0.3.1
|
github.com/minio/selfupdate v0.3.1
|
||||||
github.com/minio/sha256-simd v1.0.0
|
github.com/minio/sha256-simd v1.0.0
|
||||||
github.com/minio/simdjson-go v0.2.1
|
github.com/minio/simdjson-go v0.2.1
|
||||||
@@ -57,10 +56,9 @@ require (
|
|||||||
github.com/minio/zipindex v0.2.0
|
github.com/minio/zipindex v0.2.0
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
github.com/montanaflynn/stats v0.6.6
|
github.com/montanaflynn/stats v0.6.6
|
||||||
github.com/nats-io/nats-server/v2 v2.1.9
|
github.com/nats-io/nats-server/v2 v2.3.2
|
||||||
github.com/nats-io/nats-streaming-server v0.21.2 // indirect
|
github.com/nats-io/nats-streaming-server v0.21.2 // indirect
|
||||||
github.com/nats-io/nats.go v1.10.0
|
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30
|
||||||
github.com/nats-io/nkeys v0.2.0 // indirect
|
|
||||||
github.com/nats-io/stan.go v0.8.3
|
github.com/nats-io/stan.go v0.8.3
|
||||||
github.com/ncw/directio v1.0.5
|
github.com/ncw/directio v1.0.5
|
||||||
github.com/nsqio/go-nsq v1.0.8
|
github.com/nsqio/go-nsq v1.0.8
|
||||||
@@ -82,12 +80,10 @@ require (
|
|||||||
go.etcd.io/etcd/client/v3 v3.5.0-beta.4
|
go.etcd.io/etcd/client/v3 v3.5.0-beta.4
|
||||||
go.uber.org/atomic v1.7.0
|
go.uber.org/atomic v1.7.0
|
||||||
go.uber.org/zap v1.16.1-0.20210329175301-c23abee72d19
|
go.uber.org/zap v1.16.1-0.20210329175301-c23abee72d19
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
|
||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba
|
||||||
golang.org/x/tools v0.1.1 // indirect
|
golang.org/x/tools v0.1.1 // indirect
|
||||||
google.golang.org/api v0.31.0
|
google.golang.org/api v0.31.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace google.golang.org/grpc => google.golang.org/grpc v1.29.1
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8=
|
||||||
|
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.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.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
|
cloud.google.com/go v0.39.0/go.mod h1:rVLT6fkc8chs9sfPtFc1SBH6em7n+ZoXaG+87tDISts=
|
||||||
@@ -227,6 +228,7 @@ github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp
|
|||||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
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-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.0.3 h1:ZA346ACHIZctef6trOTwBAEvPVm1k0uLm/bb2Atc+S8=
|
github.com/cockroachdb/cockroach-go/v2 v2.0.3 h1:ZA346ACHIZctef6trOTwBAEvPVm1k0uLm/bb2Atc+S8=
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.0.3/go.mod h1:hAuDgiVgDVkfirP9JnhXEfcXEPRKBpYdGz+l7mvYSzw=
|
github.com/cockroachdb/cockroach-go/v2 v2.0.3/go.mod h1:hAuDgiVgDVkfirP9JnhXEfcXEPRKBpYdGz+l7mvYSzw=
|
||||||
@@ -318,12 +320,15 @@ github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkg
|
|||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
|
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
|
||||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
|
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||||
|
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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
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/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
|
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
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.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
@@ -526,6 +531,7 @@ github.com/goccy/go-json v0.4.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF
|
|||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||||
@@ -873,8 +879,10 @@ github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0
|
|||||||
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8=
|
github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
|
github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
|
||||||
|
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
@@ -933,6 +941,7 @@ github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||||
|
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
@@ -998,14 +1007,15 @@ github.com/minio/cli v1.22.0 h1:VTQm7lmXm3quxO917X3p+el1l0Ca5X3S4PM2ruUYO68=
|
|||||||
github.com/minio/cli v1.22.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
github.com/minio/cli v1.22.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||||
github.com/minio/colorjson v1.0.1 h1:+hvfP8C1iMB95AT+ZFDRE+Knn9QPd9lg0CRJY9DRpos=
|
github.com/minio/colorjson v1.0.1 h1:+hvfP8C1iMB95AT+ZFDRE+Knn9QPd9lg0CRJY9DRpos=
|
||||||
github.com/minio/colorjson v1.0.1/go.mod h1:oPM3zQQY8Gz9NGtgvuBEjQ+gPZLKAGc7T+kjMlwtOgs=
|
github.com/minio/colorjson v1.0.1/go.mod h1:oPM3zQQY8Gz9NGtgvuBEjQ+gPZLKAGc7T+kjMlwtOgs=
|
||||||
github.com/minio/console v0.7.6-0.20210710191111-445c0be5b1cf h1:krzhMxuTMvuZxJQZYIS3u9aIIMSADxu7/AZB4YIfRmM=
|
github.com/minio/console v0.8.1 h1:Uu/WhKLfAyeqU9C+Wgy0DVugbbtDMlKhNWfzOzulb/I=
|
||||||
github.com/minio/console v0.7.6-0.20210710191111-445c0be5b1cf/go.mod h1:PrnkbZbBzQzWaBDspDt8oTL7KMdNVQROnltJyNrBuGg=
|
github.com/minio/console v0.8.1/go.mod h1:HtWrypKxQjhkF4sAgkPpmzEIjwsBv1fw62uU8DxN9iE=
|
||||||
github.com/minio/csvparser v1.0.0 h1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=
|
github.com/minio/csvparser v1.0.0 h1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=
|
||||||
github.com/minio/csvparser v1.0.0/go.mod h1:lKXskSLzPgC5WQyzP7maKH7Sl1cqvANXo9YCto8zbtM=
|
github.com/minio/csvparser v1.0.0/go.mod h1:lKXskSLzPgC5WQyzP7maKH7Sl1cqvANXo9YCto8zbtM=
|
||||||
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf h1:wylCc/PdvdTIqYqVNEU9LJAZBanvfGY1TwTnjM3zQaA=
|
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf h1:wylCc/PdvdTIqYqVNEU9LJAZBanvfGY1TwTnjM3zQaA=
|
||||||
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf/go.mod h1:ryRFbKRDMZ1+kGF5EZwuyfF1a/eAu4c9uxZdF0fnmuY=
|
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf/go.mod h1:ryRFbKRDMZ1+kGF5EZwuyfF1a/eAu4c9uxZdF0fnmuY=
|
||||||
github.com/minio/filepath v1.0.0 h1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=
|
github.com/minio/filepath v1.0.0 h1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=
|
||||||
github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEXx9T/Bw=
|
github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEXx9T/Bw=
|
||||||
|
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||||
github.com/minio/kes v0.11.0/go.mod h1:mTF1Bv8YVEtQqF/B7Felp4tLee44Pp+dgI0rhCvgNg8=
|
github.com/minio/kes v0.11.0/go.mod h1:mTF1Bv8YVEtQqF/B7Felp4tLee44Pp+dgI0rhCvgNg8=
|
||||||
@@ -1013,8 +1023,9 @@ github.com/minio/kes v0.14.0 h1:plCGm4LwR++T1P1sXsJbyFRX54CE1WRuo9PAPj6MC3Q=
|
|||||||
github.com/minio/kes v0.14.0/go.mod h1:OUensXz2BpgMfiogslKxv7Anyx/wj+6bFC6qA7BQcfA=
|
github.com/minio/kes v0.14.0/go.mod h1:OUensXz2BpgMfiogslKxv7Anyx/wj+6bFC6qA7BQcfA=
|
||||||
github.com/minio/madmin-go v1.0.6/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
|
github.com/minio/madmin-go v1.0.6/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
|
||||||
github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
|
github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
|
||||||
github.com/minio/madmin-go v1.0.13 h1:g0KIqo9zCt6wEG6koWZr7tlWFFozIXW2NRCQx9EsCrw=
|
github.com/minio/madmin-go v1.0.17/go.mod h1:4nl9hvLWFnwCjkLfZSsZXEHgDODa2XSG6xGlIZyQ2oA=
|
||||||
github.com/minio/madmin-go v1.0.13/go.mod h1:4nl9hvLWFnwCjkLfZSsZXEHgDODa2XSG6xGlIZyQ2oA=
|
github.com/minio/madmin-go v1.0.20 h1:gb3zY97oUuBuw6BTxmn2THhiEXEgSRmSVqhhHZq3Pc0=
|
||||||
|
github.com/minio/madmin-go v1.0.20/go.mod h1:4nl9hvLWFnwCjkLfZSsZXEHgDODa2XSG6xGlIZyQ2oA=
|
||||||
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f h1:hyFvo5hSFw2K417YvDr/vAKlgCG69uTuhZW/5LNdL0U=
|
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f h1:hyFvo5hSFw2K417YvDr/vAKlgCG69uTuhZW/5LNdL0U=
|
||||||
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f/go.mod h1:tuaonkPjVApCXkbtKENHBtsqUf7YTV33qmFrC+Pgp5g=
|
github.com/minio/mc v0.0.0-20210626002108-cebf3318546f/go.mod h1:tuaonkPjVApCXkbtKENHBtsqUf7YTV33qmFrC+Pgp5g=
|
||||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||||
@@ -1023,9 +1034,8 @@ github.com/minio/md5-simd v1.1.1/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77Z
|
|||||||
github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
|
github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
|
||||||
github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
|
github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
|
||||||
github.com/minio/minio-go/v7 v7.0.11-0.20210607181445-e162fdb8e584/go.mod h1:WoyW+ySKAKjY98B9+7ZbI8z8S3jaxaisdcvj9TGlazA=
|
github.com/minio/minio-go/v7 v7.0.11-0.20210607181445-e162fdb8e584/go.mod h1:WoyW+ySKAKjY98B9+7ZbI8z8S3jaxaisdcvj9TGlazA=
|
||||||
github.com/minio/minio-go/v7 v7.0.12-0.20210617160455-b7103728fb87/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
|
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e h1:aVnxKPpUI1gVeEf9vC+QEt8OxMXiiNMeUWcrBM62oDU=
|
||||||
github.com/minio/minio-go/v7 v7.0.13-0.20210706013812-337aa536abe2 h1:Mleb9L1AnLrlHzrvb2eKjRflzLb5P3q+nS3wS4tci/Q=
|
github.com/minio/minio-go/v7 v7.0.13-0.20210715203016-9e713532886e/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
|
||||||
github.com/minio/minio-go/v7 v7.0.13-0.20210706013812-337aa536abe2/go.mod h1:S23iSP5/gbMwtxeY5FM71R+TkAYyzEdoNEDDwpt8yWs=
|
|
||||||
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae h1:GONmqbjCi/KTEc1CGujnS/m1qeJeghcQ8dUBLh19qQo=
|
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae h1:GONmqbjCi/KTEc1CGujnS/m1qeJeghcQ8dUBLh19qQo=
|
||||||
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae/go.mod h1:8/mIXK+CFdL6VqyxRn1SwD+PEX0jsN8uqjoadaw/Np0=
|
github.com/minio/operator v0.0.0-20210616045941-65f31f5f78ae/go.mod h1:8/mIXK+CFdL6VqyxRn1SwD+PEX0jsN8uqjoadaw/Np0=
|
||||||
github.com/minio/operator/logsearchapi v0.0.0-20210604224119-7e256f98cf90 h1:Qu6j6oE7+QNuq7Kr2DLyVYq3fqMdqFd/T8NAeNp47og=
|
github.com/minio/operator/logsearchapi v0.0.0-20210604224119-7e256f98cf90 h1:Qu6j6oE7+QNuq7Kr2DLyVYq3fqMdqFd/T8NAeNp47og=
|
||||||
@@ -1035,10 +1045,8 @@ github.com/minio/parquet-go v1.0.0/go.mod h1:aQlkSOfOq2AtQKkuou3mosNVMwNokd+faTa
|
|||||||
github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
|
github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
|
||||||
github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
|
github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
|
||||||
github.com/minio/pkg v1.0.8/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
github.com/minio/pkg v1.0.8/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
||||||
github.com/minio/pkg v1.0.10 h1:fohpAm/0ttQFf4BzmzH5r6A9JUIfg63AyGCPM0f9/9U=
|
github.com/minio/pkg v1.0.11 h1:gfpkP7SiznM7EyyHIfQ7lu98Ae4hV4Z+8YsoFQbH7PY=
|
||||||
github.com/minio/pkg v1.0.10/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
github.com/minio/pkg v1.0.11/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
||||||
github.com/minio/rpc v1.0.0 h1:tJCHyLfQF6k6HlMQFpKy2FO/7lc2WP8gLDGMZp18E70=
|
|
||||||
github.com/minio/rpc v1.0.0/go.mod h1:b9xqF7J0xeMXr0cM4pnBlP7Te7PDsG5JrRxl5dG6Ldk=
|
|
||||||
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
|
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
|
||||||
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
||||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||||
@@ -1097,21 +1105,27 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+
|
|||||||
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
|
github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c=
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||||
github.com/nats-io/jwt v1.1.0 h1:+vOlgtM0ZsF46GbmUoadq0/2rChNS45gtxHEa3H1gqM=
|
|
||||||
github.com/nats-io/jwt v1.1.0/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M=
|
github.com/nats-io/jwt v1.1.0/go.mod h1:n3cvmLfBfnpV4JJRN7lRYCyZnw48ksGsbThGXEk4w9M=
|
||||||
|
github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU=
|
||||||
|
github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
|
||||||
|
github.com/nats-io/jwt/v2 v2.0.2 h1:ejVCLO8gu6/4bOKIHQpmB5UhhUJfAQw55yvLWpfmKjI=
|
||||||
|
github.com/nats-io/jwt/v2 v2.0.2/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
|
||||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||||
github.com/nats-io/nats-server/v2 v2.1.9 h1:Sxr2zpaapgpBT9ElTxTVe62W+qjnhPcKY/8W5cnA/Qk=
|
|
||||||
github.com/nats-io/nats-server/v2 v2.1.9/go.mod h1:9qVyoewoYXzG1ME9ox0HwkkzyYvnlBDugfR4Gg/8uHU=
|
github.com/nats-io/nats-server/v2 v2.1.9/go.mod h1:9qVyoewoYXzG1ME9ox0HwkkzyYvnlBDugfR4Gg/8uHU=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.3.2 h1:SGJLWrjBHsl0DsdY8PeTR3YKEfiUEYVVq2STw9d8MSY=
|
||||||
|
github.com/nats-io/nats-server/v2 v2.3.2/go.mod h1:dUf7Cm5z5LbciFVwWx54owyCKm8x4/hL6p7rrljhLFY=
|
||||||
github.com/nats-io/nats-streaming-server v0.21.2 h1:chyaVdWlPdBcSbLq3cpyCYcuXA+7bVXJmM4yWrdqL/8=
|
github.com/nats-io/nats-streaming-server v0.21.2 h1:chyaVdWlPdBcSbLq3cpyCYcuXA+7bVXJmM4yWrdqL/8=
|
||||||
github.com/nats-io/nats-streaming-server v0.21.2/go.mod h1:2W8QfNVOtcFpmf0bRiwuLtRb0/hkX4NuOxPOFNOThVQ=
|
github.com/nats-io/nats-streaming-server v0.21.2/go.mod h1:2W8QfNVOtcFpmf0bRiwuLtRb0/hkX4NuOxPOFNOThVQ=
|
||||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||||
github.com/nats-io/nats.go v1.10.0 h1:L8qnKaofSfNFbXg0C5F71LdjPRnmQwSsA4ukmkt1TvY=
|
|
||||||
github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE=
|
github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE=
|
||||||
|
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30 h1:9GqilBhZaR3xYis0JgMlJjNw933WIobdjKhilXm+Vls=
|
||||||
|
github.com/nats-io/nats.go v1.11.1-0.20210623165838-4b75fc59ae30/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
github.com/nats-io/nkeys v0.1.4/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
||||||
github.com/nats-io/nkeys v0.2.0 h1:WXKF7diOaPU9cJdLD7nuzwasQy9vT1tBqzXZZf3AMJM=
|
|
||||||
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
|
||||||
|
github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8=
|
||||||
|
github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
github.com/nats-io/stan.go v0.8.3 h1:XyemjL9vAeGHooHn5RQy+ngljd8AVSM2l65Jdnpv4rI=
|
github.com/nats-io/stan.go v0.8.3 h1:XyemjL9vAeGHooHn5RQy+ngljd8AVSM2l65Jdnpv4rI=
|
||||||
@@ -1526,8 +1540,10 @@ golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
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-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||||
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
@@ -1542,6 +1558,7 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
|
|||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
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-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/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-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-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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
@@ -1647,6 +1664,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -1726,8 +1744,9 @@ golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
|
||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
@@ -1754,11 +1773,13 @@ golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba h1:O8mE0/t419eoIwhTFpKVkHiT
|
|||||||
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/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-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
@@ -1929,8 +1950,29 @@ google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6D
|
|||||||
google.golang.org/genproto v0.0.0-20200901141002-b3bf27a9dbd1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20200901141002-b3bf27a9dbd1/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
|
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a h1:pOwg4OoaRYScjmR4LlLgdtnyoHYTSAVhhqe5uPdpII8=
|
||||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.23.1/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.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.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c=
|
||||||
|
google.golang.org/grpc v1.37.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-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-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 v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -2004,6 +2046,7 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C
|
|||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
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-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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -179,3 +179,11 @@ func (m *Monitor) SetBandwidthLimit(bucket string, limit int64) {
|
|||||||
t.Limiter = rate.NewLimiter(newlimit, int(t.NodeBandwidthPerSec))
|
t.Limiter = rate.NewLimiter(newlimit, int(t.NodeBandwidthPerSec))
|
||||||
m.bucketThrottle[bucket] = t
|
m.bucketThrottle[bucket] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsThrottled returns true if a bucket has bandwidth throttling enabled.
|
||||||
|
func (m *Monitor) IsThrottled(bucket string) bool {
|
||||||
|
m.tlock.RLock()
|
||||||
|
defer m.tlock.RUnlock()
|
||||||
|
_, ok := m.bucketThrottle[bucket]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ import (
|
|||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -438,25 +441,51 @@ func (lc Lifecycle) PredictTransitionTime(obj ObjectOpts) (string, time.Time) {
|
|||||||
return "", time.Time{}
|
return "", time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
var finalTransitionDate time.Time
|
|
||||||
var finalTransitionRuleID string
|
|
||||||
|
|
||||||
// Iterate over all actionable rules and find the earliest
|
// Iterate over all actionable rules and find the earliest
|
||||||
// transition date and its associated rule ID.
|
// transition date and its associated rule ID.
|
||||||
|
var finalTransitionDate time.Time
|
||||||
|
var finalTransitionRuleID string
|
||||||
for _, rule := range lc.FilterActionableRules(obj) {
|
for _, rule := range lc.FilterActionableRules(obj) {
|
||||||
switch {
|
if due, ok := rule.Transition.NextDue(obj); ok {
|
||||||
case !rule.Transition.IsDateNull():
|
if finalTransitionDate.IsZero() || finalTransitionDate.After(due) {
|
||||||
if finalTransitionDate.IsZero() || finalTransitionDate.After(rule.Transition.Date.Time) {
|
|
||||||
finalTransitionRuleID = rule.ID
|
finalTransitionRuleID = rule.ID
|
||||||
finalTransitionDate = rule.Transition.Date.Time
|
finalTransitionDate = due
|
||||||
}
|
}
|
||||||
case !rule.Transition.IsDaysNull():
|
}
|
||||||
expectedTransition := ExpectedExpiryTime(obj.ModTime, int(rule.Expiration.Days))
|
if due, ok := rule.NoncurrentVersionTransition.NextDue(obj); ok {
|
||||||
if finalTransitionDate.IsZero() || finalTransitionDate.After(expectedTransition) {
|
if finalTransitionDate.IsZero() || finalTransitionDate.After(due) {
|
||||||
finalTransitionRuleID = rule.ID
|
finalTransitionRuleID = rule.ID
|
||||||
finalTransitionDate = expectedTransition
|
finalTransitionDate = due
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return finalTransitionRuleID, finalTransitionDate
|
return finalTransitionRuleID, finalTransitionDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetPredictionHeaders sets time to expiry and transition headers on w for a
|
||||||
|
// given obj.
|
||||||
|
func (lc Lifecycle) SetPredictionHeaders(w http.ResponseWriter, obj ObjectOpts) {
|
||||||
|
if ruleID, expiry := lc.PredictExpiryTime(obj); !expiry.IsZero() {
|
||||||
|
w.Header()[xhttp.AmzExpiration] = []string{
|
||||||
|
fmt.Sprintf(`expiry-date="%s", rule-id="%s"`, expiry.Format(http.TimeFormat), ruleID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ruleID, transition := lc.PredictTransitionTime(obj); !transition.IsZero() {
|
||||||
|
w.Header()[xhttp.MinIOTransition] = []string{
|
||||||
|
fmt.Sprintf(`transition-date="%s", rule-id="%s"`, transition.Format(http.TimeFormat), ruleID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransitionTier returns remote tier that applies to obj per ILM rules.
|
||||||
|
func (lc Lifecycle) TransitionTier(obj ObjectOpts) string {
|
||||||
|
for _, rule := range lc.FilterActionableRules(obj) {
|
||||||
|
if obj.IsLatest && rule.Transition.StorageClass != "" {
|
||||||
|
return rule.Transition.StorageClass
|
||||||
|
}
|
||||||
|
if !obj.IsLatest && rule.NoncurrentVersionTransition.StorageClass != "" {
|
||||||
|
return rule.NoncurrentVersionTransition.StorageClass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -21,8 +21,13 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
xhttp "github.com/minio/minio/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
func TestParseAndValidateLifecycleConfig(t *testing.T) {
|
||||||
@@ -429,3 +434,122 @@ func TestHasActiveRules(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetPredictionHeaders(t *testing.T) {
|
||||||
|
lc := Lifecycle{
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
ID: "rule-1",
|
||||||
|
Status: "Enabled",
|
||||||
|
Expiration: Expiration{
|
||||||
|
Days: ExpirationDays(3),
|
||||||
|
set: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "rule-2",
|
||||||
|
Status: "Enabled",
|
||||||
|
Transition: Transition{
|
||||||
|
Days: TransitionDays(3),
|
||||||
|
StorageClass: "TIER-1",
|
||||||
|
set: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "rule-3",
|
||||||
|
Status: "Enabled",
|
||||||
|
NoncurrentVersionTransition: NoncurrentVersionTransition{
|
||||||
|
NoncurrentDays: ExpirationDays(5),
|
||||||
|
StorageClass: "TIER-2",
|
||||||
|
set: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// current version
|
||||||
|
obj1 := ObjectOpts{
|
||||||
|
Name: "obj1",
|
||||||
|
IsLatest: true,
|
||||||
|
}
|
||||||
|
// non-current version
|
||||||
|
obj2 := ObjectOpts{
|
||||||
|
Name: "obj2",
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
obj ObjectOpts
|
||||||
|
expRuleID int
|
||||||
|
transRuleID int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
obj: obj1,
|
||||||
|
expRuleID: 0,
|
||||||
|
transRuleID: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
obj: obj2,
|
||||||
|
expRuleID: 0,
|
||||||
|
transRuleID: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tc := range tests {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
lc.SetPredictionHeaders(w, tc.obj)
|
||||||
|
if expHdrs, ok := w.Header()[xhttp.AmzExpiration]; ok && !strings.Contains(expHdrs[0], lc.Rules[tc.expRuleID].ID) {
|
||||||
|
t.Fatalf("Test %d: Expected %s header", i+1, xhttp.AmzExpiration)
|
||||||
|
}
|
||||||
|
if transHdrs, ok := w.Header()[xhttp.MinIOTransition]; ok {
|
||||||
|
if !strings.Contains(transHdrs[0], lc.Rules[tc.transRuleID].ID) {
|
||||||
|
t.Fatalf("Test %d: Expected %s header", i+1, xhttp.MinIOTransition)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.obj.IsLatest {
|
||||||
|
if expectedDue, _ := lc.Rules[tc.transRuleID].Transition.NextDue(tc.obj); !strings.Contains(transHdrs[0], expectedDue.Format(http.TimeFormat)) {
|
||||||
|
t.Fatalf("Test %d: Expected transition time %s", i+1, expectedDue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if expectedDue, _ := lc.Rules[tc.transRuleID].NoncurrentVersionTransition.NextDue(tc.obj); !strings.Contains(transHdrs[0], expectedDue.Format(http.TimeFormat)) {
|
||||||
|
t.Fatalf("Test %d: Expected transition time %s", i+1, expectedDue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransitionTier(t *testing.T) {
|
||||||
|
lc := Lifecycle{
|
||||||
|
Rules: []Rule{
|
||||||
|
{
|
||||||
|
ID: "rule-1",
|
||||||
|
Status: "Enabled",
|
||||||
|
Transition: Transition{
|
||||||
|
Days: TransitionDays(3),
|
||||||
|
StorageClass: "TIER-1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "rule-2",
|
||||||
|
Status: "Enabled",
|
||||||
|
NoncurrentVersionTransition: NoncurrentVersionTransition{
|
||||||
|
NoncurrentDays: ExpirationDays(3),
|
||||||
|
StorageClass: "TIER-2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
obj1 := ObjectOpts{
|
||||||
|
Name: "obj1",
|
||||||
|
IsLatest: true,
|
||||||
|
}
|
||||||
|
obj2 := ObjectOpts{
|
||||||
|
Name: "obj2",
|
||||||
|
}
|
||||||
|
if got := lc.TransitionTier(obj1); got != "TIER-1" {
|
||||||
|
t.Fatalf("Expected TIER-1 but got %s", got)
|
||||||
|
}
|
||||||
|
if got := lc.TransitionTier(obj2); got != "TIER-2" {
|
||||||
|
t.Fatalf("Expected TIER-2 but got %s", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package lifecycle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NoncurrentVersionExpiration - an action for lifecycle configuration rule.
|
// NoncurrentVersionExpiration - an action for lifecycle configuration rule.
|
||||||
@@ -112,3 +113,14 @@ func (n NoncurrentVersionTransition) Validate() error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NextDue returns upcoming NoncurrentVersionTransition date for obj if
|
||||||
|
// applicable, returns false otherwise.
|
||||||
|
func (n NoncurrentVersionTransition) NextDue(obj ObjectOpts) (time.Time, bool) {
|
||||||
|
switch {
|
||||||
|
case obj.IsLatest, n.IsDaysNull():
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpectedExpiryTime(obj.SuccessorModTime, int(n.NoncurrentDays)), true
|
||||||
|
}
|
||||||
|
|||||||
@@ -163,3 +163,20 @@ func (t Transition) IsDateNull() bool {
|
|||||||
func (t Transition) IsNull() bool {
|
func (t Transition) IsNull() bool {
|
||||||
return t.IsDaysNull() && t.IsDateNull()
|
return t.IsDaysNull() && t.IsDateNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NextDue returns upcoming transition date for obj and true if applicable,
|
||||||
|
// returns false otherwise.
|
||||||
|
func (t Transition) NextDue(obj ObjectOpts) (time.Time, bool) {
|
||||||
|
if !obj.IsLatest {
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !t.IsDateNull():
|
||||||
|
return t.Date.Time, true
|
||||||
|
case !t.IsDaysNull():
|
||||||
|
return ExpectedExpiryTime(obj.ModTime, int(t.Days)), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, false
|
||||||
|
}
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ const (
|
|||||||
CompressionSubSys = "compression"
|
CompressionSubSys = "compression"
|
||||||
LoggerWebhookSubSys = "logger_webhook"
|
LoggerWebhookSubSys = "logger_webhook"
|
||||||
AuditWebhookSubSys = "audit_webhook"
|
AuditWebhookSubSys = "audit_webhook"
|
||||||
|
AuditKafkaSubSys = "audit_kafka"
|
||||||
HealSubSys = "heal"
|
HealSubSys = "heal"
|
||||||
ScannerSubSys = "scanner"
|
ScannerSubSys = "scanner"
|
||||||
CrawlerSubSys = "crawler"
|
CrawlerSubSys = "crawler"
|
||||||
@@ -108,6 +109,7 @@ var SubSystems = set.CreateStringSet(
|
|||||||
CompressionSubSys,
|
CompressionSubSys,
|
||||||
LoggerWebhookSubSys,
|
LoggerWebhookSubSys,
|
||||||
AuditWebhookSubSys,
|
AuditWebhookSubSys,
|
||||||
|
AuditKafkaSubSys,
|
||||||
PolicyOPASubSys,
|
PolicyOPASubSys,
|
||||||
IdentityLDAPSubSys,
|
IdentityLDAPSubSys,
|
||||||
IdentityOpenIDSubSys,
|
IdentityOpenIDSubSys,
|
||||||
@@ -247,6 +249,23 @@ func (kvs KVS) String() string {
|
|||||||
return s.String()
|
return s.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge environment values with on disk KVS, environment values overrides
|
||||||
|
// anything on the disk.
|
||||||
|
func Merge(cfgKVS map[string]KVS, envname string, defaultKVS KVS) map[string]KVS {
|
||||||
|
newCfgKVS := make(map[string]KVS)
|
||||||
|
for _, e := range env.List(envname) {
|
||||||
|
tgt := strings.TrimPrefix(e, envname+Default)
|
||||||
|
if tgt == envname {
|
||||||
|
tgt = Default
|
||||||
|
}
|
||||||
|
newCfgKVS[tgt] = defaultKVS
|
||||||
|
}
|
||||||
|
for tgt, kv := range cfgKVS {
|
||||||
|
newCfgKVS[tgt] = kv
|
||||||
|
}
|
||||||
|
return newCfgKVS
|
||||||
|
}
|
||||||
|
|
||||||
// Set sets a value, if not sets a default value.
|
// Set sets a value, if not sets a default value.
|
||||||
func (kvs *KVS) Set(key, value string) {
|
func (kvs *KVS) Set(key, value string) {
|
||||||
for i, kv := range *kvs {
|
for i, kv := range *kvs {
|
||||||
@@ -355,9 +374,11 @@ func (c Config) RedactSensitiveInfo() Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nc[configName] = configVals
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the server credentials altogether
|
||||||
|
nc.DelKVS(CredentialsSubSys)
|
||||||
|
|
||||||
return nc
|
return nc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ const (
|
|||||||
EnvArgs = "MINIO_ARGS"
|
EnvArgs = "MINIO_ARGS"
|
||||||
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
|
EnvDNSWebhook = "MINIO_DNS_WEBHOOK_ENDPOINT"
|
||||||
|
|
||||||
|
EnvMinIOServerURL = "MINIO_SERVER_URL"
|
||||||
EnvMinIOBrowserRedirectURL = "MINIO_BROWSER_REDIRECT_URL"
|
EnvMinIOBrowserRedirectURL = "MINIO_BROWSER_REDIRECT_URL"
|
||||||
EnvRootDiskThresholdSize = "MINIO_ROOTDISK_THRESHOLD_SIZE"
|
EnvRootDiskThresholdSize = "MINIO_ROOTDISK_THRESHOLD_SIZE"
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ldap "github.com/go-ldap/ldap/v3"
|
ldap "github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/set"
|
||||||
|
"github.com/minio/minio/internal/auth"
|
||||||
"github.com/minio/minio/internal/config"
|
"github.com/minio/minio/internal/config"
|
||||||
|
"github.com/minio/minio/internal/logger"
|
||||||
"github.com/minio/pkg/env"
|
"github.com/minio/pkg/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -36,6 +40,9 @@ const (
|
|||||||
defaultLDAPExpiry = time.Hour * 1
|
defaultLDAPExpiry = time.Hour * 1
|
||||||
|
|
||||||
dnDelimiter = ";"
|
dnDelimiter = ";"
|
||||||
|
|
||||||
|
minLDAPExpiry time.Duration = 15 * time.Minute
|
||||||
|
maxLDAPExpiry time.Duration = 365 * 24 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config contains AD/LDAP server connectivity information.
|
// Config contains AD/LDAP server connectivity information.
|
||||||
@@ -290,7 +297,7 @@ func (l *Config) searchForUserGroups(conn *ldap.Conn, username, bindDN string) (
|
|||||||
return groups, nil
|
return groups, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupUserDN searches for the full DN ang groups of a given username
|
// LookupUserDN searches for the full DN and groups of a given username
|
||||||
func (l *Config) LookupUserDN(username string) (string, []string, error) {
|
func (l *Config) LookupUserDN(username string) (string, []string, error) {
|
||||||
if !l.isUsingLookupBind {
|
if !l.isUsingLookupBind {
|
||||||
return "", nil, errors.New("current lookup mode does not support searching for User DN")
|
return "", nil, errors.New("current lookup mode does not support searching for User DN")
|
||||||
@@ -416,8 +423,22 @@ func (l *Config) Connect() (ldapConn *ldap.Conn, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetExpiryDuration - return parsed expiry duration.
|
// GetExpiryDuration - return parsed expiry duration.
|
||||||
func (l Config) GetExpiryDuration() time.Duration {
|
func (l Config) GetExpiryDuration(dsecs string) (time.Duration, error) {
|
||||||
return l.stsExpiryDuration
|
if dsecs == "" {
|
||||||
|
return l.stsExpiryDuration, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := strconv.Atoi(dsecs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, auth.ErrInvalidDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
dur := time.Duration(d) * time.Second
|
||||||
|
|
||||||
|
if dur < minLDAPExpiry || dur > maxLDAPExpiry {
|
||||||
|
return 0, auth.ErrInvalidDuration
|
||||||
|
}
|
||||||
|
return dur, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Config) testConnection() error {
|
func (l Config) testConnection() error {
|
||||||
@@ -452,6 +473,93 @@ func (l Config) testConnection() error {
|
|||||||
return fmt.Errorf("LDAP connection test error: %w", err)
|
return fmt.Errorf("LDAP connection test error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLDAPUserDN determines if the given string could be a user DN from LDAP.
|
||||||
|
func (l Config) IsLDAPUserDN(user string) bool {
|
||||||
|
return strings.HasSuffix(user, ","+l.UserDNSearchBaseDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNonExistentUserDistNames - find user accounts (DNs) that are no longer
|
||||||
|
// present in the LDAP server.
|
||||||
|
func (l *Config) GetNonExistentUserDistNames(userDistNames []string) ([]string, error) {
|
||||||
|
if !l.isUsingLookupBind {
|
||||||
|
return nil, errors.New("current LDAP configuration does not permit looking for expired user accounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := l.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Bind to the lookup user account
|
||||||
|
if err = l.lookupBind(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonExistentUsers := []string{}
|
||||||
|
for _, dn := range userDistNames {
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
dn,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
"(objectclass=*)",
|
||||||
|
[]string{}, // only need DN, so no pass no attributes here
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
searchResult, err := conn.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
// Object does not exist error?
|
||||||
|
if ldap.IsErrorWithCode(err, 32) {
|
||||||
|
nonExistentUsers = append(nonExistentUsers, dn)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(searchResult.Entries) == 0 {
|
||||||
|
// DN was not found - this means this user account is
|
||||||
|
// expired.
|
||||||
|
nonExistentUsers = append(nonExistentUsers, dn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nonExistentUsers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroupMemberships - for each DN finds the set of LDAP groups they are a
|
||||||
|
// member of.
|
||||||
|
func (l *Config) LookupGroupMemberships(userDistNames []string, userDNToUsernameMap map[string]string) (map[string]set.StringSet, error) {
|
||||||
|
if !l.isUsingLookupBind {
|
||||||
|
return nil, errors.New("current LDAP configuration does not permit this lookup")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := l.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
// Bind to the lookup user account
|
||||||
|
if err = l.lookupBind(conn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make(map[string]set.StringSet, len(userDistNames))
|
||||||
|
for _, userDistName := range userDistNames {
|
||||||
|
username := userDNToUsernameMap[userDistName]
|
||||||
|
groups, err := l.searchForUserGroups(conn, username, userDistName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res[userDistName] = set.CreateStringSet(groups...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnabledWithLookupBind - checks if ldap IDP is enabled in lookup bind mode.
|
||||||
|
func (l Config) EnabledWithLookupBind() bool {
|
||||||
|
return l.Enabled && l.isUsingLookupBind
|
||||||
|
}
|
||||||
|
|
||||||
// Enabled returns if jwks is enabled.
|
// Enabled returns if jwks is enabled.
|
||||||
func Enabled(kvs config.KVS) bool {
|
func Enabled(kvs config.KVS) bool {
|
||||||
return kvs.Get(ServerAddr) != ""
|
return kvs.Get(ServerAddr) != ""
|
||||||
@@ -478,12 +586,16 @@ func Lookup(kvs config.KVS, rootCAs *x509.CertPool) (l Config, err error) {
|
|||||||
l.ServerAddr = ldapServer
|
l.ServerAddr = ldapServer
|
||||||
l.stsExpiryDuration = defaultLDAPExpiry
|
l.stsExpiryDuration = defaultLDAPExpiry
|
||||||
if v := env.Get(EnvSTSExpiry, kvs.Get(STSExpiry)); v != "" {
|
if v := env.Get(EnvSTSExpiry, kvs.Get(STSExpiry)); v != "" {
|
||||||
|
logger.Info("DEPRECATION WARNING: Support for configuring the default LDAP credentials expiry duration will be removed in a future release. Please use the `DurationSeconds` parameter in the LDAP STS API instead.")
|
||||||
expDur, err := time.ParseDuration(v)
|
expDur, err := time.ParseDuration(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return l, errors.New("LDAP expiry time err:" + err.Error())
|
return l, errors.New("LDAP expiry time err:" + err.Error())
|
||||||
}
|
}
|
||||||
if expDur <= 0 {
|
if expDur < minLDAPExpiry {
|
||||||
return l, errors.New("LDAP expiry time has to be positive")
|
return l, fmt.Errorf("LDAP expiry time must be at least %s", minLDAPExpiry)
|
||||||
|
}
|
||||||
|
if expDur > maxLDAPExpiry {
|
||||||
|
return l, fmt.Errorf("LDAP expiry time may not exceed %s", maxLDAPExpiry)
|
||||||
}
|
}
|
||||||
l.STSExpiryDuration = v
|
l.STSExpiryDuration = v
|
||||||
l.stsExpiryDuration = expDur
|
l.stsExpiryDuration = expDur
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ var (
|
|||||||
},
|
},
|
||||||
config.HelpKV{
|
config.HelpKV{
|
||||||
Key: STSExpiry,
|
Key: STSExpiry,
|
||||||
Description: `temporary credentials validity duration in s,m,h,d. Default is "1h"`,
|
Description: `[DEPRECATED] temporary credentials validity duration in s,m,h,d. Default is "1h"`,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Type: "duration",
|
Type: "duration",
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user