mirror of
https://github.com/pgsty/minio.git
synced 2026-08-14 02:33:16 +03:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c1a49be639 | |||
| 03172b89e2 | |||
| b517c791e9 | |||
| 14aef52004 | |||
| 67b20125e4 | |||
| d4b822d697 | |||
| 1b63291ee2 | |||
| aa7244a9a4 | |||
| 2a79ea0332 | |||
| 6e5c61d917 | |||
| 02e7de6367 | |||
| cec12f4c76 | |||
| da676ac298 | |||
| 18ec933085 | |||
| c31d2c3fdc | |||
| 8778828a03 | |||
| 48b212dd8e | |||
| be7de911c4 | |||
| 8cad407e0b | |||
| 85d2187c20 | |||
| 98d3f94996 | |||
| 173284903b | |||
| c70240b893 | |||
| 8ba2136e06 | |||
| 2dce5d9442 | |||
| f28b063091 | |||
| 90abea5b7a | |||
| c5b2a8441b | |||
| 0f5ca83418 | |||
| 8a6b13c239 | |||
| 8e8a792d9d | |||
| 95e0acbb26 | |||
| 55037e6e54 | |||
| 289e1d8b2a | |||
| e07918abe3 | |||
| ffea6fcf09 | |||
| 11b2220696 | |||
| aa8450a2a1 | |||
| 87cce344f6 | |||
| 7d4a2d2b68 | |||
| cfc8b92dff |
@@ -1,2 +1,9 @@
|
||||
.git
|
||||
.github
|
||||
docs
|
||||
default.etcd
|
||||
browser
|
||||
*.gz
|
||||
*.tar.gz
|
||||
*.bzip2
|
||||
*.zip
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.14.x, 1.15.x]
|
||||
go-version: [1.15.x, 1.16.x]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
+3
-6
@@ -6,8 +6,6 @@ LABEL maintainer="MinIO Inc <dev@min.io>"
|
||||
|
||||
COPY dockerscripts/docker-entrypoint.sh /usr/bin/
|
||||
COPY minio /usr/bin/
|
||||
COPY CREDITS /licenses/CREDITS
|
||||
COPY LICENSE /licenses/LICENSE
|
||||
|
||||
ENV MINIO_UPDATE=off \
|
||||
MINIO_ACCESS_KEY_FILE=access_key \
|
||||
@@ -17,10 +15,9 @@ ENV MINIO_UPDATE=off \
|
||||
MINIO_KMS_MASTER_KEY_FILE=kms_master_key \
|
||||
MINIO_SSE_MASTER_KEY_FILE=sse_master_key
|
||||
|
||||
RUN \
|
||||
microdnf update --nodocs && \
|
||||
microdnf install curl ca-certificates shadow-utils util-linux --nodocs && \
|
||||
microdnf clean all && \
|
||||
RUN microdnf update --nodocs
|
||||
RUN microdnf install curl ca-certificates shadow-utils util-linux --nodocs
|
||||
RUN microdnf clean all && \
|
||||
chmod +x /usr/bin/minio && \
|
||||
chmod +x /usr/bin/docker-entrypoint.sh
|
||||
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ ARG TARGETARCH
|
||||
LABEL name="MinIO" \
|
||||
vendor="MinIO Inc <dev@min.io>" \
|
||||
maintainer="MinIO Inc <dev@min.io>" \
|
||||
version="RELEASE.2021-02-11T08-23-43Z" \
|
||||
release="RELEASE.2021-02-11T08-23-43Z" \
|
||||
version="RELEASE.2021-02-23T20-05-01Z" \
|
||||
release="RELEASE.2021-02-23T20-05-01Z" \
|
||||
summary="MinIO is a High Performance Object Storage, API compatible with Amazon S3 cloud storage service." \
|
||||
description="MinIO object storage is fundamentally different. Designed for performance and the S3 API, it is 100% open-source. MinIO is ideal for large, private cloud environments with stringent security requirements and delivers mission-critical availability across a diverse range of workloads."
|
||||
|
||||
|
||||
@@ -81,9 +81,8 @@ docker-hotfix: hotfix checks
|
||||
@echo "Building minio docker image '$(TAG)'"
|
||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||
|
||||
docker: checks
|
||||
docker: build checks
|
||||
@echo "Building minio docker image '$(TAG)'"
|
||||
@GOOS=linux GO111MODULE=on CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
|
||||
@docker build -t $(TAG) . -f Dockerfile.dev
|
||||
|
||||
# Builds minio and installs it to $GOPATH/bin.
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ service minio start
|
||||
|
||||
## 使用源码安装
|
||||
|
||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install)。最低需要Golang版本为 [go1.14](https://golang.org/dl/#stable)
|
||||
采用源码安装仅供开发人员和高级用户使用,如果你还没有Golang环境, 请参考 [How to install Golang](https://golang.org/doc/install)。最低需要Golang版本为 [go1.15](https://golang.org/dl/#stable)
|
||||
|
||||
```sh
|
||||
GO111MODULE=on go get github.com/minio/minio
|
||||
|
||||
@@ -725,10 +725,6 @@ func (a adminAPIHandlers) AccountInfoHandler(w http.ResponseWriter, r *http.Requ
|
||||
}
|
||||
|
||||
accountName := cred.AccessKey
|
||||
if cred.ParentUser != "" {
|
||||
accountName = cred.ParentUser
|
||||
}
|
||||
|
||||
policies, err := globalIAMSys.PolicyDBGet(accountName, false)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
|
||||
@@ -259,6 +259,7 @@ type ServerHTTPAPIStats struct {
|
||||
// ServerHTTPStats holds all type of http operations performed to/from the server
|
||||
// including their average execution time.
|
||||
type ServerHTTPStats struct {
|
||||
S3RequestsInQueue int32 `json:"s3RequestsInQueue"`
|
||||
CurrentS3Requests ServerHTTPAPIStats `json:"currentS3Requests"`
|
||||
TotalS3Requests ServerHTTPAPIStats `json:"totalS3Requests"`
|
||||
TotalS3Errors ServerHTTPAPIStats `json:"totalS3Errors"`
|
||||
|
||||
@@ -662,6 +662,13 @@ func (h *healSequence) healSequenceStart(objAPI ObjectLayer) {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *healSequence) logHeal(healType madmin.HealItemType) {
|
||||
h.mutex.Lock()
|
||||
h.scannedItemsMap[healType]++
|
||||
h.lastHealActivity = UTCNow()
|
||||
h.mutex.Unlock()
|
||||
}
|
||||
|
||||
func (h *healSequence) queueHealTask(source healSource, healType madmin.HealItemType) error {
|
||||
globalHealConfigMu.Lock()
|
||||
opts := globalHealConfig
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
@@ -46,10 +48,12 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
||||
}
|
||||
_, present := network[nodeName]
|
||||
if !present {
|
||||
if err := isServerResolvable(endpoint, time.Second); err == nil {
|
||||
if err := isServerResolvable(endpoint, 2*time.Second); err == nil {
|
||||
network[nodeName] = "online"
|
||||
} else {
|
||||
network[nodeName] = "offline"
|
||||
// log once the error
|
||||
logger.LogOnceIf(context.Background(), err, nodeName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,30 +77,15 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
|
||||
|
||||
func getLocalDisks(endpointServerPools EndpointServerPools) []madmin.Disk {
|
||||
var localEndpoints Endpoints
|
||||
network := make(map[string]string)
|
||||
|
||||
for _, ep := range endpointServerPools {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
nodeName := endpoint.Host
|
||||
if nodeName == "" {
|
||||
nodeName = "localhost"
|
||||
}
|
||||
if endpoint.IsLocal {
|
||||
// Only proceed for local endpoints
|
||||
network[nodeName] = "online"
|
||||
localEndpoints = append(localEndpoints, endpoint)
|
||||
if !endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
_, present := network[nodeName]
|
||||
if !present {
|
||||
if err := isServerResolvable(endpoint, time.Second); err == nil {
|
||||
network[nodeName] = "online"
|
||||
} else {
|
||||
network[nodeName] = "offline"
|
||||
}
|
||||
}
|
||||
localEndpoints = append(localEndpoints, endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
localDisks, _ := initStorageDisksWithErrors(localEndpoints)
|
||||
defer closeStorageDisks(localDisks)
|
||||
storageInfo, _ := getStorageInfo(localDisks, localEndpoints.GetAllStrings())
|
||||
|
||||
+7
-6
@@ -184,12 +184,13 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
|
||||
}
|
||||
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
|
||||
ruleID, expiryTime := lc.PredictExpiryTime(lifecycle.ObjectOpts{
|
||||
Name: objInfo.Name,
|
||||
UserTags: objInfo.UserTags,
|
||||
VersionID: objInfo.VersionID,
|
||||
ModTime: objInfo.ModTime,
|
||||
IsLatest: objInfo.IsLatest,
|
||||
DeleteMarker: objInfo.DeleteMarker,
|
||||
Name: objInfo.Name,
|
||||
UserTags: objInfo.UserTags,
|
||||
VersionID: objInfo.VersionID,
|
||||
ModTime: objInfo.ModTime,
|
||||
IsLatest: objInfo.IsLatest,
|
||||
DeleteMarker: objInfo.DeleteMarker,
|
||||
SuccessorModTime: objInfo.SuccessorModTime,
|
||||
})
|
||||
if !expiryTime.IsZero() {
|
||||
w.Header()[xhttp.AmzExpiration] = []string{
|
||||
|
||||
+1
-2
@@ -459,8 +459,7 @@ func isReqAuthenticated(ctx context.Context, r *http.Request, region string, sty
|
||||
|
||||
// Verify 'Content-Md5' and/or 'X-Amz-Content-Sha256' if present.
|
||||
// The verification happens implicit during reading.
|
||||
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5),
|
||||
hex.EncodeToString(contentSHA256), -1, globalCLIContext.StrictS3Compat)
|
||||
reader, err := hash.NewReader(r.Body, -1, hex.EncodeToString(contentMD5), hex.EncodeToString(contentSHA256), -1)
|
||||
if err != nil {
|
||||
return toAPIErrorCode(ctx, err)
|
||||
}
|
||||
|
||||
@@ -204,8 +204,8 @@ wait:
|
||||
}
|
||||
}
|
||||
|
||||
lbDisks := z.serverPools[i].sets[setIndex].getOnlineDisks()
|
||||
if err := healErasureSet(ctx, setIndex, buckets, lbDisks); err != nil {
|
||||
err := z.serverPools[i].sets[setIndex].healErasureSet(ctx, buckets)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,13 +17,13 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/minio/highwayhash"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
)
|
||||
|
||||
|
||||
+9
-10
@@ -20,7 +20,6 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
@@ -28,7 +27,7 @@ import (
|
||||
func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||
tmpDir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
@@ -46,35 +45,35 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write([]byte("aaaaa"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
writer.(io.Closer).Close()
|
||||
|
||||
reader := newBitrotReader(disk, nil, volume, filePath, 35, bitrotAlgo, bitrotWriterSum(writer), 10)
|
||||
b := make([]byte, 10)
|
||||
if _, err = reader.ReadAt(b, 0); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = reader.ReadAt(b, 10); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = reader.ReadAt(b, 20); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = reader.ReadAt(b[:5], 30); err != nil {
|
||||
log.Fatal(err)
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -917,7 +917,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
return
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(fileBody, fileSize, "", "", fileSize)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
@@ -964,7 +964,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
|
||||
}
|
||||
info := ObjectInfo{Size: fileSize}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", fileSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
@@ -692,7 +692,7 @@ func restoreTransitionedObject(ctx context.Context, bucket, object string, objAP
|
||||
return err
|
||||
}
|
||||
defer gr.Close()
|
||||
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(gr, objInfo.Size, "", "", objInfo.Size)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+26
-29
@@ -179,31 +179,6 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
||||
if err != nil || rcfg == nil {
|
||||
return false, false, sync
|
||||
}
|
||||
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
||||
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
||||
if gerr != nil {
|
||||
validReplStatus := false
|
||||
switch oi.ReplicationStatus {
|
||||
// Point to note: even if two way replication with Deletemarker or Delete sync is enabled,
|
||||
// deletion of a REPLICA object/deletemarker will not trigger a replication to the other cluster.
|
||||
case replication.Pending, replication.Completed, replication.Failed:
|
||||
validReplStatus = true
|
||||
}
|
||||
if oi.DeleteMarker && validReplStatus {
|
||||
return oi.DeleteMarker, true, sync
|
||||
}
|
||||
return oi.DeleteMarker, false, sync
|
||||
}
|
||||
if oi.ReplicationStatus == replication.Replica {
|
||||
// avoid replicating a replica in bi-directional replication
|
||||
return oi.DeleteMarker, false, sync
|
||||
}
|
||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
||||
// the target online status should not be used here while deciding
|
||||
// whether to replicate deletes as the target could be temporarily down
|
||||
if tgt == nil {
|
||||
return oi.DeleteMarker, false, false
|
||||
}
|
||||
opts := replication.ObjectOpts{
|
||||
Name: dobj.ObjectName,
|
||||
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
|
||||
@@ -211,7 +186,29 @@ func checkReplicateDelete(ctx context.Context, bucket string, dobj ObjectToDelet
|
||||
DeleteMarker: oi.DeleteMarker,
|
||||
VersionID: dobj.VersionID,
|
||||
}
|
||||
return oi.DeleteMarker, rcfg.Replicate(opts), tgt.replicateSync
|
||||
replicate = rcfg.Replicate(opts)
|
||||
// when incoming delete is removal of a delete marker( a.k.a versioned delete),
|
||||
// GetObjectInfo returns extra information even though it returns errFileNotFound
|
||||
if gerr != nil {
|
||||
validReplStatus := false
|
||||
switch oi.ReplicationStatus {
|
||||
case replication.Pending, replication.Completed, replication.Failed:
|
||||
validReplStatus = true
|
||||
}
|
||||
if oi.DeleteMarker && (validReplStatus || replicate) {
|
||||
return oi.DeleteMarker, true, sync
|
||||
}
|
||||
// can be the case that other cluster is down and duplicate `mc rm --vid`
|
||||
// is issued - this still needs to be replicated back to the other target
|
||||
return oi.DeleteMarker, oi.VersionPurgeStatus == Pending || oi.VersionPurgeStatus == Failed, sync
|
||||
}
|
||||
tgt := globalBucketTargetSys.GetRemoteTargetClient(ctx, rcfg.RoleArn)
|
||||
// the target online status should not be used here while deciding
|
||||
// whether to replicate deletes as the target could be temporarily down
|
||||
if tgt == nil {
|
||||
return oi.DeleteMarker, false, false
|
||||
}
|
||||
return oi.DeleteMarker, replicate, tgt.replicateSync
|
||||
}
|
||||
|
||||
// replicate deletes to the designated replication target if replication configuration
|
||||
@@ -648,6 +645,8 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
||||
}
|
||||
}
|
||||
replicationStatus := replication.Completed
|
||||
// use core client to avoid doing multipart on PUT
|
||||
c := &miniogo.Core{Client: tgt.Client}
|
||||
if rtype != replicateAll {
|
||||
// replicate metadata for object tagging/copy with metadata replacement
|
||||
srcOpts := miniogo.CopySrcOptions{
|
||||
@@ -655,8 +654,6 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
||||
Object: object,
|
||||
VersionID: objInfo.VersionID}
|
||||
dstOpts := miniogo.PutObjectOptions{Internal: miniogo.AdvancedPutOptions{SourceVersionID: objInfo.VersionID}}
|
||||
c := &miniogo.Core{Client: tgt.Client}
|
||||
|
||||
if _, err = c.CopyObject(ctx, dest.Bucket, object, dest.Bucket, object, getCopyObjMetadata(objInfo, dest), srcOpts, dstOpts); err != nil {
|
||||
replicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate metadata for object %s/%s(%s): %s", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||
@@ -700,7 +697,7 @@ func replicateObject(ctx context.Context, objInfo ObjectInfo, objectAPI ObjectLa
|
||||
|
||||
// r takes over closing gr.
|
||||
r := bandwidth.NewMonitoredReader(ctx, globalBucketMonitor, objInfo.Bucket, objInfo.Name, gr, headerSize, b, target.BandwidthLimit)
|
||||
if _, err = tgt.PutObject(ctx, dest.Bucket, object, r, size, putOpts); err != nil {
|
||||
if _, err = c.PutObject(ctx, dest.Bucket, object, r, size, "", "", putOpts); err != nil {
|
||||
replicationStatus = replication.Failed
|
||||
logger.LogIf(ctx, fmt.Errorf("Unable to replicate for object %s/%s(%s): %w", bucket, objInfo.Name, objInfo.VersionID, err))
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
@@ -32,7 +33,6 @@ import (
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
"github.com/minio/minio/pkg/bucket/versioning"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+5
-1
@@ -50,10 +50,14 @@ import (
|
||||
var serverDebugLog = env.Get("_MINIO_SERVER_DEBUG", config.EnableOff) == config.EnableOn
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
logger.Init(GOPATH, GOROOT)
|
||||
logger.RegisterError(config.FmtError)
|
||||
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
// Inject into config package.
|
||||
config.Logger.Info = logger.Info
|
||||
config.Logger.LogIf = logger.LogIf
|
||||
|
||||
globalDNSCache = xhttp.NewDNSCache(10*time.Second, 10*time.Second, logger.LogOnceIf)
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ func deleteConfig(ctx context.Context, objAPI objectDeleter, configFile string)
|
||||
}
|
||||
|
||||
func saveConfig(ctx context.Context, objAPI ObjectLayer, configFile string, data []byte) error {
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)), globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(data), int64(len(data)), "", getSHA256Hash(data), int64(len(data)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+11
-11
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/minio/minio/cmd/config/api"
|
||||
"github.com/minio/minio/cmd/config/cache"
|
||||
"github.com/minio/minio/cmd/config/compress"
|
||||
"github.com/minio/minio/cmd/config/crawler"
|
||||
"github.com/minio/minio/cmd/config/dns"
|
||||
"github.com/minio/minio/cmd/config/etcd"
|
||||
"github.com/minio/minio/cmd/config/heal"
|
||||
@@ -35,6 +34,7 @@ import (
|
||||
"github.com/minio/minio/cmd/config/identity/openid"
|
||||
"github.com/minio/minio/cmd/config/notify"
|
||||
"github.com/minio/minio/cmd/config/policy/opa"
|
||||
"github.com/minio/minio/cmd/config/scanner"
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
@@ -60,7 +60,7 @@ func initHelp() {
|
||||
config.LoggerWebhookSubSys: logger.DefaultKVS,
|
||||
config.AuditWebhookSubSys: logger.DefaultAuditKVS,
|
||||
config.HealSubSys: heal.DefaultKVS,
|
||||
config.CrawlerSubSys: crawler.DefaultKVS,
|
||||
config.ScannerSubSys: scanner.DefaultKVS,
|
||||
}
|
||||
for k, v := range notify.DefaultNotificationKVS {
|
||||
kvs[k] = v
|
||||
@@ -117,8 +117,8 @@ func initHelp() {
|
||||
Description: "manage object healing frequency and bitrot verification checks",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.CrawlerSubSys,
|
||||
Description: "manage crawling for usage calculation, lifecycle, healing and more",
|
||||
Key: config.ScannerSubSys,
|
||||
Description: "manage scanner for usage calculation, lifecycle, healing and more",
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.LoggerWebhookSubSys,
|
||||
@@ -200,7 +200,7 @@ func initHelp() {
|
||||
config.CacheSubSys: cache.Help,
|
||||
config.CompressionSubSys: compress.Help,
|
||||
config.HealSubSys: heal.Help,
|
||||
config.CrawlerSubSys: crawler.Help,
|
||||
config.ScannerSubSys: scanner.Help,
|
||||
config.IdentityOpenIDSubSys: openid.Help,
|
||||
config.IdentityLDAPSubSys: xldap.Help,
|
||||
config.PolicyOPASubSys: opa.Help,
|
||||
@@ -274,11 +274,11 @@ func validateConfig(s config.Config, setDriveCounts []int) error {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
||||
if _, err = heal.LookupConfig(s[config.HealSubSys][config.Default]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default]); err != nil {
|
||||
if _, err = scanner.LookupConfig(s[config.ScannerSubSys][config.Default]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -606,10 +606,10 @@ func applyDynamicConfig(ctx context.Context, objAPI ObjectLayer, s config.Config
|
||||
return fmt.Errorf("Unable to apply heal config: %w", err)
|
||||
}
|
||||
|
||||
// Crawler
|
||||
crawlerCfg, err := crawler.LookupConfig(s[config.CrawlerSubSys][config.Default])
|
||||
// Scanner
|
||||
scannerCfg, err := scanner.LookupConfig(s[config.ScannerSubSys][config.Default])
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to apply crawler config: %w", err)
|
||||
return fmt.Errorf("Unable to apply scanner config: %w", err)
|
||||
}
|
||||
|
||||
// Apply configurations.
|
||||
@@ -624,7 +624,7 @@ func applyDynamicConfig(ctx context.Context, objAPI ObjectLayer, s config.Config
|
||||
globalHealConfig = healCfg
|
||||
globalHealConfigMu.Unlock()
|
||||
|
||||
logger.LogIf(ctx, crawlerSleeper.Update(crawlerCfg.Delay, crawlerCfg.MaxWait))
|
||||
logger.LogIf(ctx, scannerSleeper.Update(scannerCfg.Delay, scannerCfg.MaxWait))
|
||||
|
||||
// Update all dynamic config values in memory.
|
||||
globalServerConfigMu.Lock()
|
||||
|
||||
+26
-6
@@ -77,6 +77,7 @@ const (
|
||||
LoggerWebhookSubSys = "logger_webhook"
|
||||
AuditWebhookSubSys = "audit_webhook"
|
||||
HealSubSys = "heal"
|
||||
ScannerSubSys = "scanner"
|
||||
CrawlerSubSys = "crawler"
|
||||
|
||||
// Add new constants here if you add new fields to config.
|
||||
@@ -114,7 +115,7 @@ var SubSystems = set.CreateStringSet(
|
||||
PolicyOPASubSys,
|
||||
IdentityLDAPSubSys,
|
||||
IdentityOpenIDSubSys,
|
||||
CrawlerSubSys,
|
||||
ScannerSubSys,
|
||||
HealSubSys,
|
||||
NotifyAMQPSubSys,
|
||||
NotifyESSubSys,
|
||||
@@ -132,7 +133,7 @@ var SubSystems = set.CreateStringSet(
|
||||
var SubSystemsDynamic = set.CreateStringSet(
|
||||
APISubSys,
|
||||
CompressionSubSys,
|
||||
CrawlerSubSys,
|
||||
ScannerSubSys,
|
||||
HealSubSys,
|
||||
)
|
||||
|
||||
@@ -151,7 +152,7 @@ var SubSystemsSingleTargets = set.CreateStringSet([]string{
|
||||
IdentityLDAPSubSys,
|
||||
IdentityOpenIDSubSys,
|
||||
HealSubSys,
|
||||
CrawlerSubSys,
|
||||
ScannerSubSys,
|
||||
}...)
|
||||
|
||||
// Constant separators
|
||||
@@ -462,6 +463,13 @@ func LookupWorm() (bool, error) {
|
||||
return ParseBool(env.Get(EnvWorm, EnableOff))
|
||||
}
|
||||
|
||||
// Carries all the renamed sub-systems from their
|
||||
// previously known names
|
||||
var renamedSubsys = map[string]string{
|
||||
CrawlerSubSys: ScannerSubSys,
|
||||
// Add future sub-system renames
|
||||
}
|
||||
|
||||
// Merge - merges a new config with all the
|
||||
// missing values for default configs,
|
||||
// returns a config.
|
||||
@@ -477,9 +485,21 @@ func (c Config) Merge() Config {
|
||||
}
|
||||
}
|
||||
if _, ok := cp[subSys]; !ok {
|
||||
// A config subsystem was removed or server was downgraded.
|
||||
Logger.Info("config: ignoring unknown subsystem config %q\n", subSys)
|
||||
continue
|
||||
rnSubSys, ok := renamedSubsys[subSys]
|
||||
if !ok {
|
||||
// A config subsystem was removed or server was downgraded.
|
||||
Logger.Info("config: ignoring unknown subsystem config %q\n", subSys)
|
||||
continue
|
||||
}
|
||||
// Copy over settings from previous sub-system
|
||||
// to newly renamed sub-system
|
||||
for _, kv := range cp[rnSubSys][Default] {
|
||||
_, ok := c[subSys][tgt].Lookup(kv.Key)
|
||||
if !ok {
|
||||
ckvs.Set(kv.Key, kv.Value)
|
||||
}
|
||||
}
|
||||
subSys = rnSubSys
|
||||
}
|
||||
cp[subSys][tgt] = ckvs
|
||||
}
|
||||
|
||||
@@ -301,7 +301,8 @@ func (l *Config) Bind(username, password string) (string, []string, error) {
|
||||
var groups []string
|
||||
if l.GroupSearchFilter != "" {
|
||||
for _, groupSearchBase := range l.GroupSearchBaseDistNames {
|
||||
filter := strings.Replace(l.GroupSearchFilter, "%s", ldap.EscapeFilter(bindDN), -1)
|
||||
filter := strings.Replace(l.GroupSearchFilter, "%s", ldap.EscapeFilter(username), -1)
|
||||
filter = strings.Replace(filter, "%d", ldap.EscapeFilter(bindDN), -1)
|
||||
searchRequest := ldap.NewSearchRequest(
|
||||
groupSearchBase,
|
||||
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package crawler
|
||||
package scanner
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
@@ -29,8 +29,10 @@ const (
|
||||
Delay = "delay"
|
||||
MaxWait = "max_wait"
|
||||
|
||||
EnvDelay = "MINIO_CRAWLER_DELAY"
|
||||
EnvMaxWait = "MINIO_CRAWLER_MAX_WAIT"
|
||||
EnvDelay = "MINIO_SCANNER_DELAY"
|
||||
EnvDelayLegacy = "MINIO_CRAWLER_DELAY"
|
||||
EnvMaxWait = "MINIO_SCANNER_MAX_WAIT"
|
||||
EnvMaxWaitLegacy = "MINIO_CRAWLER_MAX_WAIT"
|
||||
)
|
||||
|
||||
// Config represents the heal settings.
|
||||
@@ -58,7 +60,7 @@ var (
|
||||
Help = config.HelpKVS{
|
||||
config.HelpKV{
|
||||
Key: Delay,
|
||||
Description: `crawler delay multiplier, defaults to '10.0'`,
|
||||
Description: `scanner delay multiplier, defaults to '10.0'`,
|
||||
Optional: true,
|
||||
Type: "float",
|
||||
},
|
||||
@@ -73,14 +75,22 @@ var (
|
||||
|
||||
// LookupConfig - lookup config and override with valid environment settings if any.
|
||||
func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
||||
if err = config.CheckValidKeys(config.CrawlerSubSys, kvs, DefaultKVS); err != nil {
|
||||
if err = config.CheckValidKeys(config.ScannerSubSys, kvs, DefaultKVS); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.Delay, err = strconv.ParseFloat(env.Get(EnvDelay, kvs.Get(Delay)), 64)
|
||||
delay := env.Get(EnvDelayLegacy, "")
|
||||
if delay == "" {
|
||||
delay = env.Get(EnvDelay, kvs.Get(Delay))
|
||||
}
|
||||
cfg.Delay, err = strconv.ParseFloat(delay, 64)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
cfg.MaxWait, err = time.ParseDuration(env.Get(EnvMaxWait, kvs.Get(MaxWait)))
|
||||
maxWait := env.Get(EnvMaxWaitLegacy, "")
|
||||
if maxWait == "" {
|
||||
maxWait = env.Get(EnvMaxWait, kvs.Get(MaxWait))
|
||||
}
|
||||
cfg.MaxWait, err = time.ParseDuration(maxWait)
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
+1
-1
@@ -19,13 +19,13 @@ import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -19,13 +19,13 @@ import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
|
||||
@@ -56,25 +56,25 @@ var (
|
||||
globalHealConfig heal.Config
|
||||
globalHealConfigMu sync.Mutex
|
||||
|
||||
dataCrawlerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||
dataScannerLeaderLockTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
|
||||
// Sleeper values are updated when config is loaded.
|
||||
crawlerSleeper = newDynamicSleeper(10, 10*time.Second)
|
||||
scannerSleeper = newDynamicSleeper(10, 10*time.Second)
|
||||
)
|
||||
|
||||
// initDataCrawler will start the crawler in the background.
|
||||
func initDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
||||
go runDataCrawler(ctx, objAPI)
|
||||
// initDataScanner will start the scanner in the background.
|
||||
func initDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
go runDataScanner(ctx, objAPI)
|
||||
}
|
||||
|
||||
// runDataCrawler will start a data crawler.
|
||||
// runDataScanner will start a data scanner.
|
||||
// The function will block until the context is canceled.
|
||||
// There should only ever be one crawler running per cluster.
|
||||
func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
||||
// Make sure only 1 crawler is running on the cluster.
|
||||
locker := objAPI.NewNSLock(minioMetaBucket, "runDataCrawler.lock")
|
||||
// There should only ever be one scanner running per cluster.
|
||||
func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
|
||||
// Make sure only 1 scanner is running on the cluster.
|
||||
locker := objAPI.NewNSLock(minioMetaBucket, "runDataScanner.lock")
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
for {
|
||||
err := locker.GetLock(ctx, dataCrawlerLeaderLockTimeout)
|
||||
err := locker.GetLock(ctx, dataScannerLeaderLockTimeout)
|
||||
if err != nil {
|
||||
time.Sleep(time.Duration(r.Float64() * float64(dataCrawlStartDelay)))
|
||||
continue
|
||||
@@ -112,7 +112,7 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
||||
crawlTimer.Reset(dataCrawlStartDelay)
|
||||
|
||||
if intDataUpdateTracker.debug {
|
||||
console.Debugln("starting crawler cycle")
|
||||
console.Debugln("starting scanner cycle")
|
||||
}
|
||||
|
||||
// Wait before starting next cycle and wait on startup.
|
||||
@@ -128,7 +128,7 @@ func runDataCrawler(ctx context.Context, objAPI ObjectLayer) {
|
||||
nextBloomCycle++
|
||||
var tmp [8]byte
|
||||
binary.LittleEndian.PutUint64(tmp[:], nextBloomCycle)
|
||||
r, err := hash.NewReader(bytes.NewReader(tmp[:]), int64(len(tmp)), "", "", int64(len(tmp)), false)
|
||||
r, err := hash.NewReader(bytes.NewReader(tmp[:]), int64(len(tmp)), "", "", int64(len(tmp)))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
@@ -167,7 +167,7 @@ type folderScanner struct {
|
||||
|
||||
// crawlDataFolder will crawl the basepath+cache.Info.Name and return an updated cache.
|
||||
// The returned cache will always be valid, but may not be updated from the existing.
|
||||
// Before each operation sleepDuration is called which can be used to temporarily halt the crawler.
|
||||
// Before each operation sleepDuration is called which can be used to temporarily halt the scanner.
|
||||
// If the supplied context is canceled the function will return at the first chance.
|
||||
func crawlDataFolder(ctx context.Context, basePath string, cache dataUsageCache, getSize getSizeFn) (dataUsageCache, error) {
|
||||
t := UTCNow()
|
||||
@@ -390,12 +390,12 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
if f.dataUsageCrawlDebug {
|
||||
console.Debugf(scannerLogPrefix+" Adding non-updated folder to heal check: %v\n", folder.name)
|
||||
}
|
||||
// If probability was already crawlerHealFolderInclude, keep it.
|
||||
// If probability was already scannerHealFolderInclude, keep it.
|
||||
folder.objectHealProbDiv = f.healFolderInclude
|
||||
}
|
||||
}
|
||||
}
|
||||
crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||
scannerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||
|
||||
cache := dataUsageEntry{}
|
||||
|
||||
@@ -407,19 +407,19 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
if f.dataUsageCrawlDebug {
|
||||
console.Debugf(scannerLogPrefix+" no bucket (%s,%s)\n", f.root, entName)
|
||||
}
|
||||
return nil
|
||||
return errDoneForNow
|
||||
}
|
||||
|
||||
if isReservedOrInvalidBucket(bucket, false) {
|
||||
if f.dataUsageCrawlDebug {
|
||||
console.Debugf(scannerLogPrefix+" invalid bucket: %v, entry: %v\n", bucket, entName)
|
||||
}
|
||||
return nil
|
||||
return errDoneForNow
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
return ctx.Err()
|
||||
return errDoneForNow
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -447,7 +447,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
}
|
||||
|
||||
// Dynamic time delay.
|
||||
wait := crawlerSleeper.Timer(ctx)
|
||||
wait := scannerSleeper.Timer(ctx)
|
||||
|
||||
// Get file size, ignore errors.
|
||||
item := crawlItem{
|
||||
@@ -537,7 +537,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
}
|
||||
|
||||
// Dynamic time delay.
|
||||
wait := crawlerSleeper.Timer(ctx)
|
||||
wait := scannerSleeper.Timer(ctx)
|
||||
resolver.bucket = bucket
|
||||
|
||||
foundObjs := false
|
||||
@@ -567,7 +567,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
|
||||
// Sleep and reset.
|
||||
wait()
|
||||
wait = crawlerSleeper.Timer(ctx)
|
||||
wait = scannerSleeper.Timer(ctx)
|
||||
entry, ok := entries.resolve(&resolver)
|
||||
if !ok {
|
||||
for _, err := range errs {
|
||||
@@ -604,7 +604,7 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
for _, ver := range fiv.Versions {
|
||||
// Sleep and reset.
|
||||
wait()
|
||||
wait = crawlerSleeper.Timer(ctx)
|
||||
wait = scannerSleeper.Timer(ctx)
|
||||
err := bgSeq.queueHealTask(healSource{
|
||||
bucket: bucket,
|
||||
object: fiv.Name,
|
||||
@@ -637,12 +637,12 @@ func (f *folderScanner) scanQueuedLevels(ctx context.Context, folders []cachedFo
|
||||
|
||||
objAPI.HealObjects(ctx, bucket, prefix, madmin.HealOpts{
|
||||
Recursive: true,
|
||||
Remove: true,
|
||||
Remove: healDeleteDangling,
|
||||
},
|
||||
func(bucket, object, versionID string) error {
|
||||
// Wait for each heal as per crawler frequency.
|
||||
// Wait for each heal as per scanner frequency.
|
||||
wait()
|
||||
wait = crawlerSleeper.Timer(ctx)
|
||||
wait = scannerSleeper.Timer(ctx)
|
||||
return bgSeq.queueHealTask(healSource{
|
||||
bucket: bucket,
|
||||
object: object,
|
||||
@@ -682,7 +682,7 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder,
|
||||
addDir = func(entName string, typ os.FileMode) error {
|
||||
select {
|
||||
case <-done:
|
||||
return ctx.Err()
|
||||
return errDoneForNow
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -690,12 +690,12 @@ func (f *folderScanner) deepScanFolder(ctx context.Context, folder cachedFolder,
|
||||
dirStack = append(dirStack, entName)
|
||||
err := readDirFn(path.Join(dirStack...), addDir)
|
||||
dirStack = dirStack[:len(dirStack)-1]
|
||||
crawlerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||
scannerSleeper.Sleep(ctx, dataCrawlSleepPerFolder)
|
||||
return err
|
||||
}
|
||||
|
||||
// Dynamic time delay.
|
||||
wait := crawlerSleeper.Timer(ctx)
|
||||
wait := scannerSleeper.Timer(ctx)
|
||||
|
||||
// Get file size, ignore errors.
|
||||
dirStack = append(dirStack, entName)
|
||||
@@ -91,7 +91,7 @@ type dataUsageCacheInfo struct {
|
||||
Name string
|
||||
LastUpdate time.Time
|
||||
NextCycle uint32
|
||||
// indicates if the disk is being healed and crawler
|
||||
// indicates if the disk is being healed and scanner
|
||||
// should skip healing the disk
|
||||
SkipHealing bool
|
||||
BloomFilter []byte `msg:"BloomFilter,omitempty"`
|
||||
@@ -485,7 +485,7 @@ type objectIO interface {
|
||||
// Only backend errors are returned as errors.
|
||||
// If the object is not found or unable to deserialize d is cleared and nil error is returned.
|
||||
func (d *dataUsageCache) load(ctx context.Context, store objectIO, name string) error {
|
||||
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, readLock, ObjectOptions{})
|
||||
r, err := store.GetObjectNInfo(ctx, dataUsageBucket, name, nil, http.Header{}, noLock, ObjectOptions{})
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case ObjectNotFound:
|
||||
@@ -513,7 +513,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
||||
}()
|
||||
defer pr.Close()
|
||||
|
||||
r, err := hash.NewReader(pr, -1, "", "", -1, false)
|
||||
r, err := hash.NewReader(pr, -1, "", "", -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -522,7 +522,7 @@ func (d *dataUsageCache) save(ctx context.Context, store objectIO, name string)
|
||||
dataUsageBucket,
|
||||
name,
|
||||
NewPutObjReader(r),
|
||||
ObjectOptions{})
|
||||
ObjectOptions{NoLock: true})
|
||||
if isErrBucketNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ func storeDataUsageInBackend(ctx context.Context, objAPI ObjectLayer, dui <-chan
|
||||
continue
|
||||
}
|
||||
size := int64(len(dataUsageJSON))
|
||||
r, err := hash.NewReader(bytes.NewReader(dataUsageJSON), size, "", "", size, false)
|
||||
r, err := hash.NewReader(bytes.NewReader(dataUsageJSON), size, "", "", size)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -269,10 +268,6 @@ func (c *diskCache) toClear() uint64 {
|
||||
return bytesToClear(int64(di.Total), int64(di.Free), uint64(c.quotaPct), uint64(c.lowWatermark), uint64(c.highWatermark))
|
||||
}
|
||||
|
||||
var (
|
||||
errDoneForNow = errors.New("done for now")
|
||||
)
|
||||
|
||||
func (c *diskCache) purgeWait(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
@@ -382,7 +377,7 @@ func (c *diskCache) purge(ctx context.Context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := readDirFilterFn(c.dir, filterFn); err != nil {
|
||||
if err := readDirFn(c.dir, filterFn); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
@@ -1025,7 +1020,7 @@ func (c *diskCache) scanCacheWritebackFailures(ctx context.Context) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := readDirFilterFn(c.dir, filterFn); err != nil {
|
||||
if err := readDirFn(c.dir, filterFn); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
+1
-1
@@ -705,7 +705,7 @@ func (c *cacheObjects) uploadObject(ctx context.Context, oi ObjectInfo) {
|
||||
if st == CommitComplete || st.String() == "" {
|
||||
return
|
||||
}
|
||||
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(cReader, oi.Size, "", "", oi.Size)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bufio"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
@@ -33,7 +34,6 @@ import (
|
||||
"github.com/minio/minio/cmd/crypto"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
"github.com/minio/sio"
|
||||
)
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -28,7 +29,6 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const erasureAlgorithm = "rs-vandermonde"
|
||||
|
||||
+14
-8
@@ -328,7 +328,9 @@ func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, obje
|
||||
}
|
||||
if scan != madmin.HealUnknownScan {
|
||||
healOnce.Do(func() {
|
||||
go healObject(bucket, object, fi.VersionID, scan)
|
||||
if _, healing := er.getOnlineDisksWithHealing(); !healing {
|
||||
go healObject(bucket, object, fi.VersionID, scan)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -370,12 +372,14 @@ func (er erasureObjects) getObject(ctx context.Context, bucket, object string, s
|
||||
|
||||
// GetObjectInfo - reads object metadata and replies back ObjectInfo.
|
||||
func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {
|
||||
// Lock the object before reading.
|
||||
lk := er.NewNSLock(bucket, object)
|
||||
if err := lk.GetRLock(ctx, globalOperationTimeout); err != nil {
|
||||
return ObjectInfo{}, err
|
||||
if !opts.NoLock {
|
||||
// Lock the object before reading.
|
||||
lk := er.NewNSLock(bucket, object)
|
||||
if err := lk.GetRLock(ctx, globalOperationTimeout); err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
defer lk.RUnlock()
|
||||
}
|
||||
defer lk.RUnlock()
|
||||
|
||||
return er.getObjectInfo(ctx, bucket, object, opts)
|
||||
}
|
||||
@@ -436,7 +440,9 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
|
||||
// if missing metadata can be reconstructed, attempt to reconstruct.
|
||||
if missingBlocks > 0 && missingBlocks < readQuorum {
|
||||
go healObject(bucket, object, fi.VersionID, madmin.HealNormalScan)
|
||||
if _, healing := er.getOnlineDisksWithHealing(); !healing {
|
||||
go healObject(bucket, object, fi.VersionID, madmin.HealNormalScan)
|
||||
}
|
||||
}
|
||||
|
||||
return fi, metaArr, onlineDisks, nil
|
||||
@@ -456,7 +462,7 @@ func (er erasureObjects) getObjectInfo(ctx context.Context, bucket, object strin
|
||||
objInfo.StorageClass = sc
|
||||
}
|
||||
}
|
||||
if !fi.VersionPurgeStatus.Empty() {
|
||||
if !fi.VersionPurgeStatus.Empty() && opts.VersionID != "" {
|
||||
// Make sure to return object info to provide extra information.
|
||||
return objInfo, toObjectErr(errMethodNotAllowed, bucket, object)
|
||||
}
|
||||
|
||||
+139
-80
@@ -250,15 +250,28 @@ func (z *erasureServerPools) getPoolIdx(ctx context.Context, bucket, object stri
|
||||
if z.SinglePool() {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
errs := make([]error, len(z.serverPools))
|
||||
objInfos := make([]ObjectInfo, len(z.serverPools))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i, pool := range z.serverPools {
|
||||
objInfo, err := pool.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||
wg.Add(1)
|
||||
go func(i int, pool *erasureSets) {
|
||||
defer wg.Done()
|
||||
objInfos[i], errs[i] = pool.GetObjectInfo(ctx, bucket, object, ObjectOptions{})
|
||||
}(i, pool)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
for i, err := range errs {
|
||||
if err != nil && !isErrObjectNotFound(err) {
|
||||
return -1, err
|
||||
}
|
||||
if isErrObjectNotFound(err) {
|
||||
// No object exists or its a delete marker,
|
||||
// check objInfo to confirm.
|
||||
if objInfo.DeleteMarker && objInfo.Name != "" {
|
||||
if objInfos[i].DeleteMarker && objInfos[i].Name != "" {
|
||||
return i, nil
|
||||
}
|
||||
// objInfo is not valid, truly the object doesn't
|
||||
@@ -388,7 +401,7 @@ func (z *erasureServerPools) CrawlAndGetDataUsage(ctx context.Context, bf *bloom
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
// Start crawler. Blocks until done.
|
||||
// Start scanner. Blocks until done.
|
||||
err := erObj.crawlAndGetDataUsage(ctx, allBuckets, bf, updates)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
@@ -511,16 +524,71 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
|
||||
|
||||
object = encodeDirObject(object)
|
||||
|
||||
for _, pool := range z.serverPools {
|
||||
gr, err = pool.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
||||
if err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
continue
|
||||
if z.SinglePool() {
|
||||
return z.serverPools[0].GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
||||
}
|
||||
|
||||
var unlockOnDefer bool
|
||||
var nsUnlocker = func() {}
|
||||
defer func() {
|
||||
if unlockOnDefer {
|
||||
nsUnlocker()
|
||||
}
|
||||
}()
|
||||
|
||||
// Acquire lock
|
||||
if lockType != noLock {
|
||||
lock := z.NewNSLock(bucket, object)
|
||||
switch lockType {
|
||||
case writeLock:
|
||||
if err = lock.GetLock(ctx, globalOperationTimeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nsUnlocker = lock.Unlock
|
||||
case readLock:
|
||||
if err = lock.GetRLock(ctx, globalOperationTimeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nsUnlocker = lock.RUnlock
|
||||
}
|
||||
unlockOnDefer = true
|
||||
}
|
||||
|
||||
errs := make([]error, len(z.serverPools))
|
||||
grs := make([]*GetObjectReader, len(z.serverPools))
|
||||
|
||||
lockType = noLock // do not take locks at lower levels
|
||||
var wg sync.WaitGroup
|
||||
for i, pool := range z.serverPools {
|
||||
wg.Add(1)
|
||||
go func(i int, pool *erasureSets) {
|
||||
defer wg.Done()
|
||||
grs[i], errs[i] = pool.GetObjectNInfo(ctx, bucket, object, rs, h, lockType, opts)
|
||||
}(i, pool)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var found int = -1
|
||||
for i, err := range errs {
|
||||
if err == nil {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
for _, grr := range grs {
|
||||
if grr != nil {
|
||||
grr.Close()
|
||||
}
|
||||
}
|
||||
return gr, err
|
||||
}
|
||||
return gr, nil
|
||||
}
|
||||
|
||||
if found >= 0 {
|
||||
return grs[found], nil
|
||||
}
|
||||
|
||||
object = decodeDirObject(object)
|
||||
if opts.VersionID != "" {
|
||||
return gr, VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
||||
}
|
||||
@@ -533,7 +601,6 @@ func (z *erasureServerPools) GetObject(ctx context.Context, bucket, object strin
|
||||
}
|
||||
|
||||
object = encodeDirObject(object)
|
||||
|
||||
for _, pool := range z.serverPools {
|
||||
if err := pool.GetObject(ctx, bucket, object, startOffset, length, writer, etag, opts); err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
@@ -555,16 +622,49 @@ func (z *erasureServerPools) GetObjectInfo(ctx context.Context, bucket, object s
|
||||
}
|
||||
|
||||
object = encodeDirObject(object)
|
||||
for _, pool := range z.serverPools {
|
||||
objInfo, err = pool.GetObjectInfo(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return objInfo, err
|
||||
}
|
||||
return objInfo, nil
|
||||
|
||||
if z.SinglePool() {
|
||||
return z.serverPools[0].GetObjectInfo(ctx, bucket, object, opts)
|
||||
}
|
||||
|
||||
// Lock the object before reading.
|
||||
lk := z.NewNSLock(bucket, object)
|
||||
if err := lk.GetRLock(ctx, globalOperationTimeout); err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
defer lk.RUnlock()
|
||||
|
||||
errs := make([]error, len(z.serverPools))
|
||||
objInfos := make([]ObjectInfo, len(z.serverPools))
|
||||
|
||||
opts.NoLock = true // avoid taking locks at lower levels for multi-pool setups.
|
||||
var wg sync.WaitGroup
|
||||
for i, pool := range z.serverPools {
|
||||
wg.Add(1)
|
||||
go func(i int, pool *erasureSets) {
|
||||
defer wg.Done()
|
||||
objInfos[i], errs[i] = pool.GetObjectInfo(ctx, bucket, object, opts)
|
||||
}(i, pool)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var found int = -1
|
||||
for i, err := range errs {
|
||||
if err == nil {
|
||||
found = i
|
||||
break
|
||||
}
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
// some errors such as MethodNotAllowed for delete marker
|
||||
// should be returned upwards.
|
||||
return objInfos[i], err
|
||||
}
|
||||
}
|
||||
|
||||
if found >= 0 {
|
||||
return objInfos[found], nil
|
||||
}
|
||||
|
||||
object = decodeDirObject(object)
|
||||
if opts.VersionID != "" {
|
||||
return objInfo, VersionNotFound{Bucket: bucket, Object: object, VersionID: opts.VersionID}
|
||||
@@ -600,7 +700,6 @@ func (z *erasureServerPools) DeleteObject(ctx context.Context, bucket string, ob
|
||||
}
|
||||
|
||||
object = encodeDirObject(object)
|
||||
|
||||
if z.SinglePool() {
|
||||
return z.serverPools[0].DeleteObject(ctx, bucket, object, opts)
|
||||
}
|
||||
@@ -1590,27 +1689,13 @@ func (z *erasureServerPools) PutObjectTags(ctx context.Context, bucket, object s
|
||||
return z.serverPools[0].PutObjectTags(ctx, bucket, object, tags, opts)
|
||||
}
|
||||
|
||||
for _, pool := range z.serverPools {
|
||||
objInfo, err := pool.PutObjectTags(ctx, bucket, object, tags, opts)
|
||||
if err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
return objInfo, nil
|
||||
}
|
||||
if opts.VersionID != "" {
|
||||
return ObjectInfo{}, VersionNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionID: opts.VersionID,
|
||||
}
|
||||
}
|
||||
return ObjectInfo{}, ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
// We don't know the size here set 1GiB atleast.
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
|
||||
return z.serverPools[idx].PutObjectTags(ctx, bucket, object, tags, opts)
|
||||
}
|
||||
|
||||
// DeleteObjectTags - delete object tags from an existing object
|
||||
@@ -1619,27 +1704,14 @@ func (z *erasureServerPools) DeleteObjectTags(ctx context.Context, bucket, objec
|
||||
if z.SinglePool() {
|
||||
return z.serverPools[0].DeleteObjectTags(ctx, bucket, object, opts)
|
||||
}
|
||||
for _, pool := range z.serverPools {
|
||||
objInfo, err := pool.DeleteObjectTags(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
return objInfo, nil
|
||||
}
|
||||
if opts.VersionID != "" {
|
||||
return ObjectInfo{}, VersionNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionID: opts.VersionID,
|
||||
}
|
||||
}
|
||||
return ObjectInfo{}, ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
|
||||
// We don't know the size here set 1GiB atleast.
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||
if err != nil {
|
||||
return ObjectInfo{}, err
|
||||
}
|
||||
|
||||
return z.serverPools[idx].DeleteObjectTags(ctx, bucket, object, opts)
|
||||
}
|
||||
|
||||
// GetObjectTags - get object tags from an existing object
|
||||
@@ -1648,25 +1720,12 @@ func (z *erasureServerPools) GetObjectTags(ctx context.Context, bucket, object s
|
||||
if z.SinglePool() {
|
||||
return z.serverPools[0].GetObjectTags(ctx, bucket, object, opts)
|
||||
}
|
||||
for _, pool := range z.serverPools {
|
||||
tags, err := pool.GetObjectTags(ctx, bucket, object, opts)
|
||||
if err != nil {
|
||||
if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return tags, err
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
if opts.VersionID != "" {
|
||||
return nil, VersionNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
VersionID: opts.VersionID,
|
||||
}
|
||||
}
|
||||
return nil, ObjectNotFound{
|
||||
Bucket: bucket,
|
||||
Object: object,
|
||||
|
||||
// We don't know the size here set 1GiB atleast.
|
||||
idx, err := z.getPoolIdx(ctx, bucket, object, 1<<30)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return z.serverPools[idx].GetObjectTags(ctx, bucket, object, opts)
|
||||
}
|
||||
|
||||
+9
-4
@@ -357,9 +357,14 @@ func newErasureSets(ctx context.Context, endpoints Endpoints, storageDisks []Sto
|
||||
|
||||
mutex := newNSLock(globalIsDistErasure)
|
||||
|
||||
// Number of buffers, max 2GB.
|
||||
n := setCount * setDriveCount
|
||||
if n > 100 {
|
||||
n = 100
|
||||
}
|
||||
// Initialize byte pool once for all sets, bpool size is set to
|
||||
// setCount * setDriveCount with each memory upto blockSizeV1.
|
||||
bp := bpool.NewBytePoolCap(setCount*setDriveCount, blockSizeV1, blockSizeV1*2)
|
||||
bp := bpool.NewBytePoolCap(n, blockSizeV1, blockSizeV1*2)
|
||||
|
||||
for i := 0; i < setCount; i++ {
|
||||
s.erasureDisks[i] = make([]StorageAPI, setDriveCount)
|
||||
@@ -1362,11 +1367,11 @@ func (s *erasureSets) HealBucket(ctx context.Context, bucket string, opts madmin
|
||||
SetCount: s.setCount,
|
||||
}
|
||||
|
||||
for _, s := range s.sets {
|
||||
for _, set := range s.sets {
|
||||
var healResult madmin.HealResultItem
|
||||
healResult, err = s.HealBucket(ctx, bucket, opts)
|
||||
healResult, err = set.HealBucket(ctx, bucket, opts)
|
||||
if err != nil {
|
||||
return result, err
|
||||
return result, toObjectErr(err, bucket)
|
||||
}
|
||||
result.Before.Drives = append(result.Before.Drives, healResult.Before.Drives...)
|
||||
result.After.Drives = append(result.After.Drives, healResult.After.Drives...)
|
||||
|
||||
+12
-5
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -260,7 +261,7 @@ func (er erasureObjects) getOnlineDisksWithHealing() (newDisks []StorageAPI, hea
|
||||
|
||||
for i, info := range infos {
|
||||
// Check if one of the drives in the set is being healed.
|
||||
// this information is used by crawler to skip healing
|
||||
// this information is used by scanner to skip healing
|
||||
// this erasure set while it calculates the usage.
|
||||
if info.Healing || info.Error != "" {
|
||||
healing = true
|
||||
@@ -345,7 +346,8 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
|
||||
var saverWg sync.WaitGroup
|
||||
saverWg.Add(1)
|
||||
go func() {
|
||||
const updateTime = 30 * time.Second
|
||||
// Add jitter to the update time so multiple sets don't sync up.
|
||||
var updateTime = 30*time.Second + time.Duration(float64(10*time.Second)*rand.Float64())
|
||||
t := time.NewTicker(updateTime)
|
||||
defer t.Stop()
|
||||
defer saverWg.Done()
|
||||
@@ -378,7 +380,7 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
|
||||
}
|
||||
}()
|
||||
|
||||
// Start one crawler per disk
|
||||
// Start one scanner per disk
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(disks))
|
||||
for i := range disks {
|
||||
@@ -418,9 +420,10 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
|
||||
cache, err = disk.CrawlAndGetDataUsage(ctx, cache)
|
||||
cache.Info.BloomFilter = nil
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
if cache.Info.LastUpdate.After(before) {
|
||||
if !cache.Info.LastUpdate.IsZero() && cache.Info.LastUpdate.After(before) {
|
||||
logger.LogIf(ctx, cache.save(ctx, er, cacheName))
|
||||
} else {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -429,11 +432,15 @@ func (er erasureObjects) crawlAndGetDataUsage(ctx context.Context, buckets []Buc
|
||||
if r := cache.root(); r != nil {
|
||||
root = cache.flatten(*r)
|
||||
}
|
||||
t := time.Now()
|
||||
bucketResults <- dataUsageEntryInfo{
|
||||
Name: cache.Info.Name,
|
||||
Parent: dataUsageRoot,
|
||||
Entry: root,
|
||||
}
|
||||
// We want to avoid synchronizing up all writes in case
|
||||
// the results are piled up.
|
||||
time.Sleep(time.Duration(float64(time.Since(t)) * rand.Float64()))
|
||||
// Save cache
|
||||
logger.LogIf(ctx, cache.save(ctx, er, cacheName))
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This code is imported from "golang.org/x/tools/internal/fastwalk",
|
||||
// only fastwalk.go is imported since we already implement readDir()
|
||||
// with some little tweaks.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errSkipFile = errors.New("fastwalk: skip this file")
|
||||
|
||||
func readDirFn(dirName string, fn func(entName string, typ os.FileMode) error) error {
|
||||
fis, err := readDir(dirName)
|
||||
if err != nil {
|
||||
if osIsNotExist(err) || err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
var mode os.FileMode
|
||||
if strings.HasSuffix(fi, SlashSeparator) {
|
||||
mode |= os.ModeDir
|
||||
}
|
||||
|
||||
if err = fn(fi, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -31,8 +32,8 @@ import (
|
||||
"github.com/minio/minio/cmd/config/storageclass"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/color"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/sync/errgroup"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -156,7 +157,7 @@ func newFormatErasureV3(numSets int, setLen int) *formatErasureV3 {
|
||||
// successfully the version only if the backend is Erasure.
|
||||
func formatGetBackendErasureVersion(formatPath string) (string, error) {
|
||||
meta := &formatMetaV1{}
|
||||
b, err := ioutil.ReadFile(formatPath)
|
||||
b, err := xioutil.ReadFile(formatPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -218,7 +219,7 @@ func formatErasureMigrateV1ToV2(export, version string) error {
|
||||
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
|
||||
|
||||
formatV1 := &formatErasureV1{}
|
||||
b, err := ioutil.ReadFile(formatPath)
|
||||
b, err := xioutil.ReadFile(formatPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -251,7 +252,7 @@ func formatErasureMigrateV2ToV3(export, version string) error {
|
||||
|
||||
formatPath := pathJoin(export, minioMetaBucket, formatConfigFile)
|
||||
formatV2 := &formatErasureV2{}
|
||||
b, err := ioutil.ReadFile(formatPath)
|
||||
b, err := xioutil.ReadFile(formatPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
mioutil "github.com/minio/minio/pkg/ioutil"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/trie"
|
||||
)
|
||||
|
||||
@@ -114,7 +114,7 @@ func (fs *FSObjects) backgroundAppend(ctx context.Context, bucket, object, uploa
|
||||
}
|
||||
|
||||
partPath := pathJoin(uploadIDDir, entry)
|
||||
err = mioutil.AppendFile(file.filePath, partPath, globalFSOSync)
|
||||
err = xioutil.AppendFile(file.filePath, partPath, globalFSOSync)
|
||||
if err != nil {
|
||||
reqInfo := logger.GetReqInfo(ctx).AppendTags("partPath", partPath)
|
||||
reqInfo.AppendTags("filepath", file.filePath)
|
||||
@@ -390,7 +390,7 @@ func (fs *FSObjects) GetMultipartInfo(ctx context.Context, bucket, object, uploa
|
||||
return minfo, toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
fsMetaBytes, err := ioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
fsMetaBytes, err := xioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return minfo, toObjectErr(err, bucket, object)
|
||||
@@ -700,7 +700,7 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
||||
GotETag: part.ETag,
|
||||
}
|
||||
}
|
||||
if err = mioutil.AppendFile(appendFilePath, pathJoin(uploadIDDir, partFile), globalFSOSync); err != nil {
|
||||
if err = xioutil.AppendFile(appendFilePath, pathJoin(uploadIDDir, partFile), globalFSOSync); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return oi, toObjectErr(err)
|
||||
}
|
||||
@@ -744,7 +744,7 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string,
|
||||
}()
|
||||
|
||||
// Read saved fs metadata for ongoing multipart.
|
||||
fsMetaBuf, err := ioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
fsMetaBuf, err := xioutil.ReadFile(pathJoin(uploadIDDir, fs.metaJSONFile))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return oi, toObjectErr(err, bucket, object)
|
||||
|
||||
+2
-1
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/color"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
"github.com/minio/minio/pkg/lock"
|
||||
"github.com/minio/minio/pkg/madmin"
|
||||
"github.com/minio/minio/pkg/mimedb"
|
||||
@@ -323,7 +324,7 @@ func (fs *FSObjects) crawlBucket(ctx context.Context, bucket string, cache dataU
|
||||
// Load bucket info.
|
||||
cache, err = crawlDataFolder(ctx, fs.fsPath, cache, func(item crawlItem) (sizeSummary, error) {
|
||||
bucket, object := item.bucket, item.objectPath()
|
||||
fsMetaBytes, err := ioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
|
||||
fsMetaBytes, err := xioutil.ReadFile(pathJoin(fs.fsPath, minioMetaBucket, bucketMetaPrefix, bucket, object, fs.metaJSONFile))
|
||||
if err != nil && !osIsNotExist(err) {
|
||||
if intDataUpdateTracker.debug {
|
||||
logger.Info(color.Green("crawlBucket:")+" object return unexpected error: %v/%v: %w", item.bucket, item.objectPath(), err)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
@@ -46,7 +47,6 @@ import (
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/policy/condition"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
|
||||
minio "github.com/minio/minio/cmd"
|
||||
)
|
||||
@@ -793,6 +793,10 @@ func (a *azureObjects) GetObjectNInfo(ctx context.Context, bucket, object string
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if startOffset != 0 || length != objInfo.Size {
|
||||
delete(objInfo.UserDefined, "Content-MD5")
|
||||
}
|
||||
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
err := a.GetObject(ctx, bucket, object, startOffset, length, pw, objInfo.InnerETag, opts)
|
||||
|
||||
@@ -497,7 +497,7 @@ func (n *hdfsObjects) populateDirectoryListing(filePath string, fileInfos map[st
|
||||
}
|
||||
|
||||
for _, fileInfo := range infos {
|
||||
filePath := n.hdfsPathJoin(filePath, fileInfo.Name())
|
||||
filePath := minio.PathJoin(filePath, fileInfo.Name())
|
||||
fileInfos[filePath] = fileInfo
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ func getGWMetadata(ctx context.Context, bucket, prefix string, gwMeta gwMetaV1)
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(metadataBytes), int64(len(metadataBytes)), "", "", int64(len(metadataBytes)), false)
|
||||
hashReader, err := hash.NewReader(bytes.NewReader(metadataBytes), int64(len(metadataBytes)), "", "", int64(len(metadataBytes)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ func randString(n int, src rand.Source, prefix string) string {
|
||||
// Chains all credential types, in the following order:
|
||||
// - AWS env vars (i.e. AWS_ACCESS_KEY_ID)
|
||||
// - AWS creds file (i.e. AWS_SHARED_CREDENTIALS_FILE or ~/.aws/credentials)
|
||||
// - Static credentials provided by user (i.e. MINIO_ROOT_USER)
|
||||
// - Static credentials provided by user (i.e. MINIO_ROOT_USER/MINIO_ACCESS_KEY)
|
||||
var defaultProviders = []credentials.Provider{
|
||||
&credentials.EnvAWS{},
|
||||
&credentials.FileAWSCredentials{},
|
||||
@@ -149,6 +149,7 @@ var defaultProviders = []credentials.Provider{
|
||||
// - IAM profile based credentials. (performs an HTTP
|
||||
// call to a pre-defined endpoint, only valid inside
|
||||
// configured ec2 instances)
|
||||
// - Static credentials provided by user (i.e. MINIO_ROOT_USER/MINIO_ACCESS_KEY)
|
||||
var defaultAWSCredProviders = []credentials.Provider{
|
||||
&credentials.EnvAWS{},
|
||||
&credentials.FileAWSCredentials{},
|
||||
|
||||
+41
-46
@@ -18,7 +18,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
@@ -120,18 +120,14 @@ func mustGetHealSequence(ctx context.Context) *healSequence {
|
||||
}
|
||||
|
||||
// healErasureSet lists and heals all objects in a specific erasure set
|
||||
func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, disks []StorageAPI) error {
|
||||
func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []BucketInfo) error {
|
||||
bgSeq := mustGetHealSequence(ctx)
|
||||
|
||||
buckets = append(buckets, BucketInfo{
|
||||
Name: pathJoin(minioMetaBucket, minioConfigPrefix),
|
||||
})
|
||||
|
||||
// Try to pro-actively heal backend-encrypted file.
|
||||
if err := bgSeq.queueHealTask(healSource{
|
||||
bucket: minioMetaBucket,
|
||||
object: backendEncryptedFile,
|
||||
}, madmin.HealItemMetadata); err != nil {
|
||||
if _, err := er.HealObject(ctx, minioMetaBucket, backendEncryptedFile, "", madmin.HealOpts{}); err != nil {
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
@@ -140,61 +136,60 @@ func healErasureSet(ctx context.Context, setIndex int, buckets []BucketInfo, dis
|
||||
// Heal all buckets with all objects
|
||||
for _, bucket := range buckets {
|
||||
// Heal current bucket
|
||||
if err := bgSeq.queueHealTask(healSource{
|
||||
bucket: bucket.Name,
|
||||
}, madmin.HealItemBucket); err != nil {
|
||||
if _, err := er.HealBucket(ctx, bucket.Name, madmin.HealOpts{}); err != nil {
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
if serverDebugLog {
|
||||
console.Debugf(color.Green("healDisk:")+" healing bucket %s content on erasure set %d\n", bucket.Name, setIndex+1)
|
||||
console.Debugf(color.Green("healDisk:")+" healing bucket %s content on erasure set %d\n", bucket.Name, er.setNumber+1)
|
||||
}
|
||||
|
||||
var entryChs []FileInfoVersionsCh
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
for _, disk := range disks {
|
||||
disk := disk
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
entryCh, err := disk.WalkVersions(ctx, bucket.Name, "", "", true, ctx.Done())
|
||||
if err != nil {
|
||||
// Disk walk returned error, ignore it.
|
||||
return
|
||||
}
|
||||
mu.Lock()
|
||||
entryChs = append(entryChs, FileInfoVersionsCh{
|
||||
Ch: entryCh,
|
||||
})
|
||||
mu.Unlock()
|
||||
}()
|
||||
disks, _ := er.getOnlineDisksWithHealing()
|
||||
if len(disks) == 0 {
|
||||
return errors.New("healErasureSet: No non-healing disks found")
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
entriesValid := make([]bool, len(entryChs))
|
||||
entries := make([]FileInfoVersions, len(entryChs))
|
||||
|
||||
for {
|
||||
entry, _, ok := lexicallySortedEntryVersions(entryChs, entries, entriesValid)
|
||||
if !ok {
|
||||
break
|
||||
// Limit listing to 3 drives.
|
||||
if len(disks) > 3 {
|
||||
disks = disks[:3]
|
||||
}
|
||||
healEntry := func(entry metaCacheEntry) {
|
||||
if entry.isDir() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, version := range entry.Versions {
|
||||
if err := bgSeq.queueHealTask(healSource{
|
||||
bucket: bucket.Name,
|
||||
object: version.Name,
|
||||
versionID: version.VersionID,
|
||||
}, madmin.HealItemObject); err != nil {
|
||||
fivs, err := entry.fileInfoVersions(bucket.Name)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
waitForLowHTTPReq(globalHealConfig.IOCount, globalHealConfig.Sleep)
|
||||
for _, version := range fivs.Versions {
|
||||
if _, err := er.HealObject(ctx, bucket.Name, version.Name, version.VersionID, madmin.HealOpts{ScanMode: madmin.HealNormalScan, Remove: healDeleteDangling}); err != nil {
|
||||
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
}
|
||||
bgSeq.logHeal(madmin.HealItemObject)
|
||||
}
|
||||
}
|
||||
err := listPathRaw(ctx, listPathRawOptions{
|
||||
disks: disks,
|
||||
bucket: bucket.Name,
|
||||
recursive: true,
|
||||
forwardTo: "", //TODO(klauspost): Set this to last known offset when resuming.
|
||||
minDisks: 1,
|
||||
reportNotFound: false,
|
||||
agreed: healEntry,
|
||||
partial: func(entries metaCacheEntries, nAgreed int, errs []error) {
|
||||
entry, _ := entries.firstFound()
|
||||
if entry != nil && !entry.isDir() {
|
||||
healEntry(*entry)
|
||||
}
|
||||
},
|
||||
finished: nil,
|
||||
})
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -136,20 +136,25 @@ func maxClients(f http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
globalHTTPStats.addRequestsInQueue(1)
|
||||
|
||||
deadlineTimer := time.NewTimer(deadline)
|
||||
defer deadlineTimer.Stop()
|
||||
|
||||
select {
|
||||
case pool <- struct{}{}:
|
||||
defer func() { <-pool }()
|
||||
globalHTTPStats.addRequestsInQueue(-1)
|
||||
f.ServeHTTP(w, r)
|
||||
case <-deadlineTimer.C:
|
||||
// Send a http timeout message
|
||||
writeErrorResponse(r.Context(), w,
|
||||
errorCodes.ToAPIErr(ErrOperationMaxedOut),
|
||||
r.URL, guessIsBrowserReq(r))
|
||||
globalHTTPStats.addRequestsInQueue(-1)
|
||||
return
|
||||
case <-r.Context().Done():
|
||||
globalHTTPStats.addRequestsInQueue(-1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+1
-2
@@ -18,9 +18,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// getSHA256Hash returns SHA-256 hash in hex encoding of given data.
|
||||
|
||||
+6
-3
@@ -137,23 +137,26 @@ func (stats *HTTPAPIStats) Load() map[string]int {
|
||||
// HTTPStats holds statistics information about
|
||||
// HTTP requests made by all clients
|
||||
type HTTPStats struct {
|
||||
s3RequestsInQueue int32
|
||||
currentS3Requests HTTPAPIStats
|
||||
totalS3Requests HTTPAPIStats
|
||||
totalS3Errors HTTPAPIStats
|
||||
}
|
||||
|
||||
func (st *HTTPStats) addRequestsInQueue(i int32) {
|
||||
atomic.AddInt32(&st.s3RequestsInQueue, i)
|
||||
}
|
||||
|
||||
// Converts http stats into struct to be sent back to the client.
|
||||
func (st *HTTPStats) toServerHTTPStats() ServerHTTPStats {
|
||||
serverStats := ServerHTTPStats{}
|
||||
|
||||
serverStats.S3RequestsInQueue = atomic.LoadInt32(&st.s3RequestsInQueue)
|
||||
serverStats.CurrentS3Requests = ServerHTTPAPIStats{
|
||||
APIStats: st.currentS3Requests.Load(),
|
||||
}
|
||||
|
||||
serverStats.TotalS3Requests = ServerHTTPAPIStats{
|
||||
APIStats: st.totalS3Requests.Load(),
|
||||
}
|
||||
|
||||
serverStats.TotalS3Errors = ServerHTTPAPIStats{
|
||||
APIStats: st.totalS3Errors.Load(),
|
||||
}
|
||||
|
||||
+20
-6
@@ -1703,27 +1703,41 @@ func (sys *IAMSys) PolicyDBGet(name string, isGroup bool) ([]string, error) {
|
||||
// This call assumes that caller has the sys.RLock()
|
||||
func (sys *IAMSys) policyDBGet(name string, isGroup bool) ([]string, error) {
|
||||
if isGroup {
|
||||
if _, ok := sys.iamGroupsMap[name]; !ok {
|
||||
g, ok := sys.iamGroupsMap[name]
|
||||
if !ok {
|
||||
return nil, errNoSuchGroup
|
||||
}
|
||||
|
||||
// Group is disabled, so we return no policy - this
|
||||
// ensures the request is denied.
|
||||
if g.Status == statusDisabled {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
mp := sys.iamGroupPolicyMap[name]
|
||||
return mp.toSlice(), nil
|
||||
}
|
||||
|
||||
// When looking for a user's policies, we also check if the
|
||||
// user and the groups they are member of are enabled.
|
||||
if u, ok := sys.iamUsersMap[name]; !ok {
|
||||
u, ok := sys.iamUsersMap[name]
|
||||
if !ok {
|
||||
return nil, errNoSuchUser
|
||||
} else if u.Status == statusDisabled {
|
||||
// User is disabled, so we return no policy - this
|
||||
// ensures the request is denied.
|
||||
}
|
||||
|
||||
if !u.IsValid() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var policies []string
|
||||
|
||||
mp := sys.iamUserPolicyMap[name]
|
||||
mp, ok := sys.iamUserPolicyMap[name]
|
||||
if !ok {
|
||||
if u.ParentUser != "" {
|
||||
mp = sys.iamUserPolicyMap[u.ParentUser]
|
||||
}
|
||||
}
|
||||
|
||||
// returned policy could be empty
|
||||
policies = append(policies, mp.toSlice()...)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
|
||||
const (
|
||||
// Lock maintenance interval.
|
||||
lockMaintenanceInterval = 15 * time.Second
|
||||
lockMaintenanceInterval = 1 * time.Minute
|
||||
|
||||
// Lock validity check interval.
|
||||
lockValidityCheckInterval = 30 * time.Second
|
||||
@@ -353,7 +353,7 @@ func startLockMaintenance(ctx context.Context) {
|
||||
break
|
||||
}
|
||||
|
||||
// Initialize a new ticker with 15secs between each ticks.
|
||||
// Initialize a new ticker with 1 minute between each ticks.
|
||||
lkTimer := time.NewTimer(lockMaintenanceInterval)
|
||||
// Stop the timer upon returning.
|
||||
defer lkTimer.Stop()
|
||||
@@ -362,8 +362,7 @@ func startLockMaintenance(ctx context.Context) {
|
||||
|
||||
// Start with random sleep time, so as to avoid
|
||||
// "synchronous checks" between servers
|
||||
duration := time.Duration(r.Float64() * float64(lockMaintenanceInterval))
|
||||
time.Sleep(duration)
|
||||
time.Sleep(time.Duration(r.Float64() * float64(lockMaintenanceInterval)))
|
||||
|
||||
for {
|
||||
// Verifies every minute for locks held more than 2 minutes.
|
||||
|
||||
@@ -56,12 +56,6 @@ const (
|
||||
EnvAuditWebhookAuthToken = "MINIO_AUDIT_WEBHOOK_AUTH_TOKEN"
|
||||
)
|
||||
|
||||
// Inject into config package.
|
||||
func init() {
|
||||
config.Logger.Info = Info
|
||||
config.Logger.LogIf = LogIf
|
||||
}
|
||||
|
||||
// Default KVS for loggerHTTP and loggerAuditHTTP
|
||||
var (
|
||||
DefaultKVS = config.KVS{
|
||||
|
||||
@@ -176,7 +176,7 @@ func (b *bucketMetacache) save(ctx context.Context) error {
|
||||
b.updated = false
|
||||
b.mu.Unlock()
|
||||
|
||||
hr, err := hash.NewReader(tmp, int64(tmp.Len()), "", "", int64(tmp.Len()), false)
|
||||
hr, err := hash.NewReader(tmp, int64(tmp.Len()), "", "", int64(tmp.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import (
|
||||
func renameAllBucketMetacache(epPath string) error {
|
||||
// Rename all previous `.minio.sys/buckets/<bucketname>/.metacache` to
|
||||
// to `.minio.sys/tmp/` for deletion.
|
||||
return readDirFilterFn(pathJoin(epPath, minioMetaBucket, bucketMetaPrefix), func(name string, typ os.FileMode) error {
|
||||
return readDirFn(pathJoin(epPath, minioMetaBucket, bucketMetaPrefix), func(name string, typ os.FileMode) error {
|
||||
if typ == os.ModeDir {
|
||||
tmpMetacacheOld := pathJoin(epPath, minioMetaTmpBucket+"-old", mustGetUUID())
|
||||
if err := renameAll(pathJoin(epPath, minioMetaBucket, metacachePrefixForID(name, slashSeparator)),
|
||||
|
||||
+10
-2
@@ -672,7 +672,7 @@ func (er *erasureObjects) listPath(ctx context.Context, o listPathOptions) (entr
|
||||
return nil
|
||||
}
|
||||
o.debugln(color.Green("listPath:")+" saving block", b.n, "to", o.objectPath(b.n))
|
||||
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)), false)
|
||||
r, err := hash.NewReader(bytes.NewReader(b.data), int64(len(b.data)), "", "", int64(len(b.data)))
|
||||
logger.LogIf(ctx, err)
|
||||
custom := b.headerKV()
|
||||
_, err = er.putObject(ctx, minioMetaBucket, o.objectPath(b.n), NewPutObjReader(r), ObjectOptions{
|
||||
@@ -782,7 +782,13 @@ type listPathRawOptions struct {
|
||||
disks []StorageAPI
|
||||
bucket, path string
|
||||
recursive bool
|
||||
|
||||
// Only return results with this prefix.
|
||||
filterPrefix string
|
||||
|
||||
// Forward to this prefix before returning results.
|
||||
forwardTo string
|
||||
|
||||
// Minimum number of good disks to continue.
|
||||
// An error will be returned if this many disks returned an error.
|
||||
minDisks int
|
||||
@@ -836,7 +842,9 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
||||
BaseDir: opts.path,
|
||||
Recursive: opts.recursive,
|
||||
ReportNotFound: opts.reportNotFound,
|
||||
FilterPrefix: opts.filterPrefix}, w)
|
||||
FilterPrefix: opts.filterPrefix,
|
||||
ForwardTo: opts.forwardTo,
|
||||
}, w)
|
||||
w.CloseWithError(werr)
|
||||
if werr != io.EOF && werr != nil && werr.Error() != errFileNotFound.Error() && werr.Error() != errVolumeNotFound.Error() {
|
||||
logger.LogIf(ctx, werr)
|
||||
|
||||
+27
-10
@@ -19,7 +19,6 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -30,6 +29,7 @@ import (
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
xioutil "github.com/minio/minio/pkg/ioutil"
|
||||
)
|
||||
|
||||
// WalkDirOptions provides options for WalkDir operations.
|
||||
@@ -49,6 +49,9 @@ type WalkDirOptions struct {
|
||||
// FilterPrefix will only return results with given prefix within folder.
|
||||
// Should never contain a slash.
|
||||
FilterPrefix string
|
||||
|
||||
// ForwardTo will forward to the given object path.
|
||||
ForwardTo string
|
||||
}
|
||||
|
||||
// WalkDir will traverse a directory and return all entries found.
|
||||
@@ -88,7 +91,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
// Fast exit track to check if we are listing an object with
|
||||
// a trailing slash, this will avoid to list the object content.
|
||||
if HasSuffix(opts.BaseDir, SlashSeparator) {
|
||||
metadata, err := ioutil.ReadFile(pathJoin(volumeDir,
|
||||
metadata, err := xioutil.ReadFile(pathJoin(volumeDir,
|
||||
opts.BaseDir[:len(opts.BaseDir)-1]+globalDirSuffix,
|
||||
xlStorageFormatFile))
|
||||
if err == nil {
|
||||
@@ -107,6 +110,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
}
|
||||
|
||||
prefix := opts.FilterPrefix
|
||||
forward := opts.ForwardTo
|
||||
var scanDir func(path string) error
|
||||
scanDir = func(current string) error {
|
||||
entries, err := s.ListDir(ctx, opts.Bucket, current, -1)
|
||||
@@ -126,6 +130,9 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
if len(prefix) > 0 && !strings.HasPrefix(entry, prefix) {
|
||||
continue
|
||||
}
|
||||
if len(forward) > 0 && entry < forward {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(entry, slashSeparator) {
|
||||
if strings.HasSuffix(entry, globalDirSuffixWithSlash) {
|
||||
// Add without extension so it is sorted correctly.
|
||||
@@ -144,7 +151,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
// If root was an object return it as such.
|
||||
if HasSuffix(entry, xlStorageFormatFile) {
|
||||
var meta metaCacheEntry
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
@@ -159,7 +166,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
// Check legacy.
|
||||
if HasSuffix(entry, xlStorageFormatFileV1) {
|
||||
var meta metaCacheEntry
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, current, entry))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
continue
|
||||
@@ -177,6 +184,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
sort.Strings(entries)
|
||||
dirStack := make([]string, 0, 5)
|
||||
prefix = "" // Remove prefix after first level.
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry == "" {
|
||||
continue
|
||||
@@ -189,8 +197,11 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
out <- metaCacheEntry{name: pop}
|
||||
if opts.Recursive {
|
||||
// Scan folder we found. Should be in correct sort order where we are.
|
||||
err := scanDir(pop)
|
||||
logger.LogIf(ctx, err)
|
||||
forward = ""
|
||||
if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, pop) {
|
||||
forward = strings.TrimPrefix(opts.ForwardTo, pop)
|
||||
}
|
||||
logger.LogIf(ctx, scanDir(pop))
|
||||
}
|
||||
dirStack = dirStack[:len(dirStack)-1]
|
||||
}
|
||||
@@ -202,7 +213,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
meta.name = meta.name[:len(meta.name)-1] + globalDirSuffixWithSlash
|
||||
}
|
||||
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
||||
switch {
|
||||
case err == nil:
|
||||
// It was an object
|
||||
@@ -211,7 +222,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
}
|
||||
out <- meta
|
||||
case osIsNotExist(err):
|
||||
meta.metadata, err = ioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
|
||||
meta.metadata, err = xioutil.ReadFile(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1))
|
||||
if err == nil {
|
||||
// Maybe rename? Would make it inconsistent across disks though.
|
||||
// os.Rename(pathJoin(volumeDir, meta.name, xlStorageFormatFileV1), pathJoin(volumeDir, meta.name, xlStorageFormatFile))
|
||||
@@ -239,8 +250,11 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
out <- metaCacheEntry{name: pop}
|
||||
if opts.Recursive {
|
||||
// Scan folder we found. Should be in correct sort order where we are.
|
||||
err := scanDir(pop)
|
||||
logger.LogIf(ctx, err)
|
||||
forward = ""
|
||||
if len(opts.ForwardTo) > 0 && strings.HasPrefix(opts.ForwardTo, pop) {
|
||||
forward = strings.TrimPrefix(opts.ForwardTo, pop)
|
||||
}
|
||||
logger.LogIf(ctx, scanDir(pop))
|
||||
}
|
||||
dirStack = dirStack[:len(dirStack)-1]
|
||||
}
|
||||
@@ -267,6 +281,7 @@ func (client *storageRESTClient) WalkDir(ctx context.Context, opts WalkDirOption
|
||||
values.Set(storageRESTRecursive, strconv.FormatBool(opts.Recursive))
|
||||
values.Set(storageRESTReportNotFound, strconv.FormatBool(opts.ReportNotFound))
|
||||
values.Set(storageRESTPrefixFilter, opts.FilterPrefix)
|
||||
values.Set(storageRESTForwardFilter, opts.ForwardTo)
|
||||
respBody, err := client.call(ctx, storageRESTMethodWalkDir, values, nil, -1)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
@@ -299,6 +314,7 @@ func (s *storageRESTServer) WalkDirHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
|
||||
prefix := r.URL.Query().Get(storageRESTPrefixFilter)
|
||||
forward := r.URL.Query().Get(storageRESTForwardFilter)
|
||||
writer := streamHTTPResponse(w)
|
||||
writer.CloseWithError(s.storage.WalkDir(r.Context(), WalkDirOptions{
|
||||
Bucket: volume,
|
||||
@@ -306,5 +322,6 @@ func (s *storageRESTServer) WalkDirHandler(w http.ResponseWriter, r *http.Reques
|
||||
Recursive: recursive,
|
||||
ReportNotFound: reportNotFound,
|
||||
FilterPrefix: prefix,
|
||||
ForwardTo: forward,
|
||||
}, writer))
|
||||
}
|
||||
|
||||
+1
-1
@@ -145,7 +145,7 @@ func (m *metacache) worthKeeping(currentCycle uint64) bool {
|
||||
// Cycle is somehow bigger.
|
||||
return false
|
||||
case cache.finished() && time.Since(cache.lastHandout) > 48*time.Hour:
|
||||
// Keep only for 2 days. Fallback if crawler is clogged.
|
||||
// Keep only for 2 days. Fallback if scanner is clogged.
|
||||
return false
|
||||
case cache.finished() && currentCycle >= dataUsageUpdateDirCycles && cache.startedCycle < currentCycle-dataUsageUpdateDirCycles:
|
||||
// Cycle is too old to be valuable.
|
||||
|
||||
+15
-1
@@ -78,6 +78,7 @@ const (
|
||||
inflightTotal MetricName = "inflight_total"
|
||||
limitTotal MetricName = "limit_total"
|
||||
missedTotal MetricName = "missed_total"
|
||||
waitingTotal MetricName = "waiting_total"
|
||||
objectTotal MetricName = "object_total"
|
||||
offlineTotal MetricName = "offline_total"
|
||||
onlineTotal MetricName = "online_total"
|
||||
@@ -386,10 +387,19 @@ func getS3RequestsInFlightMD() MetricDescription {
|
||||
Namespace: s3MetricNamespace,
|
||||
Subsystem: requestsSubsystem,
|
||||
Name: inflightTotal,
|
||||
Help: "Total number of S3 requests currently in flight.",
|
||||
Help: "Total number of S3 requests currently in flight",
|
||||
Type: gaugeMetric,
|
||||
}
|
||||
}
|
||||
func getS3RequestsInQueueMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: s3MetricNamespace,
|
||||
Subsystem: requestsSubsystem,
|
||||
Name: waitingTotal,
|
||||
Help: "Number of S3 requests in the waiting queue",
|
||||
Type: counterMetric,
|
||||
}
|
||||
}
|
||||
func getS3RequestsTotalMD() MetricDescription {
|
||||
return MetricDescription{
|
||||
Namespace: s3MetricNamespace,
|
||||
@@ -939,6 +949,10 @@ func getHTTPMetrics() MetricsGroup {
|
||||
Metrics: []Metric{},
|
||||
initialize: func(ctx context.Context, metrics *MetricsGroup) {
|
||||
httpStats := globalHTTPStats.toServerHTTPStats()
|
||||
metrics.Metrics = append(metrics.Metrics, Metric{
|
||||
Description: getS3RequestsInQueueMD(),
|
||||
Value: float64(httpStats.S3RequestsInQueue),
|
||||
})
|
||||
for api, value := range httpStats.CurrentS3Requests.APIStats {
|
||||
metrics.Metrics = append(metrics.Metrics, Metric{
|
||||
Description: getS3RequestsInFlightMD(),
|
||||
|
||||
+1
-1
@@ -419,7 +419,7 @@ func networkMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
}
|
||||
|
||||
// Populates prometheus with bucket usage metrics, this metrics
|
||||
// is only enabled if crawler is enabled.
|
||||
// is only enabled if scanner is enabled.
|
||||
func bucketUsageMetricsPrometheus(ch chan<- prometheus.Metric) {
|
||||
objLayer := newObjectLayerFn()
|
||||
// Service not initialized yet
|
||||
|
||||
+35
-33
@@ -48,6 +48,7 @@ import (
|
||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/etag"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
@@ -188,6 +189,10 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
|
||||
isSuffixLength = true
|
||||
}
|
||||
|
||||
if length > 0 {
|
||||
length--
|
||||
}
|
||||
|
||||
rs := &HTTPRangeSpec{
|
||||
IsSuffixLength: isSuffixLength,
|
||||
Start: offset,
|
||||
@@ -1021,7 +1026,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
srcInfo.metadataOnly = false
|
||||
}
|
||||
|
||||
var reader io.Reader
|
||||
var reader io.Reader = gr
|
||||
|
||||
// Set the actual size to the compressed/decrypted size if encrypted.
|
||||
actualSize, err := srcInfo.GetActualSize()
|
||||
@@ -1053,15 +1058,15 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
// avoid copying them in target object.
|
||||
crypto.RemoveInternalEntries(srcInfo.UserDefined)
|
||||
|
||||
s2c := newS2CompressReader(gr, actualSize)
|
||||
s2c := newS2CompressReader(reader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, reader)
|
||||
length = -1
|
||||
} else {
|
||||
reader = gr
|
||||
}
|
||||
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1158,11 +1163,13 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
if isTargetEncrypted {
|
||||
reader, objEncKey, err = newEncryptReader(srcInfo.Reader, newKey, dstBucket, dstObject, encMetadata, sseS3)
|
||||
var encReader io.Reader
|
||||
encReader, objEncKey, err = newEncryptReader(srcInfo.Reader, newKey, dstBucket, dstObject, encMetadata, sseS3)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
reader = etag.Wrap(encReader, srcInfo.Reader)
|
||||
}
|
||||
|
||||
if isSourceEncrypted {
|
||||
@@ -1172,7 +1179,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, targetSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1453,13 +1460,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
var (
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader = r.Body
|
||||
s3Err APIErrorCode
|
||||
putObject = objectAPI.PutObject
|
||||
)
|
||||
reader = r.Body
|
||||
|
||||
// Check if put is allowed
|
||||
if s3Err = isPutActionAllowed(ctx, rAuthType, bucket, object, r, iampolicy.PutObjectAction); s3Err != ErrNone {
|
||||
@@ -1505,13 +1511,12 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
actualSize := size
|
||||
|
||||
if objectAPI.IsCompressionSupported() && isCompressible(r.Header, object) && size > 0 {
|
||||
// Storing the compression metadata.
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(size, 10)
|
||||
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1520,13 +1525,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
// Set compression metrics.
|
||||
s2c := newS2CompressReader(actualReader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, actualReader)
|
||||
size = -1 // Since compressed size is un-predictable.
|
||||
md5hex = "" // Do not try to verify the content.
|
||||
sha256hex = ""
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1597,7 +1602,7 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, wantSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -1995,8 +2000,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
return
|
||||
}
|
||||
|
||||
partInfo, err := core.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID,
|
||||
gr, length, "", "", dstOpts.ServerSideEncryption)
|
||||
partInfo, err := core.PutObjectPart(ctx, dstBucket, dstObject, uploadID, partID, gr, length, "", "", dstOpts.ServerSideEncryption)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2011,7 +2015,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
}
|
||||
|
||||
actualPartSize = length
|
||||
var reader io.Reader
|
||||
var reader io.Reader = etag.NewReader(gr, nil)
|
||||
|
||||
mi, err := objectAPI.GetMultipartInfo(ctx, dstBucket, dstObject, uploadID, dstOpts)
|
||||
if err != nil {
|
||||
@@ -2023,15 +2027,13 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
_, isCompressed := mi.UserDefined[ReservedMetadataPrefix+"compression"]
|
||||
// Compress only if the compression is enabled during initial multipart.
|
||||
if isCompressed {
|
||||
s2c := newS2CompressReader(gr, actualPartSize)
|
||||
s2c := newS2CompressReader(reader, actualPartSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, reader)
|
||||
length = -1
|
||||
} else {
|
||||
reader = gr
|
||||
}
|
||||
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualPartSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, length, "", "", actualPartSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2073,11 +2075,12 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
copy(objectEncryptionKey[:], key)
|
||||
|
||||
partEncryptionKey := objectEncryptionKey.DerivePartKey(uint32(partID))
|
||||
reader, err = sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:]})
|
||||
encReader, err := sio.EncryptReader(reader, sio.Config{Key: partEncryptionKey[:]})
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
reader = etag.Wrap(encReader, reader)
|
||||
|
||||
wantSize := int64(-1)
|
||||
if length >= 0 {
|
||||
@@ -2085,7 +2088,7 @@ func (api objectAPIHandlers) CopyObjectPartHandler(w http.ResponseWriter, r *htt
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
|
||||
srcInfo.Reader, err = hash.NewReader(reader, wantSize, "", "", actualPartSize, globalCLIContext.StrictS3Compat)
|
||||
srcInfo.Reader, err = hash.NewReader(reader, wantSize, "", "", actualPartSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2205,12 +2208,11 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
var (
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader
|
||||
md5hex = hex.EncodeToString(md5Bytes)
|
||||
sha256hex = ""
|
||||
reader io.Reader = r.Body
|
||||
s3Error APIErrorCode
|
||||
)
|
||||
reader = r.Body
|
||||
if s3Error = isPutActionAllowed(ctx, rAuthType, bucket, object, r, iampolicy.PutObjectAction); s3Error != ErrNone {
|
||||
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(s3Error), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2267,7 +2269,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
_, isCompressed := mi.UserDefined[ReservedMetadataPrefix+"compression"]
|
||||
|
||||
if objectAPI.IsCompressionSupported() && isCompressed {
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
actualReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2276,13 +2278,13 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
// Set compression metrics.
|
||||
s2c := newS2CompressReader(actualReader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
reader = etag.Wrap(s2c, actualReader)
|
||||
size = -1 // Since compressed size is un-predictable.
|
||||
md5hex = "" // Do not try to verify the content.
|
||||
sha256hex = ""
|
||||
}
|
||||
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(reader, size, md5hex, sha256hex, actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
@@ -2339,7 +2341,7 @@ func (api objectAPIHandlers) PutObjectPartHandler(w http.ResponseWriter, r *http
|
||||
wantSize = info.EncryptedSize()
|
||||
}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, wantSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(etag.Wrap(reader, hashReader), wantSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
+48
-7
@@ -29,11 +29,15 @@ func readDir(dirPath string) (entries []string, err error) {
|
||||
return readDirN(dirPath, -1)
|
||||
}
|
||||
|
||||
// readDir applies the filter function on each entries at dirPath, doesn't recurse into
|
||||
// the directory itself.
|
||||
func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) error) error {
|
||||
// readDirFn applies the fn() function on each entries at dirPath, doesn't recurse into
|
||||
// the directory itself, if the dirPath doesn't exist this function doesn't return
|
||||
// an error.
|
||||
func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error) error {
|
||||
d, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
if osErrToFileErr(err) == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
defer d.Close()
|
||||
@@ -46,9 +50,31 @@ func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) e
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return osErrToFileErr(err)
|
||||
err = osErrToFileErr(err)
|
||||
if err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
for _, fi := range fis {
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
fi, err = os.Stat(pathJoin(dirPath, fi.Name()))
|
||||
if err != nil {
|
||||
// It got deleted in the meantime, not found
|
||||
// or returns too many symlinks ignore this
|
||||
// file/directory.
|
||||
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
|
||||
isSysErrTooManySymlinks(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Ignore symlinked directories.
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err = filter(fi.Name(), fi.Mode()); err == errDoneForNow {
|
||||
// filtering requested to return by caller.
|
||||
return nil
|
||||
@@ -90,11 +116,26 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
}
|
||||
}
|
||||
for _, fi := range fis {
|
||||
// Not need to follow symlink.
|
||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
fi, err = os.Stat(pathJoin(dirPath, fi.Name()))
|
||||
if err != nil {
|
||||
// It got deleted in the meantime, not found
|
||||
// or returns too many symlinks ignore this
|
||||
// file/directory.
|
||||
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
|
||||
isSysErrTooManySymlinks(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Ignore symlinked directories.
|
||||
if fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if fi.Mode().IsDir() {
|
||||
|
||||
if fi.IsDir() {
|
||||
// Append SlashSeparator instead of "\" so that sorting is achieved as expected.
|
||||
entries = append(entries, fi.Name()+SlashSeparator)
|
||||
} else if fi.Mode().IsRegular() {
|
||||
|
||||
@@ -149,7 +149,8 @@ func setupTestReadDirSymlink(t *testing.T) (testResults []result) {
|
||||
}
|
||||
// Add to entries.
|
||||
entries = append(entries, name1)
|
||||
// Symlinks are ignored.
|
||||
// Symlinks are preserved for regular files
|
||||
entries = append(entries, name2)
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Join(dir, "mydir"), 0777); err != nil {
|
||||
t.Fatalf("Unable to create \"mydir\", %s", err)
|
||||
|
||||
+54
-16
@@ -84,11 +84,15 @@ func readDir(dirPath string) (entries []string, err error) {
|
||||
return readDirN(dirPath, -1)
|
||||
}
|
||||
|
||||
// readDir applies the filter function on each entries at dirPath, doesn't recurse into
|
||||
// the directory itself.
|
||||
func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) error) error {
|
||||
// readDirFn applies the fn() function on each entries at dirPath, doesn't recurse into
|
||||
// the directory itself, if the dirPath doesn't exist this function doesn't return
|
||||
// an error.
|
||||
func readDirFn(dirPath string, fn func(name string, typ os.FileMode) error) error {
|
||||
f, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
if osErrToFileErr(err) == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
defer f.Close()
|
||||
@@ -103,7 +107,11 @@ func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) e
|
||||
nbuf, err = syscall.ReadDirent(int(f.Fd()), buf)
|
||||
if err != nil {
|
||||
if isSysErrNotDir(err) {
|
||||
return errFileNotFound
|
||||
return nil
|
||||
}
|
||||
err = osErrToFileErr(err)
|
||||
if err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -119,11 +127,32 @@ func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) e
|
||||
if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) {
|
||||
continue
|
||||
}
|
||||
if typ&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
|
||||
// Fallback for filesystems (like old XFS) that don't
|
||||
// support Dirent.Type and have DT_UNKNOWN (0) there
|
||||
// instead.
|
||||
if typ == unexpectedFileMode || typ&os.ModeSymlink == os.ModeSymlink {
|
||||
fi, err := os.Stat(pathJoin(dirPath, string(name)))
|
||||
if err != nil {
|
||||
// It got deleted in the meantime, not found
|
||||
// or returns too many symlinks ignore this
|
||||
// file/directory.
|
||||
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
|
||||
isSysErrTooManySymlinks(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Ignore symlinked directories.
|
||||
if typ&os.ModeSymlink == os.ModeSymlink && fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
typ = fi.Mode() & os.ModeType
|
||||
}
|
||||
if err = filter(string(name), typ); err == errDoneForNow {
|
||||
// filtering requested to return by caller.
|
||||
if err = fn(string(name), typ); err == errDoneForNow {
|
||||
// fn() requested to return by caller.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -158,7 +187,7 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
if isSysErrNotDir(err) {
|
||||
return nil, errFileNotFound
|
||||
}
|
||||
return nil, err
|
||||
return nil, osErrToFileErr(err)
|
||||
}
|
||||
if nbuf <= 0 {
|
||||
break
|
||||
@@ -172,11 +201,12 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
if len(name) == 0 || bytes.Equal(name, []byte{'.'}) || bytes.Equal(name, []byte{'.', '.'}) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Fallback for filesystems (like old XFS) that don't
|
||||
// support Dirent.Type and have DT_UNKNOWN (0) there
|
||||
// instead.
|
||||
if typ == unexpectedFileMode {
|
||||
fi, err := os.Lstat(pathJoin(dirPath, string(name)))
|
||||
if typ == unexpectedFileMode || typ&os.ModeSymlink == os.ModeSymlink {
|
||||
fi, err := os.Stat(pathJoin(dirPath, string(name)))
|
||||
if err != nil {
|
||||
// It got deleted in the meantime, not found
|
||||
// or returns too many symlinks ignore this
|
||||
@@ -187,22 +217,30 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ignore symlinked directories.
|
||||
if typ&os.ModeSymlink == os.ModeSymlink && fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
typ = fi.Mode() & os.ModeType
|
||||
}
|
||||
if typ&os.ModeSymlink == os.ModeSymlink {
|
||||
continue
|
||||
}
|
||||
|
||||
var nameStr string
|
||||
if typ.IsRegular() {
|
||||
entries = append(entries, string(name))
|
||||
nameStr = string(name)
|
||||
} else if typ.IsDir() {
|
||||
// Use temp buffer to append a slash to avoid string concat.
|
||||
tmp = tmp[:len(name)+1]
|
||||
copy(tmp, name)
|
||||
tmp[len(tmp)-1] = '/' // SlashSeparator
|
||||
entries = append(entries, string(tmp))
|
||||
nameStr = string(tmp)
|
||||
}
|
||||
|
||||
count--
|
||||
entries = append(entries, nameStr)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+77
-15
@@ -29,15 +29,28 @@ func readDir(dirPath string) (entries []string, err error) {
|
||||
return readDirN(dirPath, -1)
|
||||
}
|
||||
|
||||
// readDir applies the filter function on each entries at dirPath, doesn't recurse into
|
||||
// the directory itself.
|
||||
func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) error) error {
|
||||
// readDirFn applies the fn() function on each entries at dirPath, doesn't recurse into
|
||||
// the directory itself, if the dirPath doesn't exist this function doesn't return
|
||||
// an error.
|
||||
func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error) error {
|
||||
f, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
if osErrToFileErr(err) == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Check if file or dir. This is the quickest way.
|
||||
// Do not remove this check, on windows syscall.FindNextFile
|
||||
// would throw an exception if Fd() points to a file
|
||||
// instead of a directory, we need to quickly fail
|
||||
// in such situations - this workadound is expected.
|
||||
if _, err = f.Seek(0, io.SeekStart); err == nil {
|
||||
return errFileNotFound
|
||||
}
|
||||
|
||||
data := &syscall.Win32finddata{}
|
||||
for {
|
||||
e := syscall.FindNextFile(syscall.Handle(f.Fd()), data)
|
||||
@@ -45,31 +58,58 @@ func readDirFilterFn(dirPath string, filter func(name string, typ os.FileMode) e
|
||||
if e == syscall.ERROR_NO_MORE_FILES {
|
||||
break
|
||||
} else {
|
||||
return osErrToFileErr(&os.PathError{
|
||||
if isSysErrPathNotFound(e) {
|
||||
return nil
|
||||
}
|
||||
err = osErrToFileErr(&os.PathError{
|
||||
Op: "FindNextFile",
|
||||
Path: dirPath,
|
||||
Err: e,
|
||||
})
|
||||
if err == errFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
name := syscall.UTF16ToString(data.FileName[0:])
|
||||
if name == "" || name == "." || name == ".." { // Useless names
|
||||
continue
|
||||
}
|
||||
if data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var typ os.FileMode = 0 // regular file
|
||||
if data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
||||
switch {
|
||||
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
|
||||
// Reparse point is a symlink
|
||||
fi, err := os.Stat(pathJoin(dirPath, string(name)))
|
||||
if err != nil {
|
||||
// It got deleted in the meantime, not found
|
||||
// or returns too many symlinks ignore this
|
||||
// file/directory.
|
||||
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
|
||||
isSysErrTooManySymlinks(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
// Ignore symlinked directories.
|
||||
continue
|
||||
}
|
||||
|
||||
typ = fi.Mode()
|
||||
case data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
|
||||
typ = os.ModeDir
|
||||
}
|
||||
|
||||
if e = filter(name, typ); e == errDoneForNow {
|
||||
// filtering requested to return by caller.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
// Return N entries at the directory dirPath. If count is -1, return all entries
|
||||
@@ -81,10 +121,14 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
defer f.Close()
|
||||
|
||||
// Check if file or dir. This is the quickest way.
|
||||
_, err = f.Seek(0, io.SeekStart)
|
||||
if err == nil {
|
||||
// Do not remove this check, on windows syscall.FindNextFile
|
||||
// would throw an exception if Fd() points to a file
|
||||
// instead of a directory, we need to quickly fail
|
||||
// in such situations - this workadound is expected.
|
||||
if _, err = f.Seek(0, io.SeekStart); err == nil {
|
||||
return nil, errFileNotFound
|
||||
}
|
||||
|
||||
data := &syscall.Win32finddata{}
|
||||
handle := syscall.Handle(f.Fd())
|
||||
|
||||
@@ -106,15 +150,33 @@ func readDirN(dirPath string, count int) (entries []string, err error) {
|
||||
if name == "" || name == "." || name == ".." { // Useless names
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
|
||||
continue
|
||||
// Reparse point is a symlink
|
||||
fi, err := os.Stat(pathJoin(dirPath, string(name)))
|
||||
if err != nil {
|
||||
// It got deleted in the meantime, not found
|
||||
// or returns too many symlinks ignore this
|
||||
// file/directory.
|
||||
if osIsNotExist(err) || isSysErrPathNotFound(err) ||
|
||||
isSysErrTooManySymlinks(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
// directory symlinks are ignored.
|
||||
continue
|
||||
}
|
||||
case data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
|
||||
entries = append(entries, name+SlashSeparator)
|
||||
default:
|
||||
entries = append(entries, name)
|
||||
name = name + SlashSeparator
|
||||
}
|
||||
|
||||
count--
|
||||
entries = append(entries, name)
|
||||
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
|
||||
+1
-10
@@ -167,8 +167,7 @@ func isServerResolvable(endpoint Endpoint, timeout time.Duration) error {
|
||||
var tlsConfig *tls.Config
|
||||
if globalIsTLS {
|
||||
tlsConfig = &tls.Config{
|
||||
ServerName: endpoint.Hostname(),
|
||||
RootCAs: globalRootCAs,
|
||||
RootCAs: globalRootCAs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,14 +205,6 @@ func isServerResolvable(endpoint Endpoint, timeout time.Duration) error {
|
||||
}
|
||||
xhttp.DrainBody(resp.Body)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return StorageErr(resp.Status)
|
||||
}
|
||||
|
||||
if resp.Header.Get(xhttp.MinIOServerStatus) == unavailable {
|
||||
return StorageErr(unavailable)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -311,6 +311,8 @@ func initServer(ctx context.Context, newObject ObjectLayer) error {
|
||||
if errors.Is(err, errDiskNotFound) ||
|
||||
errors.Is(err, errConfigNotFound) ||
|
||||
errors.Is(err, context.DeadlineExceeded) ||
|
||||
errors.Is(err, errErasureWriteQuorum) ||
|
||||
errors.Is(err, errErasureReadQuorum) ||
|
||||
errors.As(err, &rquorum) ||
|
||||
errors.As(err, &wquorum) ||
|
||||
isErrBucketNotFound(err) {
|
||||
@@ -355,14 +357,12 @@ func initAllSubsystems(ctx context.Context, newObject ObjectLayer) (err error) {
|
||||
for index := range buckets {
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
if _, berr := newObject.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true}); berr != nil {
|
||||
return fmt.Errorf("Unable to list buckets to heal: %w", berr)
|
||||
}
|
||||
return nil
|
||||
_, berr := newObject.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true})
|
||||
return berr
|
||||
}, index)
|
||||
}
|
||||
if err := g.WaitErr(); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Unable to list buckets to heal: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,6 +371,8 @@ func initAllSubsystems(ctx context.Context, newObject ObjectLayer) (err error) {
|
||||
if errors.Is(err, errDiskNotFound) ||
|
||||
errors.Is(err, errConfigNotFound) ||
|
||||
errors.Is(err, context.DeadlineExceeded) ||
|
||||
errors.Is(err, errErasureWriteQuorum) ||
|
||||
errors.Is(err, errErasureReadQuorum) ||
|
||||
errors.As(err, &rquorum) ||
|
||||
errors.As(err, &wquorum) ||
|
||||
isErrBucketNotFound(err) {
|
||||
@@ -509,7 +511,7 @@ func serverMain(ctx *cli.Context) {
|
||||
initBackgroundExpiry(GlobalContext, newObject)
|
||||
}
|
||||
|
||||
initDataCrawler(GlobalContext, newObject)
|
||||
initDataScanner(GlobalContext, newObject)
|
||||
|
||||
if err = initServer(GlobalContext, newObject); err != nil {
|
||||
var cerr config.Err
|
||||
|
||||
@@ -19,6 +19,7 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/cmd/logger"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
"github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the
|
||||
@@ -65,10 +65,9 @@ func getContentSha256Cksum(r *http.Request, stype serviceType) string {
|
||||
if err != nil {
|
||||
logger.CriticalIf(GlobalContext, err)
|
||||
}
|
||||
sum256 := sha256.New()
|
||||
sum256.Write(payload)
|
||||
sum256 := sha256.Sum256(payload)
|
||||
r.Body = ioutil.NopCloser(bytes.NewReader(payload))
|
||||
return hex.EncodeToString(sum256.Sum(nil))
|
||||
return hex.EncodeToString(sum256[:])
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
+1
-1
@@ -26,6 +26,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
@@ -38,7 +39,6 @@ import (
|
||||
"github.com/minio/minio-go/v7/pkg/s3utils"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// AWS Signature Version '4' constants.
|
||||
|
||||
+10
-1
@@ -16,6 +16,8 @@
|
||||
|
||||
package cmd
|
||||
|
||||
import "errors"
|
||||
|
||||
// errUnexpected - unexpected error, requires manual intervention.
|
||||
var errUnexpected = StorageErr("unexpected error, please report this issue at https://github.com/minio/minio/issues")
|
||||
|
||||
@@ -104,6 +106,13 @@ var errLessData = StorageErr("less data available than what was requested")
|
||||
// errMoreData = returned when more data was sent by the caller than what it was supposed to.
|
||||
var errMoreData = StorageErr("more data was sent than what was advertised")
|
||||
|
||||
// indicates readDirFn to return without further applying the fn()
|
||||
var errDoneForNow = errors.New("done for now")
|
||||
|
||||
// errSkipFile returned by the fn() for readDirFn() when it needs
|
||||
// to proceed to next entry.
|
||||
var errSkipFile = errors.New("skip this file")
|
||||
|
||||
// StorageErr represents error generated by xlStorage call.
|
||||
type StorageErr string
|
||||
|
||||
@@ -134,7 +143,7 @@ func osErrToFileErr(err error) error {
|
||||
if osIsPermission(err) {
|
||||
return errFileAccessDenied
|
||||
}
|
||||
if isSysErrNotDir(err) {
|
||||
if isSysErrNotDir(err) || isSysErrIsDir(err) {
|
||||
return errFileNotFound
|
||||
}
|
||||
if isSysErrPathNotFound(err) {
|
||||
|
||||
@@ -175,30 +175,22 @@ func (client *storageRESTClient) CrawlAndGetDataUsage(ctx context.Context, cache
|
||||
go func() {
|
||||
pw.CloseWithError(cache.serializeTo(pw))
|
||||
}()
|
||||
defer pr.Close()
|
||||
respBody, err := client.call(ctx, storageRESTMethodCrawlAndGetDataUsage, url.Values{}, pr, -1)
|
||||
defer http.DrainBody(respBody)
|
||||
if err != nil {
|
||||
pr.Close()
|
||||
return cache, err
|
||||
}
|
||||
pr.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
var newCache dataUsageCache
|
||||
var decErr error
|
||||
pr, pw = io.Pipe()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
decErr = newCache.deserialize(pr)
|
||||
pr.CloseWithError(err)
|
||||
pw.CloseWithError(waitForHTTPStream(respBody, pw))
|
||||
}()
|
||||
err = waitForHTTPStream(respBody, pw)
|
||||
pw.CloseWithError(err)
|
||||
if err != nil {
|
||||
return cache, err
|
||||
}
|
||||
wg.Wait()
|
||||
return newCache, decErr
|
||||
err = newCache.deserialize(pr)
|
||||
pr.CloseWithError(err)
|
||||
return newCache, err
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) GetDiskID() (string, error) {
|
||||
|
||||
@@ -72,6 +72,7 @@ const (
|
||||
storageRESTCount = "count"
|
||||
storageRESTMarkerPath = "marker"
|
||||
storageRESTPrefixFilter = "prefix"
|
||||
storageRESTForwardFilter = "forward"
|
||||
storageRESTRecursive = "recursive"
|
||||
storageRESTReportNotFound = "report-notfound"
|
||||
storageRESTBitrotAlgo = "bitrot-algo"
|
||||
|
||||
@@ -21,6 +21,7 @@ package cmd
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"hash"
|
||||
@@ -31,7 +32,6 @@ import (
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
xhttp "github.com/minio/minio/cmd/http"
|
||||
"github.com/minio/minio/pkg/auth"
|
||||
sha256 "github.com/minio/sha256-simd"
|
||||
)
|
||||
|
||||
// Streaming AWS Signature Version '4' constants.
|
||||
|
||||
@@ -160,7 +160,7 @@ func calculateSignedChunkLength(chunkDataSize int64) int64 {
|
||||
}
|
||||
|
||||
func mustGetPutObjReader(t TestErrHandler, data io.Reader, size int64, md5hex, sha256hex string) *PutObjReader {
|
||||
hr, err := hash.NewReader(data, size, md5hex, sha256hex, size, globalCLIContext.StrictS3Compat)
|
||||
hr, err := hash.NewReader(data, size, md5hex, sha256hex, size)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import (
|
||||
"github.com/minio/minio/pkg/env"
|
||||
xnet "github.com/minio/minio/pkg/net"
|
||||
"github.com/minio/selfupdate"
|
||||
_ "github.com/minio/sha256-simd" // Needed for sha256 hash verifier.
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+11
-7
@@ -50,6 +50,7 @@ import (
|
||||
objectlock "github.com/minio/minio/pkg/bucket/object/lock"
|
||||
"github.com/minio/minio/pkg/bucket/policy"
|
||||
"github.com/minio/minio/pkg/bucket/replication"
|
||||
"github.com/minio/minio/pkg/etag"
|
||||
"github.com/minio/minio/pkg/event"
|
||||
"github.com/minio/minio/pkg/handlers"
|
||||
"github.com/minio/minio/pkg/hash"
|
||||
@@ -1233,7 +1234,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
var reader io.Reader = r.Body
|
||||
actualSize := size
|
||||
|
||||
hashReader, err := hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err := hash.NewReader(reader, size, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
@@ -1244,7 +1245,7 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
metadata[ReservedMetadataPrefix+"compression"] = compressionAlgorithmV2
|
||||
metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(actualSize, 10)
|
||||
|
||||
actualReader, err := hash.NewReader(reader, actualSize, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
actualReader, err := hash.NewReader(reader, actualSize, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
@@ -1254,8 +1255,8 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
size = -1 // Since compressed size is un-predictable.
|
||||
s2c := newS2CompressReader(actualReader, actualSize)
|
||||
defer s2c.Close()
|
||||
reader = s2c
|
||||
hashReader, err = hash.NewReader(reader, size, "", "", actualSize, globalCLIContext.StrictS3Compat)
|
||||
reader = etag.Wrap(s2c, actualReader)
|
||||
hashReader, err = hash.NewReader(reader, size, "", "", actualSize)
|
||||
if err != nil {
|
||||
writeWebErrorResponse(w, err)
|
||||
return
|
||||
@@ -1276,15 +1277,18 @@ func (web *webAPIHandlers) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if objectAPI.IsEncryptionSupported() {
|
||||
if _, ok := crypto.IsRequested(r.Header); ok && !HasSuffix(object, SlashSeparator) { // handle SSE requests
|
||||
var objectEncryptionKey crypto.ObjectKey
|
||||
reader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
var (
|
||||
objectEncryptionKey crypto.ObjectKey
|
||||
encReader io.Reader
|
||||
)
|
||||
encReader, objectEncryptionKey, err = EncryptRequest(hashReader, r, bucket, object, metadata)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
}
|
||||
info := ObjectInfo{Size: size}
|
||||
// do not try to verify encrypted content
|
||||
hashReader, err = hash.NewReader(reader, info.EncryptedSize(), "", "", size, globalCLIContext.StrictS3Compat)
|
||||
hashReader, err = hash.NewReader(etag.Wrap(encReader, hashReader), info.EncryptedSize(), "", "", size)
|
||||
if err != nil {
|
||||
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL, guessIsBrowserReq(r))
|
||||
return
|
||||
|
||||
+88
-83
@@ -217,9 +217,35 @@ func newXLStorage(ep Endpoint) (*xlStorage, error) {
|
||||
if env.Get("MINIO_CI_CD", "") != "" {
|
||||
rootDisk = true
|
||||
} else {
|
||||
rootDisk, err = disk.IsRootDisk(path, "/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if IsDocker() || IsKubernetes() {
|
||||
// Start with overlay "/" to check if
|
||||
// possible the path has device id as
|
||||
// "overlay" that would mean the path
|
||||
// is emphemeral and we should treat it
|
||||
// as root disk from the baremetal
|
||||
// terminology.
|
||||
rootDisk, err = disk.IsRootDisk(path, "/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !rootDisk {
|
||||
// No root disk was found, its possible that
|
||||
// path is referenced at "/data" which has
|
||||
// different device ID that points to the original
|
||||
// "/" on the host system, fall back to that instead
|
||||
// to verify of the device id is same.
|
||||
rootDisk, err = disk.IsRootDisk(path, "/data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// On baremetal setups its always "/" is the root disk.
|
||||
rootDisk, err = disk.IsRootDisk(path, "/")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,9 +264,10 @@ func newXLStorage(ep Endpoint) (*xlStorage, error) {
|
||||
return &b
|
||||
},
|
||||
},
|
||||
globalSync: env.Get(config.EnvFSOSync, config.EnableOff) == config.EnableOn,
|
||||
ctx: GlobalContext,
|
||||
rootDisk: rootDisk,
|
||||
globalSync: env.Get(config.EnvFSOSync, config.EnableOff) == config.EnableOn,
|
||||
ctx: GlobalContext,
|
||||
rootDisk: rootDisk,
|
||||
readODirectSupported: true,
|
||||
}
|
||||
|
||||
// Create all necessary bucket folders if possible.
|
||||
@@ -356,7 +383,7 @@ func (s *xlStorage) CrawlAndGetDataUsage(ctx context.Context, cache dataUsageCac
|
||||
return sizeSummary{}, errSkipFile
|
||||
}
|
||||
|
||||
buf, err := ioutil.ReadFile(item.Path)
|
||||
buf, err := xioutil.ReadFile(item.Path)
|
||||
if err != nil {
|
||||
if intDataUpdateTracker.debug {
|
||||
console.Debugf(color.Green("crawlBucket:")+" object path missing: %v: %w\n", item.Path, err)
|
||||
@@ -513,7 +540,7 @@ func (s *xlStorage) GetDiskID() (string, error) {
|
||||
return diskID, nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(formatFile)
|
||||
b, err := xioutil.ReadFile(formatFile)
|
||||
if err != nil {
|
||||
// If the disk is still not initialized.
|
||||
if osIsNotExist(err) {
|
||||
@@ -801,7 +828,7 @@ func (s *xlStorage) WalkVersions(ctx context.Context, volume, dirPath, marker st
|
||||
},
|
||||
}
|
||||
} else {
|
||||
xlMetaBuf, err := ioutil.ReadFile(pathJoin(volumeDir, walkResult.entry, xlStorageFormatFile))
|
||||
xlMetaBuf, err := xioutil.ReadFile(pathJoin(volumeDir, walkResult.entry, xlStorageFormatFile))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -1121,12 +1148,12 @@ func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID str
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
func (s *xlStorage) readAllData(volumeDir, filePath string, requireDirectIO bool) (buf []byte, err error) {
|
||||
func (s *xlStorage) readAllData(volumeDir string, filePath string, requireDirectIO bool) (buf []byte, err error) {
|
||||
var f *os.File
|
||||
if requireDirectIO {
|
||||
f, err = disk.OpenFileDirectIO(filePath, os.O_RDONLY, 0666)
|
||||
f, err = disk.OpenFileDirectIO(filePath, readMode, 0666)
|
||||
} else {
|
||||
f, err = os.Open(filePath)
|
||||
f, err = os.OpenFile(filePath, readMode, 0)
|
||||
}
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
@@ -1149,6 +1176,13 @@ func (s *xlStorage) readAllData(volumeDir, filePath string, requireDirectIO bool
|
||||
} else if isSysErrTooManyFiles(err) {
|
||||
return nil, errTooManyOpenFiles
|
||||
} else if isSysErrInvalidArg(err) {
|
||||
st, _ := os.Lstat(filePath)
|
||||
if st != nil && st.IsDir() {
|
||||
// Linux returns InvalidArg for directory O_DIRECT
|
||||
// we need to keep this fallback code to return correct
|
||||
// errors upwards.
|
||||
return nil, errFileNotFound
|
||||
}
|
||||
return nil, errUnsupportedDisk
|
||||
}
|
||||
return nil, err
|
||||
@@ -1156,7 +1190,7 @@ func (s *xlStorage) readAllData(volumeDir, filePath string, requireDirectIO bool
|
||||
|
||||
atomic.AddInt32(&s.activeIOCount, 1)
|
||||
rd := &odirectReader{f, nil, nil, true, true, s, nil}
|
||||
defer rd.Close()
|
||||
defer rd.Close() // activeIOCount is decremented in Close()
|
||||
|
||||
buf, err = ioutil.ReadAll(rd)
|
||||
if err != nil {
|
||||
@@ -1172,11 +1206,6 @@ func (s *xlStorage) readAllData(volumeDir, filePath string, requireDirectIO bool
|
||||
// This API is meant to be used on files which have small memory footprint, do
|
||||
// not use this on large files as it would cause server to crash.
|
||||
func (s *xlStorage) ReadAll(ctx context.Context, volume string, path string) (buf []byte, err error) {
|
||||
atomic.AddInt32(&s.activeIOCount, 1)
|
||||
defer func() {
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1188,34 +1217,8 @@ func (s *xlStorage) ReadAll(ctx context.Context, volume string, path string) (bu
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err = ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
// Check if the object doesn't exist because its bucket
|
||||
// is missing in order to return the correct error.
|
||||
_, err = os.Lstat(volumeDir)
|
||||
if err != nil && osIsNotExist(err) {
|
||||
return nil, errVolumeNotFound
|
||||
}
|
||||
return nil, errFileNotFound
|
||||
} else if osIsPermission(err) {
|
||||
return nil, errFileAccessDenied
|
||||
} else if isSysErrNotDir(err) || isSysErrIsDir(err) {
|
||||
return nil, errFileNotFound
|
||||
} else if isSysErrHandleInvalid(err) {
|
||||
// This case is special and needs to be handled for windows.
|
||||
return nil, errFileNotFound
|
||||
} else if isSysErrIO(err) {
|
||||
return nil, errFaultyDisk
|
||||
} else if isSysErrTooManyFiles(err) {
|
||||
return nil, errTooManyOpenFiles
|
||||
} else if isSysErrInvalidArg(err) {
|
||||
return nil, errUnsupportedDisk
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
requireDirectIO := globalStorageClass.GetDMA() == storageclass.DMAReadWrite && s.readODirectSupported
|
||||
return s.readAllData(volumeDir, filePath, requireDirectIO)
|
||||
}
|
||||
|
||||
// ReadFile reads exactly len(buf) bytes into buf. It returns the
|
||||
@@ -1333,42 +1336,23 @@ func (s *xlStorage) openFile(volume, path string, mode int) (f *os.File, err err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stat a volume entry.
|
||||
_, err = os.Lstat(volumeDir)
|
||||
if err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return nil, errVolumeNotFound
|
||||
} else if isSysErrIO(err) {
|
||||
return nil, errFaultyDisk
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filePath := pathJoin(volumeDir, path)
|
||||
if err = checkPathLength(filePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify if the file already exists and is not of regular type.
|
||||
var st os.FileInfo
|
||||
if st, err = os.Lstat(filePath); err == nil {
|
||||
if !st.Mode().IsRegular() {
|
||||
return nil, errIsNotRegular
|
||||
}
|
||||
} else {
|
||||
// Create top level directories if they don't exist.
|
||||
// with mode 0777 mkdir honors system umask.
|
||||
if err = mkdirAll(pathutil.Dir(filePath), 0777); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Create top level directories if they don't exist.
|
||||
// with mode 0777 mkdir honors system umask.
|
||||
if err = mkdirAll(pathutil.Dir(filePath), 0777); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
w, err := os.OpenFile(filePath, mode, 0666)
|
||||
w, err := os.OpenFile(filePath, mode|writeMode, 0666)
|
||||
if err != nil {
|
||||
// File path cannot be verified since one of the parents is a file.
|
||||
switch {
|
||||
case isSysErrNotDir(err):
|
||||
return nil, errFileAccessDenied
|
||||
case isSysErrIsDir(err):
|
||||
return nil, errIsNotRegular
|
||||
case osIsPermission(err):
|
||||
return nil, errFileAccessDenied
|
||||
case isSysErrIO(err):
|
||||
@@ -1673,19 +1657,21 @@ func (s *xlStorage) WriteAll(ctx context.Context, volume string, path string, b
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
w, err := s.openFile(volume, path, os.O_CREATE|os.O_SYNC|os.O_WRONLY)
|
||||
w, err := s.openFile(volume, path, os.O_CREATE|os.O_WRONLY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer w.Close()
|
||||
|
||||
n, err := w.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n != len(b) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1697,19 +1683,38 @@ func (s *xlStorage) AppendFile(ctx context.Context, volume string, path string,
|
||||
atomic.AddInt32(&s.activeIOCount, -1)
|
||||
}()
|
||||
|
||||
var w *os.File
|
||||
// Create file if not found. Not doing O_DIRECT here to avoid the code that does buffer aligned writes.
|
||||
// AppendFile() is only used by healing code to heal objects written in old format.
|
||||
w, err = s.openFile(volume, path, os.O_CREATE|os.O_SYNC|os.O_APPEND|os.O_WRONLY)
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = w.Write(buf); err != nil {
|
||||
// Stat a volume entry.
|
||||
if _, err = os.Lstat(volumeDir); err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return errVolumeNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Close()
|
||||
var w *os.File
|
||||
// Create file if not found. Not doing O_DIRECT here to avoid the code that does buffer aligned writes.
|
||||
// AppendFile() is only used by healing code to heal objects written in old format.
|
||||
w, err = s.openFile(volume, path, os.O_CREATE|os.O_APPEND|os.O_WRONLY)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
n, err := w.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if n != len(buf) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckParts check if path has necessary parts available.
|
||||
@@ -2005,7 +2010,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath, dataDir,
|
||||
return err
|
||||
}
|
||||
|
||||
srcBuf, err := ioutil.ReadFile(srcFilePath)
|
||||
srcBuf, err := xioutil.ReadFile(srcFilePath)
|
||||
if err != nil {
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
@@ -2015,7 +2020,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath, dataDir,
|
||||
return err
|
||||
}
|
||||
|
||||
dstBuf, err := ioutil.ReadFile(dstFilePath)
|
||||
dstBuf, err := xioutil.ReadFile(dstFilePath)
|
||||
if err != nil {
|
||||
if !osIsNotExist(err) {
|
||||
return osErrToFileErr(err)
|
||||
@@ -2026,7 +2031,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath, dataDir,
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
dstBuf, err = ioutil.ReadFile(dstFilePath)
|
||||
dstBuf, err = xioutil.ReadFile(dstFilePath)
|
||||
if err != nil && !osIsNotExist(err) {
|
||||
return osErrToFileErr(err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// +build windows darwin
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// No special option for reads on windows
|
||||
readMode = os.O_RDONLY
|
||||
|
||||
// Write with sync no buffering only used only for `xl.meta` writes
|
||||
writeMode = os.O_SYNC
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
// +build !windows,!darwin
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
// Disallow updating access times
|
||||
readMode = os.O_RDONLY | 0x40000 // O_NOATIME
|
||||
|
||||
// Write with data sync only used only for `xl.meta` writes
|
||||
writeMode = 0x1000 // O_DSYNC
|
||||
)
|
||||
@@ -0,0 +1,88 @@
|
||||
# Bucket Replication Design [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||
|
||||
This document explains the design approach of server side bucket replication. If you're looking to get started with replication, we suggest you go through the [Bucket replication guide](https://github.com/minio/minio/blob/master/docs/bucket/replication/README.md) first.
|
||||
|
||||
## Overview
|
||||
Replication relies on immutability provided by versioning to sync objects between the configured source and replication target.
|
||||
|
||||
### Replication of object version and metadata
|
||||
If an object meets replication rules as set in the replication configuration, `X-Amz-Replication-Status` is first set to `PENDING` as the PUT operation completes and replication is queued (unless synchronous replication is in place). After replication is performed, the metadata on the source object version changes to `COMPLETED` or `FAILED` depending on whether replication succeeded. The object version on the target shows `X-Amz-Replication-Status` of `REPLICA`
|
||||
|
||||
All replication failures are picked up by the scanner which runs at a one minute frequency, each time scanning upto a sixteenth of the namespace. Object versions marked `PENDING` or `FAILED` are re-queued for replication.
|
||||
|
||||
Replication speed depends on the cluster load, number of objects in the object store as well as storage speed. In addition, any bandwidth limits set via `mc admin bucket remote add` could also contribute to replication speed. The number of workers used for replication defaults to 100. Based on network bandwidth and system load, the number of workers used in replication can be configured using `mc admin config set alias api` to set the `replication_workers`. The prometheus metrics exposed by MinIO can be used to plan resource allocation and bandwidth management to optimize replication speed.
|
||||
|
||||
If synchronous replication is configured above, replication is attempted right away prior to returning the PUT object response. In the event that the replication target is down, the `X-Amz-Replication-Status` is marked as `FAILED` and resynced with target when the scanner runs again.
|
||||
|
||||
Any metadata changes on the source object version, such as metadata updates via PutObjectTagging, PutObjectRetention, PutObjectLegalHold and COPY api are replicated in a similar manner to target version, with the `X-Amz-Replication-Status` again cycling through the same states.
|
||||
|
||||
The description above details one way replication from source to target w.r.t incoming object uploads and metadata changes to source object version. If active-active replication is configured, any incoming uploads and metadata changes to versions created on the target, will sync back to the source and be marked as `REPLICA` on the source. AWS, as well as MinIO do not by default sync metadata changes on a object version marked `REPLICA` back to source. This requires a setting in the replication configuration called [replica modification sync](https://aws.amazon.com/about-aws/whats-new/2020/12/amazon-s3-replication-adds-support-two-way-replication/) which is not yet available in MinIO.
|
||||
|
||||
For active-active replication, automatic failover occurs on `GET/HEAD` operations if object or object version requested qualifies for replication and is missing on one site, but present on the other. This allows the applications to take full advantage of two-way replication even before the two sites get fully synced.
|
||||
|
||||
### Replication of DeleteMarker and versioned Delete
|
||||
|
||||
MinIO allows DeleteMarker replication and versioned delete replication by setting `--replicate delete,delete-marker` while setting up replication configuration using `mc replicate add`. The MinIO implementation is based on V2 configuration, however it has been extended to allow both DeleteMarker replication and replication of versioned deletes with the `DeleteMarkerReplication` and `DeleteReplication` fields in the replication configuration. By default, this is set to `Disabled` unless the user specifies it while adding a replication rule.
|
||||
|
||||
Similar to object version replication, DeleteMarker replication also cycles through `PENDING` to `COMPLETED` or `FAILED` states for the `X-Amz-Replication-Status` on the source when a delete marker is set (i.e. performing `mc rm` on an object without specifying a version).After replication syncs the delete marker on the target, the DeleteMarker on the target shows `X-Amz-Replication-Status` of `REPLICA`. The status of DeleteMarker replication is returned by `X-Minio-Replication-DeleteMarker-Status` header on `HEAD/GET` calls for the delete marker version in question - i.e with `mc stat --version-id dm-version-id`
|
||||
|
||||
It must be noted that if active-active replication is set up with delete marker replication, there is potential for duplicate delete markers to be created if both source and target concurrently set a Delete Marker or if one/both of the clusters went down at tandem before the replication event was synced.This is an unavoidable side-effect in active-active replication caused by allowing delete markers set on a object version with `REPLICA` status back to source.
|
||||
|
||||
In the case of versioned deletes a.k.a permanent delete of a version by doing a `mc rm --version-id` on a object, replication implementation marks a object version permanently deleted as `PENDING` purge and deletes the version from source after syncing to the target and ensuring target version is deleted. The delete marker being deleted or object version being deleted will still be visible on listing with `mc ls --versions` until the sync is completed. Objects marked as deleted will not be accessible via `GET` or `HEAD` requests and would return a http response code of `405`. The status of versioned delete replication on the source can be queried by `HEAD` request on the delete marker versionID or object versionID in question.
|
||||
An additional header `X-Minio-Replication-Delete-Status` is returned which would show `PENDING` or `FAILED` status if the replication is still not caught up.
|
||||
|
||||
Existing object replication, replica modification sync for 2-way replication and multi site replication are currently not supported.
|
||||
|
||||
### Internal metadata for replication
|
||||
|
||||
`xl.meta` that is in use for [versioning](https://raw.githubusercontent.com/minio/minio/master/docs/bucket/versioning/DESIGN.md) has additional metadata for replication of objects,delete markers and versioned deletes.
|
||||
|
||||
### Metadata for object replication
|
||||
|
||||
```
|
||||
...
|
||||
"MetaSys": {},
|
||||
"MetaUsr": {
|
||||
"X-Amz-Replication-Status": "COMPLETED",
|
||||
"content-type": "application/octet-stream",
|
||||
"etag": "8315e643ed6a5d7c9962fc0a8ef9c11f"
|
||||
},
|
||||
"PartASizes": [
|
||||
26
|
||||
],
|
||||
...
|
||||
```
|
||||
|
||||
### Additional replication metadata for DeleteMarker
|
||||
|
||||
```
|
||||
{
|
||||
"DelObj": {
|
||||
"ID": "8+jguy20TOuzUCN2PTrESA==",
|
||||
"MTime": 1613601949645331516,
|
||||
"MetaSys": {
|
||||
"X-Amz-Replication-Status": "Q09NUExFVEVE"
|
||||
}
|
||||
},
|
||||
"Type": 2
|
||||
}
|
||||
```
|
||||
|
||||
### Additional replication metadata for versioned delete
|
||||
|
||||
```
|
||||
{
|
||||
"DelObj": {
|
||||
"ID": "8+jguy20TOuzUCN2PTrESA==",
|
||||
"MTime": 1613601949645331516,
|
||||
"MetaSys": {
|
||||
"purgestatus": "RkFJTEVE"
|
||||
}
|
||||
},
|
||||
"Type": 2
|
||||
}
|
||||
```
|
||||
|
||||
## Explore Further
|
||||
- [MinIO Bucket Versioning Implementation](https://docs.minio.io/docs/minio-bucket-versioning-guide.html)
|
||||
- [MinIO Client Quickstart Guide](https://docs.minio.io/docs/minio-client-quickstart-guide.html)
|
||||
@@ -1,4 +1,4 @@
|
||||
# Bucket Replication Guide [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||
# Bucket Replication Guide [](https://slack.min.io) [](https://hub.docker.com/r/minio/minio/)
|
||||
|
||||
Bucket replication is designed to replicate selected objects in a bucket to a destination bucket.
|
||||
|
||||
@@ -172,8 +172,7 @@ Status of delete marker replication can be viewed by doing a GET/HEAD on the obj
|
||||
|
||||
The status of replication can be monitored by configuring event notifications on the source and target buckets using `mc event add`.On the source side, the `s3:PutObject`, `s3:Replication:OperationCompletedReplication` and `s3:Replication:OperationFailedReplication` events show the status of replication in the `X-Amz-Replication-Status` metadata.
|
||||
|
||||
On the target bucket, `s3:PutObject` event shows `X-Amz-Replication-Status` status of `REPLICA` in the metadata. Additional metrics to monitor backlog state for the purpose of bandwidth management and resource allocation are
|
||||
an upcoming feature.
|
||||
On the target bucket, `s3:PutObject` event shows `X-Amz-Replication-Status` status of `REPLICA` in the metadata. Additional metrics to monitor backlog state for the purpose of bandwidth management and resource allocation are exposed via Prometheus - see https://github.com/minio/minio/blob/master/docs/metrics/prometheus/list.md for more details.
|
||||
|
||||
### Sync/Async Replication
|
||||
By default, replication is completed asynchronously. If synchronous replication is desired, set the --sync flag while adding a
|
||||
@@ -183,5 +182,6 @@ remote replication target using the `mc admin bucket remote add` command
|
||||
```
|
||||
|
||||
## Explore Further
|
||||
- [MinIO Bucket Replication Design](https://raw.githubusercontent.com/minio/minio/master/docs/bucket/replication/DESIGN.md)
|
||||
- [MinIO Bucket Versioning Implementation](https://docs.minio.io/docs/minio-bucket-versioning-guide.html)
|
||||
- [MinIO Client Quickstart Guide](https://docs.minio.io/docs/minio-client-quickstart-guide.html)
|
||||
|
||||
+11
-11
@@ -263,36 +263,36 @@ The following sub-systems are dynamic i.e., configuration parameters for each su
|
||||
```
|
||||
api manage global HTTP API call specific features, such as throttling, authentication types, etc.
|
||||
heal manage object healing frequency and bitrot verification checks
|
||||
crawler manage crawling for usage calculation, lifecycle, healing and more
|
||||
scanner manage crawling for usage calculation, lifecycle, healing and more
|
||||
```
|
||||
|
||||
> NOTE: if you set any of the following sub-system configuration using ENVs, dynamic behavior is not supported.
|
||||
|
||||
### Usage crawler
|
||||
### Usage scanner
|
||||
|
||||
Data usage crawler is enabled by default. The following configuration settings allow for more staggered delay in terms of usage calculation. The crawler adapts to the system speed and completely pauses when the system is under load. It is possible to adjust the speed of the crawler and thereby the latency of updates being reflected. The delays between each operation of the crawl can be adjusted by the `mc admin config set alias/ delay=15.0`. By default the value is `10.0`. This means the crawler will sleep *10x* the time each operation takes.
|
||||
Data usage scanner is enabled by default. The following configuration settings allow for more staggered delay in terms of usage calculation. The scanner adapts to the system speed and completely pauses when the system is under load. It is possible to adjust the speed of the scanner and thereby the latency of updates being reflected. The delays between each operation of the crawl can be adjusted by the `mc admin config set alias/ delay=15.0`. By default the value is `10.0`. This means the scanner will sleep *10x* the time each operation takes.
|
||||
|
||||
In most setups this will keep the crawler slow enough to not impact overall system performance. Setting the `delay` key to a *lower* value will make the crawler faster and setting it to 0 will make the crawler run at full speed (not recommended in production). Setting it to a higher value will make the crawler slower, consuming less resources with the trade off of not collecting metrics for operations like healing and disk usage as fast.
|
||||
In most setups this will keep the scanner slow enough to not impact overall system performance. Setting the `delay` key to a *lower* value will make the scanner faster and setting it to 0 will make the scanner run at full speed (not recommended in production). Setting it to a higher value will make the scanner slower, consuming less resources with the trade off of not collecting metrics for operations like healing and disk usage as fast.
|
||||
|
||||
```
|
||||
~ mc admin config set alias/ crawler
|
||||
~ mc admin config set alias/ scanner
|
||||
KEY:
|
||||
crawler manage crawling for usage calculation, lifecycle, healing and more
|
||||
scanner manage crawling for usage calculation, lifecycle, healing and more
|
||||
|
||||
ARGS:
|
||||
delay (float) crawler delay multiplier, defaults to '10.0'
|
||||
delay (float) scanner delay multiplier, defaults to '10.0'
|
||||
max_wait (duration) maximum wait time between operations, defaults to '15s'
|
||||
```
|
||||
|
||||
Example: Following setting will decrease the crawler speed by a factor of 3, reducing the system resource use, but increasing the latency of updates being reflected.
|
||||
Example: Following setting will decrease the scanner speed by a factor of 3, reducing the system resource use, but increasing the latency of updates being reflected.
|
||||
|
||||
```sh
|
||||
~ mc admin config set alias/ crawler delay=30.0
|
||||
~ mc admin config set alias/ scanner delay=30.0
|
||||
```
|
||||
|
||||
Once set the crawler settings are automatically applied without the need for server restarts.
|
||||
Once set the scanner settings are automatically applied without the need for server restarts.
|
||||
|
||||
> NOTE: Data usage crawler is not supported under Gateway deployments.
|
||||
> NOTE: Data usage scanner is not supported under Gateway deployments.
|
||||
|
||||
### Healing
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ Capacity constrained environments, MinIO will work but not recommended for produ
|
||||
|--------:|------------------:|------------:|------------------------:|------------------------------:|-------------------------------:|
|
||||
| 1 | 1 | 1 | 0 | 0 | 0 |
|
||||
| 1 | 4 | 4 | 2 | 0 | 0 |
|
||||
| 4 | 1 | 4 | 2 | 2 | 3 |
|
||||
| 4 | 1 | 4 | 2 | 2 | 1 |
|
||||
| 5 | 1 | 5 | 2 | 2 | 2 |
|
||||
| 6 | 1 | 6 | 3 | 3 | 4 |
|
||||
| 6 | 1 | 6 | 3 | 3 | 2 |
|
||||
| 7 | 1 | 7 | 3 | 3 | 3 |
|
||||
|
||||
### Minimum System Configuration for Production
|
||||
|
||||
@@ -79,6 +79,23 @@ export MINIO_NOTIFY_WEBHOOK_QUEUE_DIR_1=/tmp/webhk
|
||||
|
||||
> NOTE: Please check the docs for the corresponding ENV setting. Alternatively, We can obtain other ENVs in the form `mc admin config set alias/ <sub-sys> --env`
|
||||
|
||||
## Symlink support
|
||||
|
||||
NAS gateway implementation allows symlinks on regular files,
|
||||
|
||||
### Behavior
|
||||
|
||||
- For reads symlink resolves to file symlink points to.
|
||||
- For deletes
|
||||
- Delete of symlink deletes the symlink but not the real file to which the symlink points.
|
||||
- Delete of actual file automatically makes symlink'ed file invisible, dangling symlinks won't be visible.
|
||||
|
||||
#### Caveats
|
||||
- Disallows follow of directory symlinks to avoid security issues, and leaving them as is on namespace makes them very inconsistent.
|
||||
- Dangling symlinks are ignored automatically.
|
||||
|
||||
*Directory symlinks is not and will not be supported as there are no safe ways to handle them.*
|
||||
|
||||
## Explore Further
|
||||
- [`mc` command-line interface](https://docs.min.io/docs/minio-client-quickstart-guide)
|
||||
- [`aws` command-line interface](https://docs.min.io/docs/aws-cli-with-minio)
|
||||
|
||||
@@ -5,7 +5,7 @@ version: '3.7'
|
||||
# it through port 9000.
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data1-1:/data1
|
||||
- data1-2:/data2
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data2-1:/data1
|
||||
- data2-2:/data2
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data3-1:/data1
|
||||
- data3-2:/data2
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
volumes:
|
||||
- data4-1:/data1
|
||||
- data4-2:/data2
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -29,7 +29,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -56,7 +56,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -83,7 +83,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
services:
|
||||
minio1:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio1
|
||||
volumes:
|
||||
- minio1-data:/export
|
||||
@@ -33,7 +33,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio2:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio2
|
||||
volumes:
|
||||
- minio2-data:/export
|
||||
@@ -64,7 +64,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio3:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio3
|
||||
volumes:
|
||||
- minio3-data:/export
|
||||
@@ -95,7 +95,7 @@ services:
|
||||
retries: 3
|
||||
|
||||
minio4:
|
||||
image: minio/minio:RELEASE.2021-02-11T08-23-43Z
|
||||
image: minio/minio:RELEASE.2021-02-23T20-05-01Z
|
||||
hostname: minio4
|
||||
volumes:
|
||||
- minio4-data:/export
|
||||
|
||||
+7
-1
@@ -123,7 +123,13 @@ export MINIO_IDENTITY_LDAP_TLS_SKIP_VERIFY=on
|
||||
|
||||
### Variable substitution in AD/LDAP configuration strings ###
|
||||
|
||||
`%s` is replaced with *username* automatically for construction bind_dn, search_filter and group_search_filter.
|
||||
In the configuration variables, `%s` is substituted with the *username* from the STS request and `%d` is substituted with the *distinguished username (user DN)* of the LDAP user. Please see the following table for which configuration variables support these substitution variables:
|
||||
|
||||
| Variable | Supported substitutions |
|
||||
|---------------------------------------------|-------------------------|
|
||||
| `MINIO_IDENTITY_LDAP_USERNAME_FORMAT` | `%s` |
|
||||
| `MINIO_IDENTITY_LDAP_USER_DN_SEARCH_FILTER` | `%s` |
|
||||
| `MINIO_IDENTITY_LDAP_GROUP_SEARCH_FILTER` | `%s` and `%d` |
|
||||
|
||||
## Managing User/Group Access Policy
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/minio/minio
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.39.0
|
||||
@@ -49,9 +49,9 @@ require (
|
||||
github.com/minio/cli v1.22.0
|
||||
github.com/minio/highwayhash v1.0.1
|
||||
github.com/minio/md5-simd v1.1.1 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.9-0.20210210235136-83423dddb072
|
||||
github.com/minio/minio-go/v7 v7.0.10
|
||||
github.com/minio/selfupdate v0.3.1
|
||||
github.com/minio/sha256-simd v0.1.1
|
||||
github.com/minio/sha256-simd v1.0.0
|
||||
github.com/minio/simdjson-go v0.2.1
|
||||
github.com/minio/sio v0.2.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
@@ -70,15 +70,18 @@ require (
|
||||
github.com/prometheus/client_golang v1.8.0
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/prometheus/procfs v0.2.0
|
||||
github.com/quasilyte/go-ruleguard v0.2.1 // indirect
|
||||
github.com/rjeczalik/notify v0.9.2
|
||||
github.com/rs/cors v1.7.0
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
github.com/shirou/gopsutil v3.20.11+incompatible
|
||||
github.com/sirupsen/logrus v1.7.0 // indirect
|
||||
github.com/spaolacci/murmur3 v1.1.0 // indirect
|
||||
github.com/streadway/amqp v1.0.0
|
||||
github.com/tidwall/gjson v1.6.7
|
||||
github.com/tidwall/sjson v1.0.4
|
||||
github.com/tinylib/msgp v1.1.3
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 // indirect
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a
|
||||
github.com/willf/bitset v1.1.11 // indirect
|
||||
github.com/willf/bloom v2.0.3+incompatible
|
||||
|
||||
@@ -61,16 +61,13 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.35.20 h1:Hs7x9Czh+MMPnZLQqHhsuZKeNFA3Vuf7pdy2r5QlVb0=
|
||||
github.com/aws/aws-sdk-go v1.35.20/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/bcicen/jstream v1.0.1 h1:BXY7Cu4rdmc0rhyTVyT3UkxAiX3bnLpKLas9btbH5ck=
|
||||
github.com/bcicen/jstream v1.0.1/go.mod h1:9ielPxqFry7Y4Tg3j4BfjPocfJ3TbsRtXOAYXYmRuAQ=
|
||||
github.com/beevik/ntp v0.3.0 h1:xzVrPrE4ziasFXgBVBZJDP0Wg/KpMwk2KHJ4Ba8GrDw=
|
||||
github.com/beevik/ntp v0.3.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
@@ -117,7 +114,6 @@ github.com/dswarbrick/smart v0.0.0-20190505152634-909a45200d6d/go.mod h1:apXo4PA
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
|
||||
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
@@ -133,9 +129,7 @@ github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
@@ -171,10 +165,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
|
||||
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.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
@@ -193,7 +185,6 @@ github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
@@ -209,7 +200,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
@@ -219,7 +209,6 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@@ -257,14 +246,12 @@ github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVo
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxuKuM=
|
||||
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
|
||||
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
|
||||
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs=
|
||||
github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4=
|
||||
@@ -282,7 +269,6 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
@@ -319,16 +305,13 @@ github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.1 h1:IGSJfqBzMS6TA0oJ7DxXdyzPK563QHa8T2IqER2ggyQ=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.1/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2 h1:6ZIM6b/JJN0X8UM43ZOM6Z4SJzla+a/u7scXFJzodkA=
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
|
||||
github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0=
|
||||
github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
|
||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||
@@ -339,7 +322,6 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@@ -358,8 +340,9 @@ github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo
|
||||
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.2/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.3 h1:DNljyrHyxlkk8139OXIAAauCwV8eQGDD6Z8YqnDXdZw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.3/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/readahead v1.3.1 h1:QqXNYvm+VvqYcbrRT4LojUciM0XrznFRIDrbHiJtu/0=
|
||||
@@ -373,7 +356,6 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
@@ -385,7 +367,6 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
@@ -396,11 +377,9 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
@@ -413,16 +392,16 @@ 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/highwayhash v1.0.1 h1:dZ6IIu8Z14VlC0VpfKofAhCy74wu/Qb5gcn52yWoz/0=
|
||||
github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/md5-simd v1.1.1 h1:9ojcLbuZ4gXbB2sX53MKn8JUZ0sB/2wfwsEcRw+I08U=
|
||||
github.com/minio/md5-simd v1.1.1/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/minio-go/v7 v7.0.9-0.20210210235136-83423dddb072 h1:zlheLAzZ66jYLUsa81R8gwPtSgKRI5FMJyAKuaJpkHE=
|
||||
github.com/minio/minio-go/v7 v7.0.9-0.20210210235136-83423dddb072/go.mod h1:pEZBUa+L2m9oECoIA6IcSK8bv/qggtQVLovjeKK5jYc=
|
||||
github.com/minio/minio-go/v7 v7.0.10 h1:1oUKe4EOPUEhw2qnPQaPsJ0lmVTYLFu03SiItauXs94=
|
||||
github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo=
|
||||
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
|
||||
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
||||
github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||
github.com/minio/simdjson-go v0.2.1 h1:nxYlp4Qd0w2pwLlif00l5vTFL6PcNAKpyHq27/pageg=
|
||||
github.com/minio/simdjson-go v0.2.1/go.mod h1:JPUSkRykfSPS+AhO0YPA1h0l5vY7NqrF4zel2b12wxc=
|
||||
github.com/minio/sio v0.2.1 h1:NjzKiIMSMcHediVQR0AFVx2tp7Wxh9tKPfDI3kH7aHQ=
|
||||
@@ -441,7 +420,6 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f h1:D4I34fbgczGrhrN4DzBCZXT3u/nMWJnGmviIjSzzXSw=
|
||||
github.com/mmcloughlin/avo v0.0.0-20201105074841-5d2f697d268f/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
@@ -454,7 +432,6 @@ github.com/montanaflynn/stats v0.5.0/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFW
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
|
||||
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=
|
||||
@@ -468,7 +445,6 @@ github.com/nats-io/nats.go v1.10.0 h1:L8qnKaofSfNFbXg0C5F71LdjPRnmQwSsA4ukmkt1Tv
|
||||
github.com/nats-io/nats.go v1.10.0/go.mod h1:AjGArbfyR50+afOUotNX2Xs5SYHf+CoOa5HH1eEl2HE=
|
||||
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.4 h1:aEsHIssIk6ETN5m2/MD8Y4B2X7FfXrBAUdkyRvbVYzA=
|
||||
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=
|
||||
@@ -524,7 +500,6 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
@@ -533,14 +508,12 @@ github.com/prometheus/client_golang v1.8.0/go.mod h1:O9VU6huf47PktckDQfMTX0Y8tY0
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
@@ -549,13 +522,13 @@ github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4=
|
||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
|
||||
github.com/quasilyte/go-ruleguard v0.2.1 h1:56eRm0daAyny9UhJnmtJW/UyLZQusukBAB8oT8AHKHo=
|
||||
github.com/quasilyte/go-ruleguard v0.2.1/go.mod h1:hN2rVc/uS4bQhQKTio2XaSJSafJwqBUWWwtssT3cQmc=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
@@ -588,8 +561,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
|
||||
github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
|
||||
github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
|
||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/gunit v1.4.2/go.mod h1:ZjM1ozSIMJlAz/ay4SG8PeKF00ckUp+zMHZXV9/bvak=
|
||||
@@ -601,9 +572,7 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRNFYlvKwSr5zff2v+uPHaffZ6/M4k=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo=
|
||||
github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
@@ -612,9 +581,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -630,6 +597,8 @@ github.com/tinylib/msgp v1.1.3 h1:3giwAkmtaEDLSV0MdO1lDLuPgklgPzmk8H9+So2BVfA=
|
||||
github.com/tinylib/msgp v1.1.3/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8 h1:ndzgwNDnKIqyCvHTXaCqh9KlOWKvBry6nuXMJmonVsE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31 h1:OXcKh35JaYsGMRzpvFkLv/MEyPuL49CThT1pZ8aSml4=
|
||||
github.com/ttacon/chalk v0.0.0-20160626202418-22c06c80ed31/go.mod h1:onvgF043R+lC5RZ8IT9rBXDaEDnpnw/Cl+HFiw+v/7Q=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
@@ -645,8 +614,8 @@ github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
@@ -659,17 +628,14 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
@@ -727,7 +693,6 @@ golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924 h1:QsnDpLLOKwHBBDa8nDws4DYNc/ryVW2vCpxCs09d4PY=
|
||||
@@ -741,6 +706,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -778,9 +744,7 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck=
|
||||
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -794,7 +758,6 @@ golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -815,6 +778,7 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
@@ -855,21 +819,16 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
@@ -897,7 +856,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// +build go1.14
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2016-2020 MinIO, Inc.
|
||||
*
|
||||
|
||||
@@ -331,7 +331,7 @@ func (lc Lifecycle) PredictExpiryTime(obj ObjectOpts) (string, time.Time) {
|
||||
// expiration date and its associated rule ID.
|
||||
for _, rule := range lc.FilterActionableRules(obj) {
|
||||
if !rule.NoncurrentVersionExpiration.IsDaysNull() && !obj.IsLatest && obj.VersionID != "" {
|
||||
return rule.ID, ExpectedExpiryTime(time.Now(), int(rule.NoncurrentVersionExpiration.NoncurrentDays))
|
||||
return rule.ID, ExpectedExpiryTime(obj.SuccessorModTime, int(rule.NoncurrentVersionExpiration.NoncurrentDays))
|
||||
}
|
||||
|
||||
if !rule.Expiration.IsDateNull() {
|
||||
|
||||
@@ -30,21 +30,3 @@ type Info struct {
|
||||
Ffree uint64
|
||||
FSType string
|
||||
}
|
||||
|
||||
// SameDisk reports whether di1 and di2 describe the same disk.
|
||||
func SameDisk(di1, di2 Info) bool {
|
||||
if di1.Total != di2.Total {
|
||||
// disk total size different
|
||||
return false
|
||||
}
|
||||
|
||||
if di1.Files != di2.Files {
|
||||
// disk total inodes different
|
||||
return false
|
||||
}
|
||||
|
||||
// returns true only if Used, Free are same, then its the same disk.
|
||||
// we are deliberately not using free inodes as that is unreliable
|
||||
// due the fact that Ffree can vary even for temporary files
|
||||
return di1.Used == di2.Used && di1.Free == di2.Free
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package disk
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// SameDisk reports whether di1 and di2 describe the same disk.
|
||||
func SameDisk(disk1, disk2 string) (bool, error) {
|
||||
st1 := syscall.Stat_t{}
|
||||
st2 := syscall.Stat_t{}
|
||||
|
||||
if err := syscall.Stat(disk1, &st1); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := syscall.Stat(disk2, &st2); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return st1.Dev == st2.Dev, nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
* MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package disk
|
||||
|
||||
// SameDisk reports whether di1 and di2 describe the same disk.
|
||||
func SameDisk(disk1, disk2 string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
@@ -25,13 +25,5 @@ func IsRootDisk(diskPath string, rootDisk string) (bool, error) {
|
||||
// On windows this function is not implemented.
|
||||
return false, nil
|
||||
}
|
||||
info, err := GetInfo(diskPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rootInfo, err := GetInfo(rootDisk)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return SameDisk(info, rootInfo), nil
|
||||
return SameDisk(diskPath, rootDisk)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package etag provides an implementation of S3 ETags.
|
||||
//
|
||||
// Each S3 object has an associated ETag that can be
|
||||
// used to e.g. quickly compare objects or check whether
|
||||
// the content of an object has changed.
|
||||
//
|
||||
// In general, an S3 ETag is an MD5 checksum of the object
|
||||
// content. However, there are many exceptions to this rule.
|
||||
//
|
||||
//
|
||||
// Single-part Upload
|
||||
//
|
||||
// In case of a basic single-part PUT operation - without server
|
||||
// side encryption or object compression - the ETag of an object
|
||||
// is its content MD5.
|
||||
//
|
||||
//
|
||||
// Multi-part Upload
|
||||
//
|
||||
// The ETag of an object does not correspond to its content MD5
|
||||
// when the object is uploaded in multiple parts via the S3
|
||||
// multipart API. Instead, S3 first computes a MD5 of each part:
|
||||
// e1 := MD5(part-1)
|
||||
// e2 := MD5(part-2)
|
||||
// ...
|
||||
// eN := MD5(part-N)
|
||||
//
|
||||
// Then, the ETag of the object is computed as MD5 of all individual
|
||||
// part checksums. S3 also encodes the number of parts into the ETag
|
||||
// by appending a -<number-of-parts> at the end:
|
||||
// ETag := MD5(e1 || e2 || e3 ... || eN) || -N
|
||||
//
|
||||
// For example: ceb8853ddc5086cc4ab9e149f8f09c88-5
|
||||
//
|
||||
// However, this scheme is only used for multipart objects that are
|
||||
// not encrypted.
|
||||
//
|
||||
// Server-side Encryption
|
||||
//
|
||||
// S3 specifies three types of server-side-encryption - SSE-C, SSE-S3
|
||||
// and SSE-KMS - with different semantics w.r.t. ETags.
|
||||
// In case of SSE-S3, the ETag of an object is computed the same as
|
||||
// for single resp. multipart plaintext objects. In particular,
|
||||
// the ETag of a singlepart SSE-S3 object is its content MD5.
|
||||
//
|
||||
// In case of SSE-C and SSE-KMS, the ETag of an object is computed
|
||||
// differently. For singlepart uploads the ETag is not the content
|
||||
// MD5 of the object. For multipart uploads the ETag is also not
|
||||
// the MD5 of the individual part checksums but it still contains
|
||||
// the number of parts as suffix.
|
||||
//
|
||||
// Instead, the ETag is kind of unpredictable for S3 clients when
|
||||
// an object is encrypted using SSE-C or SSE-KMS. Maybe AWS S3
|
||||
// computes the ETag as MD5 of the encrypted content but there is
|
||||
// no way to verify this assumption since the encryption happens
|
||||
// inside AWS S3.
|
||||
// Therefore, S3 clients must not make any assumption about ETags
|
||||
// in case of SSE-C or SSE-KMS except that the ETag is well-formed.
|
||||
//
|
||||
// To put all of this into a simple rule:
|
||||
// SSE-S3 : ETag == MD5
|
||||
// SSE-C : ETag != MD5
|
||||
// SSE-KMS: ETag != MD5
|
||||
//
|
||||
//
|
||||
// Encrypted ETags
|
||||
//
|
||||
// An S3 implementation has to remember the content MD5 of objects
|
||||
// in case of SSE-S3. However, storing the ETag of an encrypted
|
||||
// object in plaintext may reveal some information about the object.
|
||||
// For example, two objects with the same ETag are identical with
|
||||
// a very high probability.
|
||||
//
|
||||
// Therefore, an S3 implementation may encrypt an ETag before storing
|
||||
// it. In this case, the stored ETag may not be a well-formed S3 ETag.
|
||||
// For example, it can be larger due to a checksum added by authenticated
|
||||
// encryption schemes. Such an ETag must be decrypted before sent to an
|
||||
// S3 client.
|
||||
//
|
||||
//
|
||||
// S3 Clients
|
||||
//
|
||||
// There are many different S3 client implementations. Most of them
|
||||
// access the ETag by looking for the HTTP response header key "Etag".
|
||||
// However, some of them assume that the header key has to be "ETag"
|
||||
// (case-sensitive) and will fail otherwise.
|
||||
// Further, some clients require that the ETag value is a double-quoted
|
||||
// string. Therefore, this package provides dedicated functions for
|
||||
// adding and extracing the ETag to/from HTTP headers.
|
||||
package etag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ETag is a single S3 ETag.
|
||||
//
|
||||
// An S3 ETag sometimes corresponds to the MD5 of
|
||||
// the S3 object content. However, when an object
|
||||
// is encrypted, compressed or uploaded using
|
||||
// the S3 multipart API then its ETag is not
|
||||
// necessarily the MD5 of the object content.
|
||||
//
|
||||
// For a more detailed description of S3 ETags
|
||||
// take a look at the package documentation.
|
||||
type ETag []byte
|
||||
|
||||
// String returns the string representation of the ETag.
|
||||
//
|
||||
// The returned string is a hex representation of the
|
||||
// binary ETag with an optional '-<part-number>' suffix.
|
||||
func (e ETag) String() string {
|
||||
if e.IsMultipart() {
|
||||
return hex.EncodeToString(e[:16]) + string(e[16:])
|
||||
}
|
||||
return hex.EncodeToString(e)
|
||||
}
|
||||
|
||||
// IsEncrypted reports whether the ETag is encrypted.
|
||||
func (e ETag) IsEncrypted() bool {
|
||||
return len(e) > 16 && !bytes.ContainsRune(e, '-')
|
||||
}
|
||||
|
||||
// IsMultipart reports whether the ETag belongs to an
|
||||
// object that has been uploaded using the S3 multipart
|
||||
// API.
|
||||
// An S3 multipart ETag has a -<part-number> suffix.
|
||||
func (e ETag) IsMultipart() bool {
|
||||
return len(e) > 16 && bytes.ContainsRune(e, '-')
|
||||
}
|
||||
|
||||
// Parts returns the number of object parts that are
|
||||
// referenced by this ETag. It returns 1 if the object
|
||||
// has been uploaded using the S3 singlepart API.
|
||||
//
|
||||
// Parts may panic if the ETag is an invalid multipart
|
||||
// ETag.
|
||||
func (e ETag) Parts() int {
|
||||
if !e.IsMultipart() {
|
||||
return 1
|
||||
}
|
||||
|
||||
n := bytes.IndexRune(e, '-')
|
||||
parts, err := strconv.Atoi(string(e[n+1:]))
|
||||
if err != nil {
|
||||
panic(err) // malformed ETag
|
||||
}
|
||||
return parts
|
||||
}
|
||||
|
||||
var _ Tagger = ETag{} // compiler check
|
||||
|
||||
// ETag returns the ETag itself.
|
||||
//
|
||||
// By providing this method ETag implements
|
||||
// the Tagger interface.
|
||||
func (e ETag) ETag() ETag { return e }
|
||||
|
||||
// Set adds the ETag to the HTTP headers. It overwrites any
|
||||
// existing ETag entry.
|
||||
//
|
||||
// Due to legacy S3 clients, that make incorrect assumptions
|
||||
// about HTTP headers, Set should be used instead of
|
||||
// http.Header.Set(...). Otherwise, some S3 clients will not
|
||||
// able to extract the ETag.
|
||||
func Set(etag ETag, h http.Header) {
|
||||
// Some (broken) S3 clients expect the ETag header to
|
||||
// literally "ETag" - not "Etag". Further, some clients
|
||||
// expect an ETag in double quotes. Therefore, we set the
|
||||
// ETag directly as map entry instead of using http.Header.Set
|
||||
h["ETag"] = []string{`"` + etag.String() + `"`}
|
||||
}
|
||||
|
||||
// Get extracts and parses an ETag from the given HTTP headers.
|
||||
// It returns an error when the HTTP headers do not contain
|
||||
// an ETag entry or when the ETag is malformed.
|
||||
//
|
||||
// Get only accepts AWS S3 compatible ETags - i.e. no
|
||||
// encrypted ETags - and therefore is stricter than Parse.
|
||||
func Get(h http.Header) (ETag, error) {
|
||||
const strict = true
|
||||
if v := h.Get("Etag"); v != "" {
|
||||
return parse(v, strict)
|
||||
}
|
||||
v, ok := h["ETag"]
|
||||
if !ok || len(v) == 0 {
|
||||
return nil, errors.New("etag: HTTP header does not contain an ETag")
|
||||
}
|
||||
return parse(v[0], strict)
|
||||
}
|
||||
|
||||
// Equal returns true if and only if the two ETags are
|
||||
// identical.
|
||||
func Equal(a, b ETag) bool { return bytes.Equal(a, b) }
|
||||
|
||||
// Parse parses s as an S3 ETag, returning the result.
|
||||
// The string can be an encrypted, singlepart
|
||||
// or multipart S3 ETag. It returns an error if s is
|
||||
// not a valid textual representation of an ETag.
|
||||
func Parse(s string) (ETag, error) {
|
||||
const strict = false
|
||||
return parse(s, strict)
|
||||
}
|
||||
|
||||
// parse parse s as an S3 ETag, returning the result.
|
||||
// It operates in one of two modes:
|
||||
// - strict
|
||||
// - non-strict
|
||||
//
|
||||
// In strict mode, parse only accepts ETags that
|
||||
// are AWS S3 compatible. In particular, an AWS
|
||||
// S3 ETag always consists of a 128 bit checksum
|
||||
// value and an optional -<part-number> suffix.
|
||||
// Therefore, s must have the following form in
|
||||
// strict mode: <32-hex-characters>[-<integer>]
|
||||
//
|
||||
// In non-strict mode, parse also accepts ETags
|
||||
// that are not AWS S3 compatible - e.g. encrypted
|
||||
// ETags.
|
||||
func parse(s string, strict bool) (ETag, error) {
|
||||
// An S3 ETag may be a double-quoted string.
|
||||
// Therefore, we remove double quotes at the
|
||||
// start and end, if any.
|
||||
if strings.HasPrefix(s, `"`) && strings.HasSuffix(s, `"`) {
|
||||
s = s[1 : len(s)-1]
|
||||
}
|
||||
|
||||
// An S3 ETag may be a multipart ETag that
|
||||
// contains a '-' followed by a number.
|
||||
// If the ETag does not a '-' is is either
|
||||
// a singlepart or encrypted ETag.
|
||||
n := strings.IndexRune(s, '-')
|
||||
if n == -1 {
|
||||
etag, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strict && len(etag) != 16 { // AWS S3 ETags are always 128 bit long
|
||||
return nil, fmt.Errorf("etag: invalid length %d", len(etag))
|
||||
}
|
||||
return ETag(etag), nil
|
||||
}
|
||||
|
||||
prefix, suffix := s[:n], s[n:]
|
||||
if len(prefix) != 32 {
|
||||
return nil, fmt.Errorf("etag: invalid prefix length %d", len(prefix))
|
||||
}
|
||||
if len(suffix) <= 1 {
|
||||
return nil, errors.New("etag: suffix is not a part number")
|
||||
}
|
||||
|
||||
etag, err := hex.DecodeString(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
partNumber, err := strconv.Atoi(suffix[1:]) // suffix[0] == '-' Therefore, we start parsing at suffix[1]
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if strict && (partNumber == 0 || partNumber > 10000) {
|
||||
return nil, fmt.Errorf("etag: invalid part number %d", partNumber)
|
||||
}
|
||||
return ETag(append(etag, suffix...)), nil
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etag
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var parseTests = []struct {
|
||||
String string
|
||||
ETag ETag
|
||||
ShouldFail bool
|
||||
}{
|
||||
{String: "3b83ef96387f1465", ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101}}, // 0
|
||||
{String: "3b83ef96387f14655fc854ddc3c6bd57", ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101, 95, 200, 84, 221, 195, 198, 189, 87}}, // 1
|
||||
{String: `"3b83ef96387f14655fc854ddc3c6bd57"`, ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101, 95, 200, 84, 221, 195, 198, 189, 87}}, // 2
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-1", ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 49}}, // 3
|
||||
{String: `"ceb8853ddc5086cc4ab9e149f8f09c88-2"`, ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 50}}, // 4
|
||||
{ // 5
|
||||
String: "90402c78d2dccddee1e9e86222ce2c6361675f3529d26000ae2e900ff216b3cb59e130e092d8a2981e776f4d0bd60941",
|
||||
ETag: ETag{144, 64, 44, 120, 210, 220, 205, 222, 225, 233, 232, 98, 34, 206, 44, 99, 97, 103, 95, 53, 41, 210, 96, 0, 174, 46, 144, 15, 242, 22, 179, 203, 89, 225, 48, 224, 146, 216, 162, 152, 30, 119, 111, 77, 11, 214, 9, 65},
|
||||
},
|
||||
|
||||
{String: `"3b83ef96387f14655fc854ddc3c6bd57`, ShouldFail: true}, // 6
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-", ShouldFail: true}, // 7
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-2a", ShouldFail: true}, // 8
|
||||
{String: "ceb8853ddc5086cc4ab9e149f8f09c88-2-1", ShouldFail: true}, // 9
|
||||
{String: "90402c78d2dccddee1e9e86222ce2c-1", ShouldFail: true}, // 10
|
||||
{String: "90402c78d2dccddee1e9e86222ce2c6361675f3529d26000ae2e900ff216b3cb59e130e092d8a2981e776f4d0bd60941-1", ShouldFail: true}, // 11
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
for i, test := range parseTests {
|
||||
etag, err := Parse(test.String)
|
||||
if err == nil && test.ShouldFail {
|
||||
t.Fatalf("Test %d: parse should have failed but succeeded", i)
|
||||
}
|
||||
if err != nil && !test.ShouldFail {
|
||||
t.Fatalf("Test %d: failed to parse ETag %q: %v", i, test.String, err)
|
||||
}
|
||||
if !Equal(etag, test.ETag) {
|
||||
t.Log([]byte(etag))
|
||||
t.Fatalf("Test %d: ETags don't match", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stringTests = []struct {
|
||||
ETag ETag
|
||||
String string
|
||||
}{
|
||||
{ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101}, String: "3b83ef96387f1465"}, // 0
|
||||
{ETag: ETag{59, 131, 239, 150, 56, 127, 20, 101, 95, 200, 84, 221, 195, 198, 189, 87}, String: "3b83ef96387f14655fc854ddc3c6bd57"}, // 1
|
||||
{ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 49}, String: "ceb8853ddc5086cc4ab9e149f8f09c88-1"}, // 2
|
||||
{ETag: ETag{206, 184, 133, 61, 220, 80, 134, 204, 74, 185, 225, 73, 248, 240, 156, 136, 45, 50}, String: "ceb8853ddc5086cc4ab9e149f8f09c88-2"}, // 3
|
||||
{ // 4
|
||||
ETag: ETag{144, 64, 44, 120, 210, 220, 205, 222, 225, 233, 232, 98, 34, 206, 44, 99, 97, 103, 95, 53, 41, 210, 96, 0, 174, 46, 144, 15, 242, 22, 179, 203, 89, 225, 48, 224, 146, 216, 162, 152, 30, 119, 111, 77, 11, 214, 9, 65},
|
||||
String: "90402c78d2dccddee1e9e86222ce2c6361675f3529d26000ae2e900ff216b3cb59e130e092d8a2981e776f4d0bd60941",
|
||||
},
|
||||
}
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
for i, test := range stringTests {
|
||||
s := test.ETag.String()
|
||||
if s != test.String {
|
||||
t.Fatalf("Test %d: got %s - want %s", i, s, test.String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var equalTests = []struct {
|
||||
A string
|
||||
B string
|
||||
Equal bool
|
||||
}{
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: "3b83ef96387f14655fc854ddc3c6bd57", Equal: true}, // 0
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: `"3b83ef96387f14655fc854ddc3c6bd57"`, Equal: true}, // 1
|
||||
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: "3b83ef96387f14655fc854ddc3c6bd57-2", Equal: false}, // 2
|
||||
{A: "3b83ef96387f14655fc854ddc3c6bd57", B: "ceb8853ddc5086cc4ab9e149f8f09c88", Equal: false}, // 3
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
for i, test := range equalTests {
|
||||
A, err := Parse(test.A)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %v", i, err)
|
||||
}
|
||||
B, err := Parse(test.B)
|
||||
if err != nil {
|
||||
t.Fatalf("Test %d: %v", i, err)
|
||||
}
|
||||
if equal := Equal(A, B); equal != test.Equal {
|
||||
t.Fatalf("Test %d: got %v - want %v", i, equal, test.Equal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readerTests = []struct { // Reference values computed by: echo <content> | md5sum
|
||||
Content string
|
||||
ETag ETag
|
||||
}{
|
||||
{
|
||||
Content: "", ETag: ETag{212, 29, 140, 217, 143, 0, 178, 4, 233, 128, 9, 152, 236, 248, 66, 126},
|
||||
},
|
||||
{
|
||||
Content: " ", ETag: ETag{114, 21, 238, 156, 125, 157, 194, 41, 210, 146, 26, 64, 232, 153, 236, 95},
|
||||
},
|
||||
{
|
||||
Content: "Hello World", ETag: ETag{177, 10, 141, 177, 100, 224, 117, 65, 5, 183, 169, 155, 231, 46, 63, 229},
|
||||
},
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
for i, test := range readerTests {
|
||||
reader := NewReader(strings.NewReader(test.Content), test.ETag)
|
||||
if _, err := io.Copy(ioutil.Discard, reader); err != nil {
|
||||
t.Fatalf("Test %d: read failed: %v", i, err)
|
||||
}
|
||||
if ETag := reader.ETag(); !Equal(ETag, test.ETag) {
|
||||
t.Fatalf("Test %d: ETag mismatch: got %q - want %q", i, ETag, test.ETag)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
// MinIO Cloud Storage, (C) 2021 MinIO, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etag
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Tagger is the interface that wraps the basic ETag method.
|
||||
type Tagger interface {
|
||||
ETag() ETag
|
||||
}
|
||||
|
||||
type wrapReader struct {
|
||||
io.Reader
|
||||
Tagger
|
||||
}
|
||||
|
||||
// ETag returns the ETag of the underlying Tagger.
|
||||
func (r *wrapReader) ETag() ETag {
|
||||
if r.Tagger == nil {
|
||||
return nil
|
||||
}
|
||||
return r.Tagger.ETag()
|
||||
}
|
||||
|
||||
// Wrap returns an io.Reader that reads from the wrapped
|
||||
// io.Reader and implements the Tagger interaface.
|
||||
//
|
||||
// If content implements Tagger then the returned Reader
|
||||
// returns ETag of the content. Otherwise, it returns
|
||||
// nil as ETag.
|
||||
//
|
||||
// Wrap provides an adapter for io.Reader implemetations
|
||||
// that don't implement the Tagger interface.
|
||||
// It is mainly used to provide a high-level io.Reader
|
||||
// access to the ETag computed by a low-level io.Reader:
|
||||
//
|
||||
// content := etag.NewReader(r.Body, nil)
|
||||
//
|
||||
// compressedContent := Compress(content)
|
||||
// encryptedContent := Encrypt(compressedContent)
|
||||
//
|
||||
// // Now, we need an io.Reader that can access
|
||||
// // the ETag computed over the content.
|
||||
// reader := etag.Wrap(encryptedContent, content)
|
||||
//
|
||||
func Wrap(wrapped, content io.Reader) io.Reader {
|
||||
if t, ok := content.(Tagger); ok {
|
||||
return wrapReader{
|
||||
Reader: wrapped,
|
||||
Tagger: t,
|
||||
}
|
||||
}
|
||||
return wrapReader{
|
||||
Reader: wrapped,
|
||||
}
|
||||
}
|
||||
|
||||
// A Reader wraps an io.Reader and computes the
|
||||
// MD5 checksum of the read content as ETag.
|
||||
//
|
||||
// Optionally, a Reader can also verify that
|
||||
// the computed ETag matches an expected value.
|
||||
// Therefore, it compares both ETags once the
|
||||
// underlying io.Reader returns io.EOF.
|
||||
// If the computed ETag does not match the
|
||||
// expected ETag then Read returns a VerifyError.
|
||||
//
|
||||
// Reader implements the Tagger interface.
|
||||
type Reader struct {
|
||||
src io.Reader
|
||||
|
||||
md5 hash.Hash
|
||||
checksum ETag
|
||||
|
||||
readN int64
|
||||
}
|
||||
|
||||
// NewReader returns a new Reader that computes the
|
||||
// MD5 checksum of the content read from r as ETag.
|
||||
//
|
||||
// If the provided etag is not nil the returned
|
||||
// Reader compares the etag with the computed
|
||||
// MD5 sum once the r returns io.EOF.
|
||||
func NewReader(r io.Reader, etag ETag) *Reader {
|
||||
if er, ok := r.(*Reader); ok {
|
||||
if er.readN == 0 && Equal(etag, er.checksum) {
|
||||
return er
|
||||
}
|
||||
}
|
||||
return &Reader{
|
||||
src: r,
|
||||
md5: md5.New(),
|
||||
checksum: etag,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads up to len(p) bytes from the underlying
|
||||
// io.Reader as specified by the io.Reader interface.
|
||||
func (r *Reader) Read(p []byte) (int, error) {
|
||||
n, err := r.src.Read(p)
|
||||
r.readN += int64(n)
|
||||
r.md5.Write(p[:n])
|
||||
|
||||
if err == io.EOF && len(r.checksum) != 0 {
|
||||
if etag := r.ETag(); !Equal(etag, r.checksum) {
|
||||
return n, VerifyError{
|
||||
Expected: r.checksum,
|
||||
Computed: etag,
|
||||
}
|
||||
}
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// ETag returns the ETag of all the content read
|
||||
// so far. Reading more content changes the MD5
|
||||
// checksum. Therefore, calling ETag multiple
|
||||
// times may return different results.
|
||||
func (r *Reader) ETag() ETag {
|
||||
sum := r.md5.Sum(nil)
|
||||
return ETag(sum)
|
||||
}
|
||||
|
||||
// VerifyError is an error signaling that a
|
||||
// computed ETag does not match an expected
|
||||
// ETag.
|
||||
type VerifyError struct {
|
||||
Expected ETag
|
||||
Computed ETag
|
||||
}
|
||||
|
||||
func (v VerifyError) Error() string {
|
||||
return fmt.Sprintf("etag: expected ETag %q does not match computed ETag %q", v.Expected, v.Computed)
|
||||
}
|
||||
@@ -80,7 +80,6 @@ func (name Name) Expand() []Name {
|
||||
ObjectCreatedPost, ObjectCreatedPut,
|
||||
ObjectCreatedPutRetention, ObjectCreatedPutLegalHold,
|
||||
ObjectCreatedPutTagging, ObjectCreatedDeleteTagging,
|
||||
ObjectReplicationComplete, ObjectReplicationFailed,
|
||||
}
|
||||
case ObjectRemovedAll:
|
||||
return []Name{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user