mirror of
https://github.com/pgsty/minio.git
synced 2026-11-02 13:52:08 +03:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f225ca3312 | |||
| 8b68e0bfdc | |||
| 6ae97aedc9 | |||
| 960d604013 | |||
| 63bf5f42a1 | |||
| ff80cfd83d | |||
| 99fde2ba85 | |||
| ce0cb913bc | |||
| d99d16e8c3 | |||
| 31743789dc | |||
| 6fd63e920a | |||
| 59cc3e93d6 | |||
| 61a4bb38cd | |||
| b192bc348c | |||
| 6440d0fbf3 | |||
| ee0055b929 | |||
| 24ecc44bac | |||
| 0ae4915a93 | |||
| 4cd777a5e0 | |||
| 65028d4a35 | |||
| caac9d216e | |||
| 057192913c | |||
| f25cbdf43c | |||
| 6da4a9c7bb | |||
| 80ca120088 | |||
| a669946357 | |||
| 7ffc162ea8 | |||
| bcfd7fbbcf | |||
| 486e2e48ea | |||
| 2ddf2ca934 | |||
| 403ec7cf21 | |||
| 29b1a29044 | |||
| b4ab8e095a | |||
| ff4f4d4649 | |||
| 9987ff570b | |||
| cff8235068 | |||
| 9ef132c33b | |||
| ff8269575a |
@@ -148,7 +148,7 @@ hotfix-vars:
|
||||
|
||||
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
|
||||
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.0/pkger_2.2.0_linux_amd64.deb
|
||||
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.0/linux-systemd/distributed/minio.service
|
||||
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
|
||||
@sudo apt install ./pkger_2.2.0_linux_amd64.deb --yes
|
||||
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
|
||||
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
|
||||
|
||||
@@ -58,7 +58,7 @@ func (a adminAPIHandlers) DelConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
password := cred.SecretKey
|
||||
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -162,7 +162,7 @@ func (a adminAPIHandlers) SetConfigKVHandler(w http.ResponseWriter, r *http.Requ
|
||||
password := cred.SecretKey
|
||||
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -443,7 +443,7 @@ func (a adminAPIHandlers) SetConfigHandler(w http.ResponseWriter, r *http.Reques
|
||||
password := cred.SecretKey
|
||||
kvBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.R
|
||||
password := cred.SecretKey
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ func (a adminAPIHandlers) AttachDetachPolicyLDAP(w http.ResponseWriter, r *http.
|
||||
password := cred.SecretKey
|
||||
reqBytes, err := madmin.DecryptData(password, io.LimitReader(r.Body, r.ContentLength))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminConfigBadJSON), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -774,6 +774,16 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// Permission checks:
|
||||
//
|
||||
// 1. Any type of account (i.e. access keys (previously/still called service
|
||||
// accounts), STS accounts, internal IDP accounts, etc) with the
|
||||
// policy.UpdateServiceAccountAdminAction permission can update any service
|
||||
// account.
|
||||
//
|
||||
// 2. We would like to let a user update their own access keys, however it
|
||||
// is currently blocked pending a re-design. Users are still able to delete
|
||||
// and re-create them.
|
||||
if !globalIAMSys.IsAllowed(policy.Args{
|
||||
AccountName: cred.AccessKey,
|
||||
Groups: cred.Groups,
|
||||
@@ -782,15 +792,8 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
|
||||
IsOwner: owner,
|
||||
Claims: cred.Claims,
|
||||
}) {
|
||||
requestUser := cred.AccessKey
|
||||
if cred.ParentUser != "" {
|
||||
requestUser = cred.ParentUser
|
||||
}
|
||||
|
||||
if requestUser != svcAccount.ParentUser {
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
password := cred.SecretKey
|
||||
|
||||
@@ -206,6 +206,7 @@ func TestIAMInternalIDPServerSuite(t *testing.T) {
|
||||
suite.TestCannedPolicies(c)
|
||||
suite.TestGroupAddRemove(c)
|
||||
suite.TestServiceAccountOpsByAdmin(c)
|
||||
suite.TestServiceAccountPrivilegeEscalationBug(c)
|
||||
suite.TestServiceAccountOpsByUser(c)
|
||||
suite.TestAddServiceAccountPerms(c)
|
||||
suite.TearDownSuite(c)
|
||||
@@ -896,14 +897,6 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
|
||||
// 3. Check S3 access
|
||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||
|
||||
// 4. Check that svc account can restrict the policy, and that the
|
||||
// session policy can be updated.
|
||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, accessKey, bucket)
|
||||
|
||||
// 4. Check that service account's secret key and account status can be
|
||||
// updated.
|
||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, accessKey, bucket)
|
||||
|
||||
// 5. Check that service account can be deleted.
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, accessKey, bucket)
|
||||
|
||||
@@ -979,6 +972,95 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
|
||||
c.assertSvcAccDeletion(ctx, s, s.adm, accessKey, bucket)
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestServiceAccountPrivilegeEscalationBug(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := s.client.MakeBucket(ctx, "public", minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("bucket creat error: %v", err)
|
||||
}
|
||||
|
||||
err = s.client.MakeBucket(ctx, "private", minio.MakeBucketOptions{})
|
||||
if err != nil {
|
||||
c.Fatalf("bucket creat error: %v", err)
|
||||
}
|
||||
|
||||
pubPolicyBytes := []byte(`{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::public",
|
||||
"arn:aws:s3:::public/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}`)
|
||||
|
||||
fullS3PolicyBytes := []byte(`{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": [
|
||||
"s3:*"
|
||||
],
|
||||
"Resource": [
|
||||
"arn:aws:s3:::*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`)
|
||||
|
||||
// Create a service account for the root user.
|
||||
cr, err := s.adm.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
||||
TargetUser: globalActiveCred.AccessKey,
|
||||
Policy: pubPolicyBytes,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("admin should be able to create service account for themselves %s", err)
|
||||
}
|
||||
|
||||
svcClient := s.getUserClient(c, cr.AccessKey, cr.SecretKey, "")
|
||||
|
||||
// Check that the service account can access the public bucket.
|
||||
buckets, err := svcClient.ListBuckets(ctx)
|
||||
if err != nil {
|
||||
c.Fatalf("err fetching buckets %s", err)
|
||||
}
|
||||
if len(buckets) != 1 || buckets[0].Name != "public" {
|
||||
c.Fatalf("service account should only have access to public bucket")
|
||||
}
|
||||
|
||||
// Create an madmin client with the service account creds.
|
||||
svcAdmClient, err := madmin.NewWithOptions(s.endpoint, &madmin.Options{
|
||||
Creds: credentials.NewStaticV4(cr.AccessKey, cr.SecretKey, ""),
|
||||
Secure: s.secure,
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Err creating svcacct admin client: %v", err)
|
||||
}
|
||||
svcAdmClient.SetCustomTransport(s.TestSuiteCommon.client.Transport)
|
||||
|
||||
// Attempt to update the policy on the service account.
|
||||
err = svcAdmClient.UpdateServiceAccount(ctx, cr.AccessKey,
|
||||
madmin.UpdateServiceAccountReq{
|
||||
NewPolicy: fullS3PolicyBytes,
|
||||
})
|
||||
|
||||
if err == nil {
|
||||
c.Fatalf("service account should not be able to update policy on itself")
|
||||
} else if !strings.Contains(err.Error(), "Access Denied") {
|
||||
c.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) SetUpAccMgmtPlugin(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
+43
-26
@@ -50,6 +50,7 @@ import (
|
||||
"github.com/minio/madmin-go/v3/estream"
|
||||
"github.com/minio/minio-go/v7/pkg/set"
|
||||
"github.com/minio/minio/internal/dsync"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
@@ -429,6 +430,8 @@ func (a adminAPIHandlers) ServerUpdateHandler(w http.ResponseWriter, r *http.Req
|
||||
// ServiceHandler - POST /minio/admin/v3/service?action={action}
|
||||
// ----------
|
||||
// Supports following actions:
|
||||
// - restart (restarts all the MinIO instances in a setup)
|
||||
// - stop (stops all the MinIO instances in a setup)
|
||||
// - freeze (freezes all incoming S3 API calls)
|
||||
// - unfreeze (unfreezes previously frozen S3 API calls)
|
||||
func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -438,25 +441,30 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
||||
action := vars["action"]
|
||||
|
||||
var serviceSig serviceSignal
|
||||
switch act := madmin.ServiceAction(action); act {
|
||||
switch madmin.ServiceAction(action) {
|
||||
case madmin.ServiceActionRestart:
|
||||
serviceSig = serviceRestart
|
||||
case madmin.ServiceActionStop:
|
||||
serviceSig = serviceStop
|
||||
case madmin.ServiceActionFreeze:
|
||||
serviceSig = serviceFreeze
|
||||
case madmin.ServiceActionUnfreeze:
|
||||
serviceSig = serviceUnFreeze
|
||||
default:
|
||||
process := act == madmin.ServiceActionRestart || act == madmin.ServiceActionStop
|
||||
if process {
|
||||
apiErr := errorCodes.ToAPIErr(ErrMalformedPOSTRequest)
|
||||
apiErr.Description = "process actions are not supported via this API anymore, please upgrade 'mc' or madmin-go to use ServiceV2() API"
|
||||
writeErrorResponseJSON(ctx, w, apiErr, r.URL)
|
||||
return
|
||||
}
|
||||
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application)
|
||||
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
||||
return
|
||||
}
|
||||
|
||||
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
|
||||
var objectAPI ObjectLayer
|
||||
switch serviceSig {
|
||||
case serviceRestart:
|
||||
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
|
||||
case serviceStop:
|
||||
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
|
||||
case serviceFreeze, serviceUnFreeze:
|
||||
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceFreezeAdminAction)
|
||||
}
|
||||
if objectAPI == nil {
|
||||
return
|
||||
}
|
||||
@@ -477,6 +485,8 @@ func (a adminAPIHandlers) ServiceHandler(w http.ResponseWriter, r *http.Request)
|
||||
freezeServices()
|
||||
case serviceUnFreeze:
|
||||
unfreezeServices()
|
||||
case serviceRestart, serviceStop:
|
||||
globalServiceSignalCh <- serviceSig
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,7 +534,7 @@ func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Reques
|
||||
case madmin.ServiceActionUnfreeze:
|
||||
serviceSig = serviceUnFreeze
|
||||
default:
|
||||
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.Application)
|
||||
logger.LogIf(ctx, fmt.Errorf("Unrecognized service action %s requested", action), logger.ErrorKind)
|
||||
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
|
||||
return
|
||||
}
|
||||
@@ -795,7 +805,7 @@ func (a adminAPIHandlers) MetricsHandler(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
// DataUsageInfoHandler - GET /minio/admin/v3/datausage
|
||||
// DataUsageInfoHandler - GET /minio/admin/v3/datausage?capacity={true}
|
||||
// ----------
|
||||
// Get server/cluster data usage info
|
||||
func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -818,6 +828,16 @@ func (a adminAPIHandlers) DataUsageInfoHandler(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// Get capacity info when asked.
|
||||
if r.Form.Get("capacity") == "true" {
|
||||
sinfo := objectAPI.StorageInfo(ctx, false)
|
||||
dataUsageInfo.TotalCapacity = GetTotalUsableCapacity(sinfo.Disks, sinfo)
|
||||
dataUsageInfo.TotalFreeCapacity = GetTotalUsableCapacityFree(sinfo.Disks, sinfo)
|
||||
if dataUsageInfo.TotalCapacity > dataUsageInfo.TotalFreeCapacity {
|
||||
dataUsageInfo.TotalUsedCapacity = dataUsageInfo.TotalCapacity - dataUsageInfo.TotalFreeCapacity
|
||||
}
|
||||
}
|
||||
|
||||
writeSuccessResponseJSON(w, dataUsageInfoJSON)
|
||||
}
|
||||
|
||||
@@ -1219,7 +1239,7 @@ func extractHealInitParams(vars map[string]string, qParms url.Values, r io.Reade
|
||||
if hip.clientToken == "" {
|
||||
jerr := json.NewDecoder(r).Decode(&hip.hs)
|
||||
if jerr != nil {
|
||||
logger.LogIf(GlobalContext, jerr, logger.Application)
|
||||
logger.LogIf(GlobalContext, jerr, logger.ErrorKind)
|
||||
err = ErrRequestBodyParse
|
||||
return
|
||||
}
|
||||
@@ -1632,7 +1652,7 @@ func (a adminAPIHandlers) ObjectSpeedTestHandler(w http.ResponseWriter, r *http.
|
||||
duration = time.Second * 10
|
||||
}
|
||||
|
||||
storageInfo := objectAPI.StorageInfo(ctx, true)
|
||||
storageInfo := objectAPI.StorageInfo(ctx, false)
|
||||
|
||||
sufficientCapacity, canAutotune, capacityErrMsg := validateObjPerfOptions(ctx, storageInfo, concurrent, size, autotune)
|
||||
if !sufficientCapacity {
|
||||
@@ -1909,14 +1929,11 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
setEventStreamHeaders(w)
|
||||
|
||||
// Trace Publisher and peer-trace-client uses nonblocking send and hence does not wait for slow receivers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
|
||||
// Keep 100k buffered channel, should be sufficient to ensure we do not lose any events.
|
||||
traceCh := make(chan madmin.TraceInfo, 100000)
|
||||
|
||||
// Keep 100k buffered channel.
|
||||
// If receiver cannot keep up with that we drop events.
|
||||
traceCh := make(chan []byte, 100000)
|
||||
peers, _ := newPeerRestClients(globalEndpoints)
|
||||
|
||||
err = globalTrace.Subscribe(traceOpts.TraceTypes(), traceCh, ctx.Done(), func(entry madmin.TraceInfo) bool {
|
||||
err = globalTrace.SubscribeJSON(traceOpts.TraceTypes(), traceCh, ctx.Done(), func(entry madmin.TraceInfo) bool {
|
||||
return shouldTrace(entry, traceOpts)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1933,19 +1950,19 @@ func (a adminAPIHandlers) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
peer.Trace(traceCh, ctx.Done(), traceOpts)
|
||||
peer.Trace(ctx, traceCh, traceOpts)
|
||||
}
|
||||
|
||||
keepAliveTicker := time.NewTicker(time.Second)
|
||||
defer keepAliveTicker.Stop()
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
for {
|
||||
select {
|
||||
case entry := <-traceCh:
|
||||
if err := enc.Encode(entry); err != nil {
|
||||
if _, err := w.Write(entry); err != nil {
|
||||
return
|
||||
}
|
||||
grid.PutByteBuffer(entry)
|
||||
if len(traceCh) == 0 {
|
||||
// Flush if nothing is queued
|
||||
w.(http.Flusher).Flush()
|
||||
@@ -2337,7 +2354,7 @@ func getKubernetesInfo(dctx context.Context) madmin.KubernetesInfo {
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewHTTPTransport(),
|
||||
Transport: globalHealthChkTransport,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
@@ -3053,7 +3070,7 @@ func getClusterMetaInfo(ctx context.Context) []byte {
|
||||
ci.Info.NoOfServers = totalNodeCount()
|
||||
ci.Info.MinioVersion = Version
|
||||
|
||||
si := objectAPI.StorageInfo(ctx, true)
|
||||
si := objectAPI.StorageInfo(ctx, false)
|
||||
|
||||
ci.Info.NoOfDrives = len(si.Disks)
|
||||
for _, disk := range si.Disks {
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ func registerAPIRouter(router *mux.Router) {
|
||||
// HeadObject
|
||||
router.Methods(http.MethodHead).Path("/{object:.+}").HandlerFunc(
|
||||
collectAPIStats("headobject", maxClients(gz(httpTraceAll(api.HeadObjectHandler)))))
|
||||
// GetObjectAttribytes
|
||||
// GetObjectAttributes
|
||||
router.Methods(http.MethodGet).Path("/{object:.+}").HandlerFunc(
|
||||
collectAPIStats("getobjectattributes", maxClients(gz(httpTraceHdrs(api.GetObjectAttributesHandler))))).Queries("attributes", "")
|
||||
// CopyObjectPart
|
||||
|
||||
+5
-4
@@ -162,7 +162,8 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
|
||||
s3Err := ErrAccessDenied
|
||||
if _, ok := r.Header[xhttp.AmzContentSha256]; ok &&
|
||||
getRequestAuthType(r) == authTypeSigned {
|
||||
// We only support admin credentials to access admin APIs.
|
||||
|
||||
// Get credential information from the request.
|
||||
cred, owner, s3Err = getReqAccessKeyV4(r, region, serviceS3)
|
||||
if s3Err != ErrNone {
|
||||
return cred, owner, s3Err
|
||||
@@ -256,7 +257,7 @@ func getClaimsFromTokenWithSecret(token, secret string) (map[string]interface{},
|
||||
if err != nil {
|
||||
// Base64 decoding fails, we should log to indicate
|
||||
// something is malforming the request sent by client.
|
||||
logger.LogIf(GlobalContext, err, logger.Application)
|
||||
logger.LogIf(GlobalContext, err, logger.ErrorKind)
|
||||
return nil, errAuthentication
|
||||
}
|
||||
claims.MapClaims[sessionPolicyNameExtracted] = string(spBytes)
|
||||
@@ -338,7 +339,7 @@ func checkRequestAuthTypeWithVID(ctx context.Context, r *http.Request, action po
|
||||
|
||||
func authenticateRequest(ctx context.Context, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
|
||||
if logger.GetReqInfo(ctx) == nil {
|
||||
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.Minio)
|
||||
logger.LogIf(ctx, errors.New("unexpected context.Context does not have a logger.ReqInfo"), logger.ErrorKind)
|
||||
return ErrAccessDenied
|
||||
}
|
||||
|
||||
@@ -377,7 +378,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
|
||||
// To extract region from XML in request body, get copy of request body.
|
||||
payload, err := io.ReadAll(io.LimitReader(r.Body, maxLocationConstraintSize))
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err, logger.Application)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
return ErrMalformedXML
|
||||
}
|
||||
|
||||
|
||||
@@ -213,11 +213,14 @@ func (b BatchJobSnowball) Validate() error {
|
||||
}
|
||||
}
|
||||
_, err := humanize.ParseBytes(*b.SmallerThan)
|
||||
return BatchJobYamlErr{
|
||||
line: b.line,
|
||||
col: b.col,
|
||||
msg: err.Error(),
|
||||
if err != nil {
|
||||
return BatchJobYamlErr{
|
||||
line: b.line,
|
||||
col: b.col,
|
||||
msg: err.Error(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BatchJobSizeFilter supports size based filters - LesserThan and GreaterThan
|
||||
|
||||
+2
-26
@@ -20,16 +20,12 @@ package cmd
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
// Calculates bitrot in chunks and writes the hash into the stream.
|
||||
@@ -90,7 +86,7 @@ func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize
|
||||
}
|
||||
|
||||
// Returns streaming bitrot writer implementation.
|
||||
func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||
func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||
r, w := io.Pipe()
|
||||
h := algo.New()
|
||||
|
||||
@@ -110,7 +106,7 @@ func newStreamingBitrotWriter(disk StorageAPI, volume, filePath string, length i
|
||||
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
|
||||
totalFileSize = bitrotSumsTotalSize + length
|
||||
}
|
||||
r.CloseWithError(disk.CreateFile(context.TODO(), volume, filePath, totalFileSize, r))
|
||||
r.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, r))
|
||||
}()
|
||||
return bw
|
||||
}
|
||||
@@ -154,29 +150,12 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
||||
// Can never happen unless there are programmer bugs
|
||||
return 0, errUnexpected
|
||||
}
|
||||
ignoredErrs := []error{
|
||||
errDiskNotFound,
|
||||
}
|
||||
if strings.HasPrefix(b.volume, minioMetaBucket) {
|
||||
ignoredErrs = append(ignoredErrs,
|
||||
errFileNotFound,
|
||||
errVolumeNotFound,
|
||||
errFileVersionNotFound,
|
||||
)
|
||||
}
|
||||
if b.rc == nil {
|
||||
// For the first ReadAt() call we need to open the stream for reading.
|
||||
b.currOffset = offset
|
||||
streamOffset := (offset/b.shardSize)*int64(b.h.Size()) + offset
|
||||
if len(b.data) == 0 && b.tillOffset != streamOffset {
|
||||
b.rc, err = b.disk.ReadFileStream(context.TODO(), b.volume, b.filePath, streamOffset, b.tillOffset-streamOffset)
|
||||
if err != nil {
|
||||
if !IsErr(err, ignoredErrs...) {
|
||||
logger.LogOnceIf(GlobalContext,
|
||||
fmt.Errorf("Reading erasure shards at (%s: %s/%s) returned '%w', will attempt to reconstruct if we have quorum",
|
||||
b.disk, b.volume, b.filePath, err), "bitrot-read-file-stream-"+b.volume+"-"+b.filePath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b.rc = io.NewSectionReader(bytes.NewReader(b.data), streamOffset, b.tillOffset-streamOffset)
|
||||
}
|
||||
@@ -198,10 +177,7 @@ func (b *streamingBitrotReader) ReadAt(buf []byte, offset int64) (int, error) {
|
||||
return 0, err
|
||||
}
|
||||
b.h.Write(buf)
|
||||
|
||||
if !bytes.Equal(b.h.Sum(nil), b.hashBytes) {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s - content hash does not match - expected %s, got %s",
|
||||
b.disk, b.volume, b.filePath, hex.EncodeToString(b.hashBytes), hex.EncodeToString(b.h.Sum(nil))))
|
||||
return 0, errFileCorrupt
|
||||
}
|
||||
b.currOffset += int64(len(buf))
|
||||
|
||||
@@ -19,11 +19,8 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
// Implementation to calculate bitrot for the whole file.
|
||||
@@ -38,12 +35,10 @@ type wholeBitrotWriter struct {
|
||||
func (b *wholeBitrotWriter) Write(p []byte) (int, error) {
|
||||
err := b.disk.AppendFile(context.TODO(), b.volume, b.filePath, p)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
|
||||
return 0, err
|
||||
}
|
||||
_, err = b.Hash.Write(p)
|
||||
if err != nil {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s returned %w", b.disk, err))
|
||||
return 0, err
|
||||
}
|
||||
return len(p), nil
|
||||
@@ -72,12 +67,10 @@ func (b *wholeBitrotReader) ReadAt(buf []byte, offset int64) (n int, err error)
|
||||
if b.buf == nil {
|
||||
b.buf = make([]byte, b.tillOffset-offset)
|
||||
if _, err := b.disk.ReadFile(context.TODO(), b.volume, b.filePath, offset, b.buf, b.verifier); err != nil {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, err))
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if len(b.buf) < len(buf) {
|
||||
logger.LogIf(GlobalContext, fmt.Errorf("Drive: %s -> %s/%s returned %w", b.disk, b.volume, b.filePath, errLessData))
|
||||
return 0, errLessData
|
||||
}
|
||||
n = copy(buf, b.buf)
|
||||
|
||||
+2
-2
@@ -102,9 +102,9 @@ func BitrotAlgorithmFromString(s string) (a BitrotAlgorithm) {
|
||||
return
|
||||
}
|
||||
|
||||
func newBitrotWriter(disk StorageAPI, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||
func newBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
|
||||
if algo == HighwayHash256S {
|
||||
return newStreamingBitrotWriter(disk, volume, filePath, length, algo, shardSize)
|
||||
return newStreamingBitrotWriter(disk, origvolume, volume, filePath, length, algo, shardSize)
|
||||
}
|
||||
return newWholeBitrotWriter(disk, volume, filePath, algo, shardSize)
|
||||
}
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func testBitrotReaderWriterAlgo(t *testing.T, bitrotAlgo BitrotAlgorithm) {
|
||||
|
||||
disk.MakeVol(context.Background(), volume)
|
||||
|
||||
writer := newBitrotWriter(disk, volume, filePath, 35, bitrotAlgo, 10)
|
||||
writer := newBitrotWriter(disk, "", volume, filePath, 35, bitrotAlgo, 10)
|
||||
|
||||
_, err = writer.Write([]byte("aaaaaaaaaa"))
|
||||
if err != nil {
|
||||
|
||||
@@ -152,10 +152,12 @@ func (client *bootstrapRESTClient) Verify(ctx context.Context, srcCfg *ServerSys
|
||||
return nil
|
||||
}
|
||||
|
||||
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{}))
|
||||
recvCfg, err := serverVerifyHandler.Call(ctx, client.gridConn, grid.NewMSS())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// We do not need the response after returning.
|
||||
defer serverVerifyHandler.PutResponse(recvCfg)
|
||||
|
||||
return srcCfg.Diff(recvCfg)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ func checkRemoteEndpoint(ctx context.Context, epURL *url.URL) error {
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: NewHTTPTransport(),
|
||||
Transport: globalRemoteTargetTransport,
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
@@ -3213,6 +3213,9 @@ func queueReplicationHeal(ctx context.Context, bucket string, oi ObjectInfo, rcf
|
||||
return roi
|
||||
}
|
||||
|
||||
if isVeeamSOSAPIObject(oi.Name) {
|
||||
return roi
|
||||
}
|
||||
if rcfg.Config == nil || rcfg.remotes == nil {
|
||||
return roi
|
||||
}
|
||||
@@ -3390,7 +3393,7 @@ func (p *ReplicationPool) persistToDrive(ctx context.Context, v MRFReplicateEntr
|
||||
|
||||
for _, localDrive := range localDrives {
|
||||
r := newReader()
|
||||
err := localDrive.CreateFile(ctx, minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
||||
err := localDrive.CreateFile(ctx, "", minioMetaBucket, pathJoin(replicationMRFDir, globalLocalNodeNameHex+".bin"), -1, r)
|
||||
r.Close()
|
||||
if err == nil {
|
||||
break
|
||||
|
||||
+2
-1
@@ -397,6 +397,7 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
|
||||
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
|
||||
ctxt.IdleTimeout = ctx.Duration("idle-timeout")
|
||||
ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout")
|
||||
ctxt.MaxIdleConnsPerHost = ctx.Int("max-idle-conns-per-host")
|
||||
|
||||
if conf := ctx.String("config"); len(conf) > 0 {
|
||||
err = mergeServerCtxtFromConfigFile(conf, ctxt)
|
||||
@@ -1033,7 +1034,7 @@ func getTLSConfig() (x509Certs []*x509.Certificate, manager *certs.Manager, secu
|
||||
}
|
||||
if err = manager.AddCertificate(certFile, keyFile); err != nil {
|
||||
err = fmt.Errorf("Unable to load TLS certificate '%s,%s': %w", certFile, keyFile, err)
|
||||
logger.LogIf(GlobalContext, err, logger.Minio)
|
||||
logger.LogIf(GlobalContext, err, logger.ErrorKind)
|
||||
}
|
||||
}
|
||||
secureConn = true
|
||||
|
||||
@@ -196,7 +196,7 @@ func (sys *HTTPConsoleLoggerSys) Send(ctx context.Context, entry interface{}) er
|
||||
sys.logBuf.Value = lg
|
||||
sys.logBuf = sys.logBuf.Next()
|
||||
sys.Unlock()
|
||||
err := sys.console.Send(entry, string(logger.All))
|
||||
err := sys.console.Send(entry)
|
||||
if err != nil {
|
||||
atomic.AddInt64(&sys.failedMessages, 1)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ type BucketUsageInfo struct {
|
||||
|
||||
// DataUsageInfo represents data usage stats of the underlying Object API
|
||||
type DataUsageInfo struct {
|
||||
TotalCapacity uint64 `json:"capacity,omitempty"`
|
||||
TotalUsedCapacity uint64 `json:"usedCapacity,omitempty"`
|
||||
TotalFreeCapacity uint64 `json:"freeCapacity,omitempty"`
|
||||
|
||||
// LastUpdate is the timestamp of when the data usage info was last updated.
|
||||
// This does not indicate a full scan.
|
||||
LastUpdate time.Time `json:"lastUpdate"`
|
||||
@@ -87,6 +91,7 @@ type DataUsageInfo struct {
|
||||
// Objects total size across all buckets
|
||||
ObjectsTotalSize uint64 `json:"objectsTotalSize"`
|
||||
ReplicationInfo map[string]BucketTargetUsageInfo `json:"objectsReplicationInfo"`
|
||||
|
||||
// Total number of buckets in this cluster
|
||||
BucketsCount uint64 `json:"bucketsCount"`
|
||||
|
||||
|
||||
+3
-1
@@ -155,7 +155,9 @@ func loadDataUsageFromBackend(ctx context.Context, objAPI ObjectLayer) (DataUsag
|
||||
bui.ReplicationFailedSizeV1 > 0 || bui.ReplicationPendingCountV1 > 0 {
|
||||
cfg, _ := getReplicationConfig(GlobalContext, bucket)
|
||||
if cfg != nil && cfg.RoleArn != "" {
|
||||
dataUsageInfo.ReplicationInfo = make(map[string]BucketTargetUsageInfo)
|
||||
if dataUsageInfo.ReplicationInfo == nil {
|
||||
dataUsageInfo.ReplicationInfo = make(map[string]BucketTargetUsageInfo)
|
||||
}
|
||||
dataUsageInfo.ReplicationInfo[cfg.RoleArn] = BucketTargetUsageInfo{
|
||||
ReplicationFailedSize: bui.ReplicationFailedSizeV1,
|
||||
ReplicationFailedCount: bui.ReplicationFailedCountV1,
|
||||
|
||||
+73
-7
@@ -62,8 +62,9 @@ type ProxyEndpoint struct {
|
||||
// Node holds information about a node in this cluster
|
||||
type Node struct {
|
||||
*url.URL
|
||||
Pools []int
|
||||
IsLocal bool
|
||||
Pools []int
|
||||
IsLocal bool
|
||||
GridHost string
|
||||
}
|
||||
|
||||
// Endpoint - any type of endpoint.
|
||||
@@ -254,6 +255,7 @@ func (l EndpointServerPools) GetNodes() (nodes []Node) {
|
||||
Scheme: ep.Scheme,
|
||||
Host: ep.Host,
|
||||
}
|
||||
node.GridHost = ep.GridHost()
|
||||
}
|
||||
if !slices.Contains(node.Pools, ep.PoolIdx) {
|
||||
node.Pools = append(node.Pools, ep.PoolIdx)
|
||||
@@ -411,6 +413,70 @@ func (l EndpointServerPools) GridHosts() (gridHosts []string, gridLocal string)
|
||||
return gridHosts, gridLocal
|
||||
}
|
||||
|
||||
// FindGridHostsFromPeerPool will return a matching peerPool from provided peer (as string)
|
||||
func (l EndpointServerPools) FindGridHostsFromPeerPool(peer string) []int {
|
||||
if peer == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pools []int
|
||||
for _, ep := range l {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
if endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
|
||||
if !slices.Contains(pools, endpoint.PoolIdx) {
|
||||
pools = append(pools, endpoint.PoolIdx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pools
|
||||
}
|
||||
|
||||
// FindGridHostsFromPeerStr will return a matching peer from provided peer (as string)
|
||||
func (l EndpointServerPools) FindGridHostsFromPeerStr(peer string) (peerGrid string) {
|
||||
if peer == "" {
|
||||
return ""
|
||||
}
|
||||
for _, ep := range l {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
if endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
|
||||
if endpoint.Host == peer {
|
||||
return endpoint.GridHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// FindGridHostsFromPeer will return a matching peer from provided peer.
|
||||
func (l EndpointServerPools) FindGridHostsFromPeer(peer *xnet.Host) (peerGrid string) {
|
||||
if peer == nil {
|
||||
return ""
|
||||
}
|
||||
for _, ep := range l {
|
||||
for _, endpoint := range ep.Endpoints {
|
||||
if endpoint.IsLocal {
|
||||
continue
|
||||
}
|
||||
host, err := xnet.ParseHost(endpoint.Host)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if host.String() == peer.String() {
|
||||
return endpoint.GridHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Hostnames - returns list of unique hostnames
|
||||
func (l EndpointServerPools) Hostnames() []string {
|
||||
foundSet := set.NewStringSet()
|
||||
@@ -507,7 +573,7 @@ func hostResolveToLocalhost(endpoint Endpoint) bool {
|
||||
endpoint.Hostname(),
|
||||
)
|
||||
ctx := logger.SetReqInfo(GlobalContext, reqInfo)
|
||||
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
|
||||
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.ErrorKind)
|
||||
return false
|
||||
}
|
||||
var loopback int
|
||||
@@ -579,7 +645,7 @@ func (endpoints Endpoints) UpdateIsLocal() error {
|
||||
ctx := logger.SetReqInfo(GlobalContext,
|
||||
reqInfo)
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
|
||||
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.Application)
|
||||
endpoints[i].Hostname()), endpoints[i].Hostname(), logger.ErrorKind)
|
||||
}
|
||||
|
||||
continue
|
||||
@@ -608,7 +674,7 @@ func (endpoints Endpoints) UpdateIsLocal() error {
|
||||
))
|
||||
ctx := logger.SetReqInfo(GlobalContext,
|
||||
reqInfo)
|
||||
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.Application)
|
||||
logger.LogOnceIf(ctx, err, endpoints[i].Hostname(), logger.ErrorKind)
|
||||
}
|
||||
} else {
|
||||
resolvedList[i] = true
|
||||
@@ -773,7 +839,7 @@ func (p PoolEndpointList) UpdateIsLocal() error {
|
||||
ctx := logger.SetReqInfo(GlobalContext,
|
||||
reqInfo)
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("%s resolves to localhost in a containerized deployment, waiting for it to resolve to a valid IP",
|
||||
endpoint.Hostname()), endpoint.Hostname(), logger.Application)
|
||||
endpoint.Hostname()), endpoint.Hostname(), logger.ErrorKind)
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -801,7 +867,7 @@ func (p PoolEndpointList) UpdateIsLocal() error {
|
||||
))
|
||||
ctx := logger.SetReqInfo(GlobalContext,
|
||||
reqInfo)
|
||||
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.Application)
|
||||
logger.LogOnceIf(ctx, err, endpoint.Hostname(), logger.ErrorKind)
|
||||
}
|
||||
} else {
|
||||
resolvedList[endpoint] = true
|
||||
|
||||
@@ -80,11 +80,9 @@ func (e *Erasure) EncodeData(ctx context.Context, data []byte) ([][]byte, error)
|
||||
}
|
||||
encoded, err := e.encoder().Split(data)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
if err = e.encoder().Encode(encoded); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return nil, err
|
||||
}
|
||||
return encoded, nil
|
||||
@@ -111,11 +109,7 @@ func (e *Erasure) DecodeDataBlocks(data [][]byte) error {
|
||||
// DecodeDataAndParityBlocks decodes the given erasure-coded data and verifies it.
|
||||
// It returns an error if the decoding failed.
|
||||
func (e *Erasure) DecodeDataAndParityBlocks(ctx context.Context, data [][]byte) error {
|
||||
if err := e.encoder().Reconstruct(data); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return e.encoder().Reconstruct(data)
|
||||
}
|
||||
|
||||
// ShardSize - returns actual shared size from erasure blockSize.
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
// Reads in parallel from readers.
|
||||
@@ -211,11 +210,9 @@ func (p *parallelReader) Read(dst [][]byte) ([][]byte, error) {
|
||||
// A set of preferred drives can be supplied. In that case they will be used and the data reconstructed.
|
||||
func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.ReaderAt, offset, length, totalLength int64, prefer []bool) (written int64, derr error) {
|
||||
if offset < 0 || length < 0 {
|
||||
logger.LogIf(ctx, errInvalidArgument)
|
||||
return -1, errInvalidArgument
|
||||
}
|
||||
if offset+length > totalLength {
|
||||
logger.LogIf(ctx, errInvalidArgument)
|
||||
return -1, errInvalidArgument
|
||||
}
|
||||
|
||||
@@ -269,7 +266,6 @@ func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.Read
|
||||
}
|
||||
|
||||
if err = e.DecodeDataBlocks(bufs); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return -1, err
|
||||
}
|
||||
|
||||
@@ -282,7 +278,6 @@ func (e Erasure) Decode(ctx context.Context, writer io.Writer, readers []io.Read
|
||||
}
|
||||
|
||||
if bytesWritten != length {
|
||||
logger.LogIf(ctx, errLessData)
|
||||
return bytesWritten, errLessData
|
||||
}
|
||||
|
||||
@@ -321,7 +316,6 @@ func (e Erasure) Heal(ctx context.Context, writers []io.Writer, readers []io.Rea
|
||||
}
|
||||
|
||||
if err = e.DecodeDataAndParityBlocks(ctx, bufs); err != nil {
|
||||
logger.LogOnceIf(ctx, err, "erasure-heal-decode")
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -332,7 +326,6 @@ func (e Erasure) Heal(ctx context.Context, writers []io.Writer, readers []io.Rea
|
||||
}
|
||||
|
||||
if err = w.Write(ctx, bufs); err != nil {
|
||||
logger.LogOnceIf(ctx, err, "erasure-heal-write")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ func TestErasureDecode(t *testing.T) {
|
||||
buffer := make([]byte, test.blocksize, 2*test.blocksize)
|
||||
writers := make([]io.Writer, len(disks))
|
||||
for i, disk := range disks {
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(test.data), writeAlgorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(test.data), writeAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
n, err := erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1)
|
||||
closeBitrotWriters(writers)
|
||||
@@ -228,7 +228,7 @@ func TestErasureDecodeRandomOffsetLength(t *testing.T) {
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(length), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(length), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
// 10000 iterations with random offsets and lengths.
|
||||
@@ -297,7 +297,7 @@ func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64,
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
content := make([]byte, size)
|
||||
|
||||
@@ -22,9 +22,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/minio/minio/internal/hash"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
// Writes in parallel to writers
|
||||
@@ -92,29 +89,26 @@ func (e *Erasure) Encode(ctx context.Context, src io.Reader, writers []io.Writer
|
||||
io.EOF,
|
||||
io.ErrUnexpectedEOF,
|
||||
}...) {
|
||||
if !hash.IsChecksumMismatch(err) {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
eof := err == io.EOF || err == io.ErrUnexpectedEOF
|
||||
if n == 0 && total != 0 {
|
||||
// Reached EOF, nothing more to be done.
|
||||
break
|
||||
}
|
||||
|
||||
// We take care of the situation where if n == 0 and total == 0 by creating empty data and parity files.
|
||||
blocks, err = e.EncodeData(ctx, buf[:n])
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if err = writer.Write(ctx, blocks); err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
total += int64(n)
|
||||
if eof {
|
||||
break
|
||||
|
||||
@@ -41,7 +41,7 @@ func (a badDisk) ReadFileStream(ctx context.Context, volume, path string, offset
|
||||
return nil, errFaultyDisk
|
||||
}
|
||||
|
||||
func (a badDisk) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error {
|
||||
func (a badDisk) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) error {
|
||||
return errFaultyDisk
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ func TestErasureEncode(t *testing.T) {
|
||||
if disk == OfflineDisk {
|
||||
continue
|
||||
}
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
|
||||
}
|
||||
n, err := erasure.Encode(context.Background(), bytes.NewReader(data[test.offset:]), writers, buffer, erasure.dataBlocks+1)
|
||||
closeBitrotWriters(writers)
|
||||
@@ -127,7 +127,7 @@ func TestErasureEncode(t *testing.T) {
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "object2", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "object2", erasure.ShardFileSize(int64(len(data[test.offset:]))), test.algorithm, erasure.ShardSize())
|
||||
}
|
||||
for j := range disks[:test.offDisks] {
|
||||
switch w := writers[j].(type) {
|
||||
@@ -192,7 +192,7 @@ func benchmarkErasureEncode(data, parity, dataDown, parityDown int, size int64,
|
||||
Recursive: false,
|
||||
Immediate: false,
|
||||
})
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
_, err := erasure.Encode(context.Background(), bytes.NewReader(content), writers, buffer, erasure.dataBlocks+1)
|
||||
closeBitrotWriters(writers)
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestErasureHeal(t *testing.T) {
|
||||
buffer := make([]byte, test.blocksize, 2*test.blocksize)
|
||||
writers := make([]io.Writer, len(disks))
|
||||
for i, disk := range disks {
|
||||
writers[i] = newBitrotWriter(disk, "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, "", "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
|
||||
}
|
||||
_, err = erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1)
|
||||
closeBitrotWriters(writers)
|
||||
@@ -128,7 +128,7 @@ func TestErasureHeal(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
os.Remove(pathJoin(disk.String(), "testbucket", "testobject"))
|
||||
staleWriters[i] = newBitrotWriter(disk, "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
|
||||
staleWriters[i] = newBitrotWriter(disk, "", "testbucket", "testobject", erasure.ShardFileSize(test.size), test.algorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
// test case setup is complete - now call Heal()
|
||||
|
||||
@@ -246,7 +246,7 @@ func TestListOnlineDisks(t *testing.T) {
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
fi, err := getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo %v", err)
|
||||
@@ -424,7 +424,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", true, true)
|
||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", true, true)
|
||||
fi, err := getLatestFileInfo(ctx, partsMetadata, z.serverPools[0].sets[0].defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo %v", err)
|
||||
@@ -437,7 +437,7 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
|
||||
partsMetadata[j].ModTime = test.modTimes[j]
|
||||
}
|
||||
|
||||
if erasureDisks, err = writeUniqueFileInfo(ctx, erasureDisks, bucket, object, partsMetadata, diskCount(erasureDisks)); err != nil {
|
||||
if erasureDisks, err = writeUniqueFileInfo(ctx, erasureDisks, "", bucket, object, partsMetadata, diskCount(erasureDisks)); err != nil {
|
||||
t.Fatal(ctx, err)
|
||||
}
|
||||
|
||||
@@ -534,7 +534,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
_, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
_, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
readQuorum := len(erasureDisks) / 2
|
||||
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
|
||||
t.Fatalf("Failed to read xl meta data %v", reducedErr)
|
||||
@@ -542,7 +542,7 @@ func TestDisksWithAllParts(t *testing.T) {
|
||||
|
||||
// Test 1: Test that all disks are returned without any failures with
|
||||
// unmodified meta data
|
||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read xl meta data %v", err)
|
||||
}
|
||||
|
||||
+60
-38
@@ -178,7 +178,10 @@ func shouldHealObjectOnDisk(erErr, dataErr error, meta FileInfo, latestMeta File
|
||||
return false
|
||||
}
|
||||
|
||||
const xMinIOHealing = ReservedMetadataPrefix + "healing"
|
||||
const (
|
||||
xMinIOHealing = ReservedMetadataPrefix + "healing"
|
||||
xMinIODataMov = ReservedMetadataPrefix + "data-mov"
|
||||
)
|
||||
|
||||
// SetHealing marks object (version) as being healed.
|
||||
// Note: this is to be used only from healObject
|
||||
@@ -196,6 +199,21 @@ func (fi FileInfo) Healing() bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// SetDataMov marks object (version) as being currently
|
||||
// in movement, such as decommissioning or rebalance.
|
||||
func (fi *FileInfo) SetDataMov() {
|
||||
if fi.Metadata == nil {
|
||||
fi.Metadata = make(map[string]string)
|
||||
}
|
||||
fi.Metadata[xMinIODataMov] = "true"
|
||||
}
|
||||
|
||||
// DataMov returns true if object is being in movement
|
||||
func (fi FileInfo) DataMov() bool {
|
||||
_, ok := fi.Metadata[xMinIODataMov]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Heals an object by re-writing corrupt/missing erasure blocks.
|
||||
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
|
||||
dryRun := opts.DryRun
|
||||
@@ -230,7 +248,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
}
|
||||
|
||||
// Re-read when we have lock...
|
||||
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, bucket, object, versionID, true, true)
|
||||
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, "", bucket, object, versionID, true, true)
|
||||
if isAllNotFound(errs) {
|
||||
err := errFileNotFound
|
||||
if versionID != "" {
|
||||
@@ -518,7 +536,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
inlineBuffers[i] = bytes.NewBuffer(make([]byte, 0, erasure.ShardFileSize(latestMeta.Size)+32))
|
||||
writers[i] = newStreamingBitrotWriterBuffer(inlineBuffers[i], DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
} else {
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, partPath,
|
||||
writers[i] = newBitrotWriter(disk, bucket, minioMetaTmpBucket, partPath,
|
||||
tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
}
|
||||
@@ -796,7 +814,7 @@ func statAllDirs(ctx context.Context, storageDisks []StorageAPI, bucket, prefix
|
||||
}
|
||||
index := index
|
||||
g.Go(func() error {
|
||||
entries, err := storageDisks[index].ListDir(ctx, bucket, prefix, 1)
|
||||
entries, err := storageDisks[index].ListDir(ctx, "", bucket, prefix, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -810,6 +828,10 @@ func statAllDirs(ctx context.Context, storageDisks []StorageAPI, bucket, prefix
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func isAllVolumeNotFound(errs []error) bool {
|
||||
return countErrs(errs, errVolumeNotFound) == len(errs)
|
||||
}
|
||||
|
||||
// isAllNotFound will return if any element of the error slice is not
|
||||
// errFileNotFound, errFileVersionNotFound or errVolumeNotFound.
|
||||
// A 0 length slice will always return false.
|
||||
@@ -878,11 +900,10 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
// We can consider an object data not reliable
|
||||
// when xl.meta is not found in read quorum disks.
|
||||
// or when xl.meta is not readable in read quorum disks.
|
||||
danglingErrsCount := func(cerrs []error) (int, int, int) {
|
||||
danglingErrsCount := func(cerrs []error) (int, int) {
|
||||
var (
|
||||
notFoundCount int
|
||||
corruptedCount int
|
||||
driveNotFoundCount int
|
||||
nonActionableCount int
|
||||
)
|
||||
for _, readErr := range cerrs {
|
||||
if readErr == nil {
|
||||
@@ -891,29 +912,16 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
switch {
|
||||
case errors.Is(readErr, errFileNotFound) || errors.Is(readErr, errFileVersionNotFound):
|
||||
notFoundCount++
|
||||
case errors.Is(readErr, errFileCorrupt):
|
||||
corruptedCount++
|
||||
default:
|
||||
// All other errors are non-actionable
|
||||
driveNotFoundCount++
|
||||
nonActionableCount++
|
||||
}
|
||||
}
|
||||
return notFoundCount, corruptedCount, driveNotFoundCount
|
||||
return notFoundCount, nonActionableCount
|
||||
}
|
||||
|
||||
ndataErrs := make([]error, len(dataErrs))
|
||||
for i := range dataErrs {
|
||||
if errs[i] != dataErrs[i] {
|
||||
// Only count part errors, if the error is not
|
||||
// same as xl.meta error. This is to avoid
|
||||
// double counting when both parts and xl.meta
|
||||
// are not available.
|
||||
ndataErrs[i] = dataErrs[i]
|
||||
}
|
||||
}
|
||||
|
||||
notFoundMetaErrs, corruptedMetaErrs, driveNotFoundMetaErrs := danglingErrsCount(errs)
|
||||
notFoundPartsErrs, corruptedPartsErrs, driveNotFoundPartsErrs := danglingErrsCount(ndataErrs)
|
||||
notFoundMetaErrs, nonActionableMetaErrs := danglingErrsCount(errs)
|
||||
notFoundPartsErrs, nonActionablePartsErrs := danglingErrsCount(dataErrs)
|
||||
|
||||
for _, m := range metaArr {
|
||||
if m.IsValid() {
|
||||
@@ -923,10 +931,9 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
}
|
||||
|
||||
if !validMeta.IsValid() {
|
||||
// validMeta is invalid because notFoundPartsErrs is
|
||||
// greater than parity blocks, thus invalidating the FileInfo{}
|
||||
// every dataErrs[i], metaArr[i] is an empty FileInfo{}
|
||||
dataBlocks := (len(ndataErrs) + 1) / 2
|
||||
// validMeta is invalid because all xl.meta is missing apparently
|
||||
// we should figure out if dataDirs are also missing > dataBlocks.
|
||||
dataBlocks := (len(dataErrs) + 1) / 2
|
||||
if notFoundPartsErrs > dataBlocks {
|
||||
// Not using parity to ensure that we do not delete
|
||||
// any valid content, if any is recoverable. But if
|
||||
@@ -943,25 +950,40 @@ func isObjectDangling(metaArr []FileInfo, errs []error, dataErrs []error) (valid
|
||||
return validMeta, false
|
||||
}
|
||||
|
||||
if driveNotFoundMetaErrs > 0 || driveNotFoundPartsErrs > 0 {
|
||||
if nonActionableMetaErrs > 0 || nonActionablePartsErrs > 0 {
|
||||
return validMeta, false
|
||||
}
|
||||
|
||||
if validMeta.Deleted {
|
||||
// notFoundPartsErrs is ignored since
|
||||
// - delete marker does not have any parts
|
||||
return validMeta, corruptedMetaErrs+notFoundMetaErrs > len(errs)/2
|
||||
dataBlocks := (len(errs) + 1) / 2
|
||||
return validMeta, notFoundMetaErrs > dataBlocks
|
||||
}
|
||||
|
||||
totalErrs := notFoundMetaErrs + corruptedMetaErrs + notFoundPartsErrs + corruptedPartsErrs
|
||||
if validMeta.IsRemote() {
|
||||
// notFoundPartsErrs is ignored since
|
||||
// - transition status of complete has no parts
|
||||
totalErrs = notFoundMetaErrs + corruptedMetaErrs
|
||||
quorum := validMeta.Erasure.DataBlocks
|
||||
if validMeta.Erasure.DataBlocks == validMeta.Erasure.ParityBlocks {
|
||||
quorum++
|
||||
}
|
||||
|
||||
// We have valid meta, now verify if we have enough files with parity blocks.
|
||||
return validMeta, totalErrs > validMeta.Erasure.ParityBlocks
|
||||
// TODO: It is possible to replay the object via just single
|
||||
// xl.meta file, considering quorum number of data-dirs are still
|
||||
// present on other drives.
|
||||
//
|
||||
// However this requires a bit of a rewrite, leave this up for
|
||||
// future work.
|
||||
|
||||
if notFoundMetaErrs > 0 && notFoundMetaErrs >= quorum {
|
||||
// All xl.meta is beyond data blocks missing, this is dangling
|
||||
return validMeta, true
|
||||
}
|
||||
|
||||
if notFoundPartsErrs > 0 && notFoundPartsErrs >= quorum {
|
||||
// All data-dir is beyond data blocks missing, this is dangling
|
||||
return validMeta, true
|
||||
}
|
||||
|
||||
return validMeta, false
|
||||
}
|
||||
|
||||
// HealObject - heal the given object, automatically deletes the object if stale/corrupted if `remove` is true.
|
||||
@@ -993,7 +1015,7 @@ func (er erasureObjects) HealObject(ctx context.Context, bucket, object, version
|
||||
|
||||
// Perform quick read without lock.
|
||||
// This allows to quickly check if all is ok or all are missing.
|
||||
_, errs := readAllFileInfo(healCtx, storageDisks, bucket, object, versionID, false, false)
|
||||
_, errs := readAllFileInfo(healCtx, storageDisks, "", bucket, object, versionID, false, false)
|
||||
if isAllNotFound(errs) {
|
||||
err := errFileNotFound
|
||||
if versionID != "" {
|
||||
|
||||
+107
-57
@@ -41,6 +41,10 @@ func TestIsObjectDangling(t *testing.T) {
|
||||
fi := newFileInfo("test-object", 2, 2)
|
||||
fi.Erasure.Index = 1
|
||||
|
||||
ifi := newFileInfo("test-object", 2, 2)
|
||||
ifi.SetInlineData()
|
||||
ifi.Erasure.Index = 1
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
metaArr []FileInfo
|
||||
@@ -129,13 +133,77 @@ func TestIsObjectDangling(t *testing.T) {
|
||||
expectedMeta: FileInfo{},
|
||||
expectedDangling: false,
|
||||
},
|
||||
{
|
||||
name: "FileInfoUnDecided-case4",
|
||||
metaArr: []FileInfo{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
ifi,
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
errFileCorrupt,
|
||||
nil,
|
||||
},
|
||||
dataErrs: nil,
|
||||
expectedMeta: ifi,
|
||||
expectedDangling: false,
|
||||
},
|
||||
{
|
||||
name: "FileInfoUnDecided-case5-(ignore errFileCorrupt error)",
|
||||
metaArr: []FileInfo{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
fi,
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
dataErrs: []error{
|
||||
errFileCorrupt,
|
||||
errFileNotFound,
|
||||
nil,
|
||||
errFileCorrupt,
|
||||
},
|
||||
expectedMeta: fi,
|
||||
expectedDangling: false,
|
||||
},
|
||||
{
|
||||
name: "FileInfoUnDecided-case6-(data-dir intact)",
|
||||
metaArr: []FileInfo{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
fi,
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
errFileNotFound,
|
||||
errFileNotFound,
|
||||
nil,
|
||||
},
|
||||
dataErrs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
expectedMeta: fi,
|
||||
expectedDangling: false,
|
||||
},
|
||||
{
|
||||
name: "FileInfoDecided-case1",
|
||||
metaArr: []FileInfo{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
fi,
|
||||
ifi,
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
@@ -144,25 +212,7 @@ func TestIsObjectDangling(t *testing.T) {
|
||||
nil,
|
||||
},
|
||||
dataErrs: nil,
|
||||
expectedMeta: fi,
|
||||
expectedDangling: true,
|
||||
},
|
||||
{
|
||||
name: "FileInfoDecided-case2",
|
||||
metaArr: []FileInfo{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
fi,
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
errFileCorrupt,
|
||||
nil,
|
||||
},
|
||||
dataErrs: nil,
|
||||
expectedMeta: fi,
|
||||
expectedMeta: ifi,
|
||||
expectedDangling: true,
|
||||
},
|
||||
{
|
||||
@@ -175,8 +225,8 @@ func TestIsObjectDangling(t *testing.T) {
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
errFileCorrupt,
|
||||
errFileNotFound,
|
||||
errFileNotFound,
|
||||
nil,
|
||||
},
|
||||
dataErrs: nil,
|
||||
@@ -184,26 +234,26 @@ func TestIsObjectDangling(t *testing.T) {
|
||||
expectedDangling: true,
|
||||
},
|
||||
{
|
||||
name: "FileInfoDecided-case2-(duplicate data errors)",
|
||||
name: "FileInfoDecided-case3-(enough data-dir missing)",
|
||||
metaArr: []FileInfo{
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{Deleted: true},
|
||||
fi,
|
||||
},
|
||||
errs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
errFileCorrupt,
|
||||
errFileNotFound,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
dataErrs: []error{
|
||||
errFileNotFound,
|
||||
errFileCorrupt,
|
||||
nil,
|
||||
errFileNotFound,
|
||||
nil,
|
||||
errFileNotFound,
|
||||
},
|
||||
expectedMeta: FileInfo{Deleted: true},
|
||||
expectedMeta: fi,
|
||||
expectedDangling: true,
|
||||
},
|
||||
// Add new cases as seen
|
||||
@@ -266,7 +316,7 @@ func TestHealing(t *testing.T) {
|
||||
}
|
||||
|
||||
disk := er.getDisks()[0]
|
||||
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPreHeal, err := disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -289,7 +339,7 @@ func TestHealing(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal, err := disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -308,7 +358,7 @@ func TestHealing(t *testing.T) {
|
||||
// gone down when an object was replaced by a new object.
|
||||
fileInfoOutDated := fileInfoPreHeal
|
||||
fileInfoOutDated.ModTime = time.Now()
|
||||
err = disk.WriteMetadata(context.Background(), bucket, object, fileInfoOutDated)
|
||||
err = disk.WriteMetadata(context.Background(), "", bucket, object, fileInfoOutDated)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -318,7 +368,7 @@ func TestHealing(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -426,11 +476,11 @@ func TestHealingVersioned(t *testing.T) {
|
||||
}
|
||||
|
||||
disk := er.getDisks()[0]
|
||||
fileInfoPreHeal1, err := disk.ReadVersion(context.Background(), bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPreHeal1, err := disk.ReadVersion(context.Background(), "", bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fileInfoPreHeal2, err := disk.ReadVersion(context.Background(), bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPreHeal2, err := disk.ReadVersion(context.Background(), "", bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -453,11 +503,11 @@ func TestHealingVersioned(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfoPostHeal1, err := disk.ReadVersion(context.Background(), bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal1, err := disk.ReadVersion(context.Background(), "", bucket, object, oi1.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fileInfoPostHeal2, err := disk.ReadVersion(context.Background(), bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal2, err := disk.ReadVersion(context.Background(), "", bucket, object, oi2.VersionID, ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -479,7 +529,7 @@ func TestHealingVersioned(t *testing.T) {
|
||||
// gone down when an object was replaced by a new object.
|
||||
fileInfoOutDated := fileInfoPreHeal1
|
||||
fileInfoOutDated.ModTime = time.Now()
|
||||
err = disk.WriteMetadata(context.Background(), bucket, object, fileInfoOutDated)
|
||||
err = disk.WriteMetadata(context.Background(), "", bucket, object, fileInfoOutDated)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -489,7 +539,7 @@ func TestHealingVersioned(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfoPostHeal1, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal1, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -499,7 +549,7 @@ func TestHealingVersioned(t *testing.T) {
|
||||
t.Fatal("HealObject failed")
|
||||
}
|
||||
|
||||
fileInfoPostHeal2, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal2, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -637,7 +687,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
// Restore...
|
||||
setDisks(orgDisks[:4]...)
|
||||
|
||||
fileInfoPreHeal, err := disks[0].ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPreHeal, err := disks[0].ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -654,7 +704,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfoPostHeal, err := disks[0].ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal, err := disks[0].ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -684,7 +734,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
|
||||
setDisks(orgDisks[:4]...)
|
||||
disk := getDisk(0)
|
||||
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -702,7 +752,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
}
|
||||
|
||||
disk = getDisk(0)
|
||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -733,7 +783,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
setDisks(orgDisks[:4]...)
|
||||
|
||||
disk = getDisk(0)
|
||||
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPreHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -751,7 +801,7 @@ func TestHealingDanglingObject(t *testing.T) {
|
||||
}
|
||||
|
||||
disk = getDisk(0)
|
||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
fileInfoPostHeal, err = disk.ReadVersion(context.Background(), "", bucket, object, "", ReadOptions{ReadData: false, Healing: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -836,7 +886,7 @@ func TestHealCorrectQuorum(t *testing.T) {
|
||||
er := set.sets[0]
|
||||
erasureDisks := er.getDisks()
|
||||
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
nfi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if errors.Is(err, errFileNotFound) {
|
||||
continue
|
||||
@@ -858,12 +908,12 @@ func TestHealCorrectQuorum(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
if countErrs(errs, nil) != len(fileInfos) {
|
||||
t.Fatal("Expected all xl.meta healed, but partial heal detected")
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, minioMetaBucket, cfgFile, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", minioMetaBucket, cfgFile, "", false, true)
|
||||
nfi, err = getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if errors.Is(err, errFileNotFound) {
|
||||
continue
|
||||
@@ -885,7 +935,7 @@ func TestHealCorrectQuorum(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, minioMetaBucket, cfgFile, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", minioMetaBucket, cfgFile, "", false, true)
|
||||
if countErrs(errs, nil) != len(fileInfos) {
|
||||
t.Fatal("Expected all xl.meta healed, but partial heal detected")
|
||||
}
|
||||
@@ -970,7 +1020,7 @@ func TestHealObjectCorruptedPools(t *testing.T) {
|
||||
t.Fatalf("Failed to heal object - %v", err)
|
||||
}
|
||||
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
@@ -998,7 +1048,7 @@ func TestHealObjectCorruptedPools(t *testing.T) {
|
||||
t.Errorf("Expected nil but received %v", err)
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
nfi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
@@ -1029,7 +1079,7 @@ func TestHealObjectCorruptedPools(t *testing.T) {
|
||||
t.Errorf("Expected nil but received %v", err)
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
nfi, err = getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
@@ -1133,7 +1183,7 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
|
||||
firstDisk := erasureDisks[0]
|
||||
|
||||
// Test 1: Remove the object backend files from the first disk.
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
@@ -1156,7 +1206,7 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
|
||||
t.Errorf("Expected xl.meta file to be present but stat failed - %v", err)
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
nfi1, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
@@ -1179,7 +1229,7 @@ func TestHealObjectCorruptedXLMeta(t *testing.T) {
|
||||
t.Errorf("Expected nil but received %v", err)
|
||||
}
|
||||
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs = readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
nfi2, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
@@ -1277,7 +1327,7 @@ func TestHealObjectCorruptedParts(t *testing.T) {
|
||||
firstDisk := erasureDisks[0]
|
||||
secondDisk := erasureDisks[1]
|
||||
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, bucket, object, "", false, true)
|
||||
fileInfos, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
|
||||
fi, err := getLatestFileInfo(ctx, fileInfos, er.defaultParityCount, errs)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to getLatestFileInfo - %v", err)
|
||||
|
||||
@@ -20,9 +20,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
@@ -148,29 +146,9 @@ func hashOrder(key string, cardinality int) []int {
|
||||
return nums
|
||||
}
|
||||
|
||||
var readFileInfoIgnoredErrs = append(objectOpIgnoredErrs,
|
||||
errFileNotFound,
|
||||
errVolumeNotFound,
|
||||
errFileVersionNotFound,
|
||||
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
|
||||
io.EOF, // some times we would read without locks, ignore these errors
|
||||
)
|
||||
|
||||
func readFileInfo(ctx context.Context, disk StorageAPI, bucket, object, versionID string, opts ReadOptions) (FileInfo, error) {
|
||||
fi, err := disk.ReadVersion(ctx, bucket, object, versionID, opts)
|
||||
|
||||
if err != nil && !IsErr(err, readFileInfoIgnoredErrs...) {
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
|
||||
disk.String(), bucket, object, err),
|
||||
disk.String())
|
||||
}
|
||||
|
||||
return fi, err
|
||||
}
|
||||
|
||||
// Reads all `xl.meta` metadata as a FileInfo slice.
|
||||
// Returns error slice indicating the failed metadata reads.
|
||||
func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, versionID string, readData, healing bool) ([]FileInfo, []error) {
|
||||
func readAllFileInfo(ctx context.Context, disks []StorageAPI, origbucket string, bucket, object, versionID string, readData, healing bool) ([]FileInfo, []error) {
|
||||
metadataArray := make([]FileInfo, len(disks))
|
||||
|
||||
opts := ReadOptions{
|
||||
@@ -186,7 +164,7 @@ func readAllFileInfo(ctx context.Context, disks []StorageAPI, bucket, object, ve
|
||||
if disks[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
metadataArray[index], err = readFileInfo(ctx, disks[index], bucket, object, versionID, opts)
|
||||
metadataArray[index], err = disks[index].ReadVersion(ctx, origbucket, bucket, object, versionID, opts)
|
||||
return err
|
||||
}, index)
|
||||
}
|
||||
@@ -330,15 +308,12 @@ var (
|
||||
// returns error if totalSize is -1, partSize is 0, partIndex is 0.
|
||||
func calculatePartSizeFromIdx(ctx context.Context, totalSize int64, partSize int64, partIndex int) (currPartSize int64, err error) {
|
||||
if totalSize < -1 {
|
||||
logger.LogIf(ctx, errInvalidArgument)
|
||||
return 0, errInvalidArgument
|
||||
}
|
||||
if partSize == 0 {
|
||||
logger.LogIf(ctx, errPartSizeZero)
|
||||
return 0, errPartSizeZero
|
||||
}
|
||||
if partIndex < 1 {
|
||||
logger.LogIf(ctx, errPartSizeIndex)
|
||||
return 0, errPartSizeIndex
|
||||
}
|
||||
if totalSize == -1 {
|
||||
|
||||
@@ -381,7 +381,7 @@ func pickValidFileInfo(ctx context.Context, metaArr []FileInfo, modTime time.Tim
|
||||
}
|
||||
|
||||
// writeUniqueFileInfo - writes unique `xl.meta` content for each disk concurrently.
|
||||
func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix string, files []FileInfo, quorum int) ([]StorageAPI, error) {
|
||||
func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, origbucket, bucket, prefix string, files []FileInfo, quorum int) ([]StorageAPI, error) {
|
||||
g := errgroup.WithNErrs(len(disks))
|
||||
|
||||
// Start writing `xl.meta` to all disks in parallel.
|
||||
@@ -395,7 +395,7 @@ func writeUniqueFileInfo(ctx context.Context, disks []StorageAPI, bucket, prefix
|
||||
fi := files[index]
|
||||
fi.Erasure.Index = index + 1
|
||||
if fi.IsValid() {
|
||||
return disks[index].WriteMetadata(ctx, bucket, prefix, fi)
|
||||
return disks[index].WriteMetadata(ctx, origbucket, bucket, prefix, fi)
|
||||
}
|
||||
return errCorruptedFormat
|
||||
}, index)
|
||||
|
||||
+37
-23
@@ -61,7 +61,7 @@ func (er erasureObjects) getMultipartSHADir(bucket, object string) string {
|
||||
// checkUploadIDExists - verify if a given uploadID exists and is valid.
|
||||
func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object, uploadID string, write bool) (fi FileInfo, metArr []FileInfo, err error) {
|
||||
defer func() {
|
||||
if errors.Is(err, errFileNotFound) || errors.Is(err, errVolumeNotFound) {
|
||||
if errors.Is(err, errFileNotFound) {
|
||||
err = errUploadIDNotFound
|
||||
}
|
||||
}()
|
||||
@@ -71,7 +71,7 @@ func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object
|
||||
storageDisks := er.getDisks()
|
||||
|
||||
// Read metadata associated with the object from all disks.
|
||||
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, minioMetaMultipartBucket,
|
||||
partsMetadata, errs := readAllFileInfo(ctx, storageDisks, bucket, minioMetaMultipartBucket,
|
||||
uploadIDPath, "", false, false)
|
||||
|
||||
readQuorum, writeQuorum, err := objectQuorumFromMeta(ctx, partsMetadata, errs, er.defaultParityCount)
|
||||
@@ -87,15 +87,14 @@ func (er erasureObjects) checkUploadIDExists(ctx context.Context, bucket, object
|
||||
// List all online disks.
|
||||
_, modTime, etag := listOnlineDisks(storageDisks, partsMetadata, errs, quorum)
|
||||
|
||||
var reducedErr error
|
||||
if write {
|
||||
reducedErr := reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
|
||||
if errors.Is(reducedErr, errErasureWriteQuorum) {
|
||||
return fi, nil, reducedErr
|
||||
}
|
||||
reducedErr = reduceWriteQuorumErrs(ctx, errs, objectOpIgnoredErrs, writeQuorum)
|
||||
} else {
|
||||
if reducedErr := reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum); reducedErr != nil {
|
||||
return fi, nil, reducedErr
|
||||
}
|
||||
reducedErr = reduceReadQuorumErrs(ctx, errs, objectOpIgnoredErrs, readQuorum)
|
||||
}
|
||||
if reducedErr != nil {
|
||||
return fi, nil, reducedErr
|
||||
}
|
||||
|
||||
// Pick one from the first valid metadata.
|
||||
@@ -200,7 +199,7 @@ func (er erasureObjects) cleanupStaleUploadsOnDisk(ctx context.Context, disk Sto
|
||||
readDirFn(pathJoin(diskPath, minioMetaMultipartBucket), func(shaDir string, typ os.FileMode) error {
|
||||
readDirFn(pathJoin(diskPath, minioMetaMultipartBucket, shaDir), func(uploadIDDir string, typ os.FileMode) error {
|
||||
uploadIDPath := pathJoin(shaDir, uploadIDDir)
|
||||
fi, err := disk.ReadVersion(ctx, minioMetaMultipartBucket, uploadIDPath, "", ReadOptions{})
|
||||
fi, err := disk.ReadVersion(ctx, "", minioMetaMultipartBucket, uploadIDPath, "", ReadOptions{})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -281,15 +280,14 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
|
||||
if !disk.IsOnline() {
|
||||
continue
|
||||
}
|
||||
uploadIDs, err = disk.ListDir(ctx, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
|
||||
uploadIDs, err = disk.ListDir(ctx, bucket, minioMetaMultipartBucket, er.getMultipartSHADir(bucket, object), -1)
|
||||
if err != nil {
|
||||
if errors.Is(err, errDiskNotFound) {
|
||||
continue
|
||||
}
|
||||
if errors.Is(err, errFileNotFound) || errors.Is(err, errVolumeNotFound) {
|
||||
if errors.Is(err, errFileNotFound) {
|
||||
return result, nil
|
||||
}
|
||||
logger.LogIf(ctx, err)
|
||||
return result, toObjectErr(err, bucket, object)
|
||||
}
|
||||
break
|
||||
@@ -486,8 +484,8 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
|
||||
uploadIDPath := er.getUploadIDDir(bucket, object, uploadUUID)
|
||||
|
||||
// Write updated `xl.meta` to all disks.
|
||||
if _, err := writeUniqueFileInfo(ctx, onlineDisks, minioMetaMultipartBucket, uploadIDPath, partsMetadata, writeQuorum); err != nil {
|
||||
return nil, toObjectErr(err, minioMetaMultipartBucket, uploadIDPath)
|
||||
if _, err := writeUniqueFileInfo(ctx, onlineDisks, bucket, minioMetaMultipartBucket, uploadIDPath, partsMetadata, writeQuorum); err != nil {
|
||||
return nil, toObjectErr(err, bucket, object)
|
||||
}
|
||||
return &NewMultipartUploadResult{
|
||||
UploadID: uploadID,
|
||||
@@ -582,6 +580,13 @@ func writeAllDisks(ctx context.Context, disks []StorageAPI, dstBucket, dstEntry
|
||||
func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uploadID string, partID int, r *PutObjReader, opts ObjectOptions) (pi PartInfo, err error) {
|
||||
auditObjectErasureSet(ctx, object, &er)
|
||||
|
||||
data := r.Reader
|
||||
// Validate input data size and it can never be less than zero.
|
||||
if data.Size() < -1 {
|
||||
logger.LogIf(ctx, errInvalidArgument, logger.ErrorKind)
|
||||
return pi, toObjectErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
// Read lock for upload id.
|
||||
// Only held while reading the upload metadata.
|
||||
uploadIDRLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
|
||||
@@ -596,16 +601,12 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
||||
// Validates if upload ID exists.
|
||||
fi, _, err := er.checkUploadIDExists(rctx, bucket, object, uploadID, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, errVolumeNotFound) {
|
||||
return pi, toObjectErr(err, bucket)
|
||||
}
|
||||
return pi, toObjectErr(err, bucket, object, uploadID)
|
||||
}
|
||||
|
||||
data := r.Reader
|
||||
// Validate input data size and it can never be less than zero.
|
||||
if data.Size() < -1 {
|
||||
logger.LogIf(rctx, errInvalidArgument, logger.Application)
|
||||
return pi, toObjectErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
// Write lock for this part ID, only hold it if we are planning to read from the
|
||||
// streamto avoid any concurrent updates.
|
||||
//
|
||||
@@ -681,7 +682,7 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
|
||||
if disk == nil {
|
||||
continue
|
||||
}
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, tmpPartPath, erasure.ShardFileSize(data.Size()), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, bucket, minioMetaTmpBucket, tmpPartPath, erasure.ShardFileSize(data.Size()), DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
toEncode := io.Reader(data)
|
||||
@@ -794,6 +795,9 @@ func (er erasureObjects) GetMultipartInfo(ctx context.Context, bucket, object, u
|
||||
|
||||
fi, _, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, errVolumeNotFound) {
|
||||
return result, toObjectErr(err, bucket)
|
||||
}
|
||||
return result, toObjectErr(err, bucket, object, uploadID)
|
||||
}
|
||||
|
||||
@@ -979,6 +983,9 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
|
||||
fi, partsMetadata, err := er.checkUploadIDExists(ctx, bucket, object, uploadID, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, errVolumeNotFound) {
|
||||
return oi, toObjectErr(err, bucket)
|
||||
}
|
||||
return oi, toObjectErr(err, bucket, object, uploadID)
|
||||
}
|
||||
|
||||
@@ -1237,6 +1244,10 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
|
||||
// Save the consolidated actual size.
|
||||
fi.Metadata[ReservedMetadataPrefix+"actual-size"] = strconv.FormatInt(objectActualSize, 10)
|
||||
|
||||
if opts.DataMovement {
|
||||
fi.SetDataMov()
|
||||
}
|
||||
|
||||
// Update all erasure metadata, make sure to not modify fields like
|
||||
// checksum which are different on each disks.
|
||||
for index := range partsMetadata {
|
||||
@@ -1337,6 +1348,9 @@ func (er erasureObjects) AbortMultipartUpload(ctx context.Context, bucket, objec
|
||||
|
||||
// Validates if upload ID exists.
|
||||
if _, _, err = er.checkUploadIDExists(ctx, bucket, object, uploadID, false); err != nil {
|
||||
if errors.Is(err, errVolumeNotFound) {
|
||||
return toObjectErr(err, bucket)
|
||||
}
|
||||
return toObjectErr(err, bucket, object, uploadID)
|
||||
}
|
||||
|
||||
|
||||
+22
-43
@@ -46,7 +46,6 @@ import (
|
||||
"github.com/minio/pkg/v2/mimedb"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
"github.com/minio/pkg/v2/wildcard"
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// list all errors which can be ignored in object operations.
|
||||
@@ -91,7 +90,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
|
||||
|
||||
// Read metadata associated with the object from all disks.
|
||||
if srcOpts.VersionID != "" {
|
||||
metaArr, errs = readAllFileInfo(ctx, storageDisks, srcBucket, srcObject, srcOpts.VersionID, true, false)
|
||||
metaArr, errs = readAllFileInfo(ctx, storageDisks, "", srcBucket, srcObject, srcOpts.VersionID, true, false)
|
||||
} else {
|
||||
metaArr, errs = readAllXL(ctx, storageDisks, srcBucket, srcObject, true, false, true)
|
||||
}
|
||||
@@ -179,7 +178,7 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
|
||||
}
|
||||
|
||||
// Write unique `xl.meta` for each disk.
|
||||
if _, err = writeUniqueFileInfo(ctx, onlineDisks, srcBucket, srcObject, metaArr, writeQuorum); err != nil {
|
||||
if _, err = writeUniqueFileInfo(ctx, onlineDisks, "", srcBucket, srcObject, metaArr, writeQuorum); err != nil {
|
||||
return oi, toObjectErr(err, srcBucket, srcObject)
|
||||
}
|
||||
|
||||
@@ -546,29 +545,6 @@ func (er erasureObjects) deleteIfDangling(ctx context.Context, bucket, object st
|
||||
return m, err
|
||||
}
|
||||
|
||||
var readRawFileInfoErrs = append(objectOpIgnoredErrs,
|
||||
errFileNotFound,
|
||||
errFileNameTooLong,
|
||||
errVolumeNotFound,
|
||||
errFileVersionNotFound,
|
||||
io.ErrUnexpectedEOF, // some times we would read without locks, ignore these errors
|
||||
io.EOF, // some times we would read without locks, ignore these errors
|
||||
msgp.ErrShortBytes,
|
||||
context.DeadlineExceeded,
|
||||
context.Canceled,
|
||||
)
|
||||
|
||||
func readRawFileInfo(ctx context.Context, disk StorageAPI, bucket, object string, readData bool) (RawFileInfo, error) {
|
||||
rf, err := disk.ReadXL(ctx, bucket, object, readData)
|
||||
|
||||
if err != nil && !IsErr(err, readRawFileInfoErrs...) {
|
||||
logger.LogOnceIf(ctx, fmt.Errorf("Drive %s, path (%s/%s) returned an error (%w)",
|
||||
disk.String(), bucket, object, err),
|
||||
disk.String())
|
||||
}
|
||||
return rf, err
|
||||
}
|
||||
|
||||
func fileInfoFromRaw(ri RawFileInfo, bucket, object string, readData, inclFreeVers, allParts bool) (FileInfo, error) {
|
||||
var xl xlMetaV2
|
||||
if err := xl.LoadOrConvert(ri.Buf); err != nil {
|
||||
@@ -611,7 +587,7 @@ func readAllRawFileInfo(ctx context.Context, disks []StorageAPI, bucket, object
|
||||
if disks[index] == nil {
|
||||
return errDiskNotFound
|
||||
}
|
||||
rf, err := readRawFileInfo(ctx, disks[index], bucket, object, readData)
|
||||
rf, err := disks[index].ReadXL(ctx, bucket, object, readData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -791,10 +767,10 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
|
||||
if opts.VersionID != "" {
|
||||
// Read a specific version ID
|
||||
fi, err = readFileInfo(ctx, disk, bucket, object, opts.VersionID, ropts)
|
||||
fi, err = disk.ReadVersion(ctx, "", bucket, object, opts.VersionID, ropts)
|
||||
} else {
|
||||
// Read the latest version
|
||||
rfi, err = readRawFileInfo(ctx, disk, bucket, object, readData)
|
||||
rfi, err = disk.ReadXL(ctx, bucket, object, readData)
|
||||
if err == nil {
|
||||
fi, err = fileInfoFromRaw(rfi, bucket, object, readData, opts.InclFreeVersions, true)
|
||||
}
|
||||
@@ -829,7 +805,11 @@ func (er erasureObjects) getObjectFileInfo(ctx context.Context, bucket, object s
|
||||
|
||||
var missingBlocks int
|
||||
for i := range errs {
|
||||
if errors.Is(errs[i], errFileNotFound) {
|
||||
if IsErr(errs[i],
|
||||
errFileNotFound,
|
||||
errFileVersionNotFound,
|
||||
errFileCorrupt,
|
||||
) {
|
||||
missingBlocks++
|
||||
}
|
||||
}
|
||||
@@ -1114,7 +1094,7 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
|
||||
|
||||
// Validate input data size and it can never be less than zero.
|
||||
if data.Size() < -1 {
|
||||
logger.LogIf(ctx, errInvalidArgument, logger.Application)
|
||||
logger.LogIf(ctx, errInvalidArgument, logger.ErrorKind)
|
||||
return ObjectInfo{}, toObjectErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
@@ -1219,7 +1199,7 @@ func (er erasureObjects) putMetacacheObject(ctx context.Context, key string, r *
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = writeUniqueFileInfo(ctx, onlineDisks, minioMetaBucket, key, partsMetadata, writeQuorum); err != nil {
|
||||
if _, err = writeUniqueFileInfo(ctx, onlineDisks, "", minioMetaBucket, key, partsMetadata, writeQuorum); err != nil {
|
||||
return ObjectInfo{}, toObjectErr(err, minioMetaBucket, key)
|
||||
}
|
||||
|
||||
@@ -1301,7 +1281,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
|
||||
// Validate input data size and it can never be less than -1.
|
||||
if data.Size() < -1 {
|
||||
logger.LogIf(ctx, errInvalidArgument, logger.Application)
|
||||
logger.LogIf(ctx, errInvalidArgument, logger.ErrorKind)
|
||||
return ObjectInfo{}, toObjectErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
@@ -1449,7 +1429,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
continue
|
||||
}
|
||||
|
||||
writers[i] = newBitrotWriter(disk, minioMetaTmpBucket, tempErasureObj, shardFileSize, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
writers[i] = newBitrotWriter(disk, bucket, minioMetaTmpBucket, tempErasureObj, shardFileSize, DefaultBitrotAlgorithm, erasure.ShardSize())
|
||||
}
|
||||
|
||||
toEncode := io.Reader(data)
|
||||
@@ -1469,7 +1449,7 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
n, erasureErr := erasure.Encode(ctx, toEncode, writers, buffer, writeQuorum)
|
||||
closeBitrotWriters(writers)
|
||||
if erasureErr != nil {
|
||||
return ObjectInfo{}, toObjectErr(erasureErr, minioMetaTmpBucket, tempErasureObj)
|
||||
return ObjectInfo{}, toObjectErr(erasureErr, bucket, object)
|
||||
}
|
||||
|
||||
// Should return IncompleteBody{} error when reader has fewer bytes
|
||||
@@ -1536,13 +1516,12 @@ func (er erasureObjects) putObject(ctx context.Context, bucket string, object st
|
||||
partsMetadata[index].Metadata = userDefined
|
||||
partsMetadata[index].Size = n
|
||||
partsMetadata[index].ModTime = modTime
|
||||
}
|
||||
|
||||
if len(inlineBuffers) > 0 {
|
||||
// Set an additional header when data is inlined.
|
||||
for index := range partsMetadata {
|
||||
if len(inlineBuffers) > 0 {
|
||||
partsMetadata[index].SetInlineData()
|
||||
}
|
||||
if opts.DataMovement {
|
||||
partsMetadata[index].SetDataMov()
|
||||
}
|
||||
}
|
||||
|
||||
// Rename the successfully written temporary object to final location.
|
||||
@@ -2057,7 +2036,7 @@ func (er erasureObjects) PutObjectMetadata(ctx context.Context, bucket, object s
|
||||
|
||||
// Read metadata associated with the object from all disks.
|
||||
if opts.VersionID != "" {
|
||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false, false)
|
||||
metaArr, errs = readAllFileInfo(ctx, disks, "", bucket, object, opts.VersionID, false, false)
|
||||
} else {
|
||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
|
||||
}
|
||||
@@ -2130,7 +2109,7 @@ func (er erasureObjects) PutObjectTags(ctx context.Context, bucket, object strin
|
||||
|
||||
// Read metadata associated with the object from all disks.
|
||||
if opts.VersionID != "" {
|
||||
metaArr, errs = readAllFileInfo(ctx, disks, bucket, object, opts.VersionID, false, false)
|
||||
metaArr, errs = readAllFileInfo(ctx, disks, "", bucket, object, opts.VersionID, false, false)
|
||||
} else {
|
||||
metaArr, errs = readAllXL(ctx, disks, bucket, object, false, false, true)
|
||||
}
|
||||
@@ -2458,7 +2437,7 @@ func (er erasureObjects) DecomTieredObject(ctx context.Context, bucket, object s
|
||||
var onlineDisks []StorageAPI
|
||||
onlineDisks, partsMetadata = shuffleDisksAndPartsMetadata(storageDisks, partsMetadata, fi)
|
||||
|
||||
if _, err := writeUniqueFileInfo(ctx, onlineDisks, bucket, object, partsMetadata, writeQuorum); err != nil {
|
||||
if _, err := writeUniqueFileInfo(ctx, onlineDisks, "", bucket, object, partsMetadata, writeQuorum); err != nil {
|
||||
return toObjectErr(err, bucket, object)
|
||||
}
|
||||
|
||||
|
||||
@@ -521,7 +521,7 @@ func TestGetObjectNoQuorum(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, disk := range xl.getDisks() {
|
||||
files, _ := disk.ListDir(ctx, bucket, object, -1)
|
||||
files, _ := disk.ListDir(ctx, "", bucket, object, -1)
|
||||
for _, file := range files {
|
||||
if file != "xl.meta" {
|
||||
disk.Delete(ctx, bucket, pathJoin(object, file), DeleteOptions{
|
||||
@@ -625,7 +625,7 @@ func TestHeadObjectNoQuorum(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, disk := range xl.getDisks() {
|
||||
files, _ := disk.ListDir(ctx, bucket, object, -1)
|
||||
files, _ := disk.ListDir(ctx, "", bucket, object, -1)
|
||||
for _, file := range files {
|
||||
if file != "xl.meta" {
|
||||
disk.Delete(ctx, bucket, pathJoin(object, file), DeleteOptions{
|
||||
@@ -908,7 +908,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts1, errs1 := readAllFileInfo(ctx, erasureDisks, bucket, object1, "", false, false)
|
||||
parts1, errs1 := readAllFileInfo(ctx, erasureDisks, "", bucket, object1, "", false, false)
|
||||
parts1SC := globalStorageClass
|
||||
|
||||
// Object for test case 2 - No StorageClass defined, MetaData in PutObject requesting RRS Class
|
||||
@@ -920,7 +920,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts2, errs2 := readAllFileInfo(ctx, erasureDisks, bucket, object2, "", false, false)
|
||||
parts2, errs2 := readAllFileInfo(ctx, erasureDisks, "", bucket, object2, "", false, false)
|
||||
parts2SC := globalStorageClass
|
||||
|
||||
// Object for test case 3 - No StorageClass defined, MetaData in PutObject requesting Standard Storage Class
|
||||
@@ -932,7 +932,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts3, errs3 := readAllFileInfo(ctx, erasureDisks, bucket, object3, "", false, false)
|
||||
parts3, errs3 := readAllFileInfo(ctx, erasureDisks, "", bucket, object3, "", false, false)
|
||||
parts3SC := globalStorageClass
|
||||
|
||||
// Object for test case 4 - Standard StorageClass defined as Parity 6, MetaData in PutObject requesting Standard Storage Class
|
||||
@@ -950,7 +950,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts4, errs4 := readAllFileInfo(ctx, erasureDisks, bucket, object4, "", false, false)
|
||||
parts4, errs4 := readAllFileInfo(ctx, erasureDisks, "", bucket, object4, "", false, false)
|
||||
parts4SC := storageclass.Config{
|
||||
Standard: storageclass.StorageClass{
|
||||
Parity: 6,
|
||||
@@ -973,7 +973,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts5, errs5 := readAllFileInfo(ctx, erasureDisks, bucket, object5, "", false, false)
|
||||
parts5, errs5 := readAllFileInfo(ctx, erasureDisks, "", bucket, object5, "", false, false)
|
||||
parts5SC := globalStorageClass
|
||||
|
||||
// Object for test case 6 - RRS StorageClass defined as Parity 2, MetaData in PutObject requesting Standard Storage Class
|
||||
@@ -994,7 +994,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts6, errs6 := readAllFileInfo(ctx, erasureDisks, bucket, object6, "", false, false)
|
||||
parts6, errs6 := readAllFileInfo(ctx, erasureDisks, "", bucket, object6, "", false, false)
|
||||
parts6SC := storageclass.Config{
|
||||
RRS: storageclass.StorageClass{
|
||||
Parity: 2,
|
||||
@@ -1017,7 +1017,7 @@ func testObjectQuorumFromMeta(obj ObjectLayer, instanceType string, dirs []strin
|
||||
t.Fatalf("Failed to putObject %v", err)
|
||||
}
|
||||
|
||||
parts7, errs7 := readAllFileInfo(ctx, erasureDisks, bucket, object7, "", false, false)
|
||||
parts7, errs7 := readAllFileInfo(ctx, erasureDisks, "", bucket, object7, "", false, false)
|
||||
parts7SC := storageclass.Config{
|
||||
Standard: storageclass.StorageClass{
|
||||
Parity: 5,
|
||||
|
||||
@@ -653,7 +653,8 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
|
||||
}
|
||||
}
|
||||
_, err = z.CompleteMultipartUpload(ctx, bucket, objInfo.Name, res.UploadID, parts, ObjectOptions{
|
||||
MTime: objInfo.ModTime,
|
||||
DataMovement: true,
|
||||
MTime: objInfo.ModTime,
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("decommissionObject: CompleteMultipartUpload() %w", err)
|
||||
@@ -665,11 +666,13 @@ func (z *erasureServerPools) decommissionObject(ctx context.Context, bucket stri
|
||||
if err != nil {
|
||||
return fmt.Errorf("decommissionObject: hash.NewReader() %w", err)
|
||||
}
|
||||
|
||||
_, err = z.PutObject(ctx,
|
||||
bucket,
|
||||
objInfo.Name,
|
||||
NewPutObjReader(hr),
|
||||
ObjectOptions{
|
||||
DataMovement: true,
|
||||
VersionID: objInfo.VersionID,
|
||||
MTime: objInfo.ModTime,
|
||||
UserDefined: objInfo.UserDefined,
|
||||
|
||||
@@ -809,7 +809,8 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
|
||||
}
|
||||
}
|
||||
_, err = z.CompleteMultipartUpload(ctx, bucket, oi.Name, res.UploadID, parts, ObjectOptions{
|
||||
MTime: oi.ModTime,
|
||||
DataMovement: true,
|
||||
MTime: oi.ModTime,
|
||||
})
|
||||
if err != nil {
|
||||
err = fmt.Errorf("rebalanceObject: CompleteMultipartUpload() %w", err)
|
||||
@@ -821,11 +822,13 @@ func (z *erasureServerPools) rebalanceObject(ctx context.Context, bucket string,
|
||||
if err != nil {
|
||||
return fmt.Errorf("rebalanceObject: hash.NewReader() %w", err)
|
||||
}
|
||||
|
||||
_, err = z.PutObject(ctx,
|
||||
bucket,
|
||||
oi.Name,
|
||||
NewPutObjReader(hr),
|
||||
ObjectOptions{
|
||||
DataMovement: true,
|
||||
VersionID: oi.VersionID,
|
||||
MTime: oi.ModTime,
|
||||
UserDefined: oi.UserDefined,
|
||||
|
||||
@@ -85,7 +85,7 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
)
|
||||
|
||||
// Maximum number of reusable buffers per node at any given point in time.
|
||||
n := 1024 // single node single/multiple drives set this to 1024 entries
|
||||
n := uint64(1024) // single node single/multiple drives set this to 1024 entries
|
||||
|
||||
if globalIsDistErasure {
|
||||
n = 2048
|
||||
@@ -95,6 +95,11 @@ func newErasureServerPools(ctx context.Context, endpointServerPools EndpointServ
|
||||
n = 256 // 256MiB for CI/CD environments is sufficient
|
||||
}
|
||||
|
||||
// Avoid allocating more than half of the available memory
|
||||
if maxN := availableMemory() / (blockSizeV2 * 2); n > maxN {
|
||||
n = maxN
|
||||
}
|
||||
|
||||
// Initialize byte pool once for all sets, bpool size is set to
|
||||
// setCount * setDriveCount with each memory upto blockSizeV2.
|
||||
globalBytePoolCap = bpool.NewBytePoolCap(n, blockSizeV2, blockSizeV2*2)
|
||||
@@ -1307,7 +1312,10 @@ func (z *erasureServerPools) ListObjectVersions(ctx context.Context, bucket, pre
|
||||
|
||||
merged, err := z.listPath(ctx, &opts)
|
||||
if err != nil && err != io.EOF {
|
||||
return loi, err
|
||||
if !isErrBucketNotFound(err) {
|
||||
logger.LogOnceIf(ctx, err, "erasure-list-objects-path-"+bucket)
|
||||
}
|
||||
return loi, toObjectErr(err, bucket)
|
||||
}
|
||||
defer merged.truncate(0) // Release when returning
|
||||
|
||||
@@ -1456,6 +1464,12 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
||||
}
|
||||
return loi, nil
|
||||
}
|
||||
if isErrBucketNotFound(err) {
|
||||
return loi, err
|
||||
}
|
||||
if contextCanceled(ctx) {
|
||||
return ListObjectsInfo{}, ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
if len(prefix) > 0 && maxKeys == 1 && marker == "" {
|
||||
@@ -1481,7 +1495,9 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
||||
loi.Objects = append(loi.Objects, objInfo)
|
||||
return loi, nil
|
||||
}
|
||||
|
||||
if isErrBucketNotFound(err) {
|
||||
return ListObjectsInfo{}, err
|
||||
}
|
||||
if contextCanceled(ctx) {
|
||||
return ListObjectsInfo{}, ctx.Err()
|
||||
}
|
||||
@@ -1492,7 +1508,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
||||
if !isErrBucketNotFound(err) {
|
||||
logger.LogOnceIf(ctx, err, "erasure-list-objects-path-"+bucket)
|
||||
}
|
||||
return loi, err
|
||||
return loi, toObjectErr(err, bucket)
|
||||
}
|
||||
|
||||
merged.forwardPast(opts.Marker)
|
||||
@@ -1537,7 +1553,7 @@ func (z *erasureServerPools) ListObjects(ctx context.Context, bucket, prefix, ma
|
||||
}
|
||||
|
||||
func (z *erasureServerPools) ListMultipartUploads(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, maxUploads int) (ListMultipartsInfo, error) {
|
||||
if err := checkListMultipartArgs(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter, z); err != nil {
|
||||
if err := checkListMultipartArgs(ctx, bucket, prefix, keyMarker, uploadIDMarker, delimiter); err != nil {
|
||||
return ListMultipartsInfo{}, err
|
||||
}
|
||||
|
||||
@@ -1956,7 +1972,7 @@ func (z *erasureServerPools) HealBucket(ctx context.Context, bucket string, opts
|
||||
// error walker returns error. Optionally if context.Done() is received
|
||||
// then Walk() stops the walker.
|
||||
func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, results chan<- ObjectInfo, opts WalkOptions) error {
|
||||
if err := checkListObjsArgs(ctx, bucket, prefix, "", z); err != nil {
|
||||
if err := checkListObjsArgs(ctx, bucket, prefix, ""); err != nil {
|
||||
// Upon error close the channel.
|
||||
xioutil.SafeClose(results)
|
||||
return err
|
||||
|
||||
+7
-8
@@ -733,7 +733,7 @@ func listDeletedBuckets(ctx context.Context, storageDisks []StorageAPI, delBucke
|
||||
// we ignore disk not found errors
|
||||
return nil
|
||||
}
|
||||
volsInfo, err := storageDisks[index].ListDir(ctx, minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix), -1)
|
||||
volsInfo, err := storageDisks[index].ListDir(ctx, "", minioMetaBucket, pathJoin(bucketMetaPrefix, deletedBucketsPrefix), -1)
|
||||
if err != nil {
|
||||
if errors.Is(err, errFileNotFound) {
|
||||
return nil
|
||||
@@ -1136,14 +1136,13 @@ func (s *erasureSets) HealFormat(ctx context.Context, dryRun bool) (res madmin.H
|
||||
|
||||
xldisk, ok := disk.(*xlStorageDiskIDCheck)
|
||||
if ok {
|
||||
if driveQuorum {
|
||||
commonWrites, commonDeletes := calcCommonWritesDeletes(currentDisksInfo[m], (s.setDriveCount+1)/2)
|
||||
xldisk.totalWrites.Store(commonWrites)
|
||||
xldisk.totalDeletes.Store(commonDeletes)
|
||||
xldisk.storage.setWriteAttribute(commonWrites)
|
||||
xldisk.storage.setDeleteAttribute(commonDeletes)
|
||||
_, commonDeletes := calcCommonWritesDeletes(currentDisksInfo[m], (s.setDriveCount+1)/2)
|
||||
xldisk.totalDeletes.Store(commonDeletes)
|
||||
xldisk.storage.setDeleteAttribute(commonDeletes)
|
||||
|
||||
if globalDriveMonitoring {
|
||||
go xldisk.monitorDiskWritable(xldisk.diskCtx)
|
||||
}
|
||||
go xldisk.monitorDiskWritable(xldisk.diskCtx)
|
||||
}
|
||||
} else {
|
||||
disk.Close() // Close the remote storage client, re-initialize with healthchecks.
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/klauspost/reedsolomon"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
)
|
||||
|
||||
// getDataBlockLen - get length of data blocks from encoded blocks.
|
||||
@@ -43,19 +42,16 @@ func getDataBlockLen(enBlocks [][]byte, dataBlocks int) int {
|
||||
func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, dataBlocks int, offset int64, length int64) (int64, error) {
|
||||
// Offset and out size cannot be negative.
|
||||
if offset < 0 || length < 0 {
|
||||
logger.LogIf(ctx, errUnexpected)
|
||||
return 0, errUnexpected
|
||||
}
|
||||
|
||||
// Do we have enough blocks?
|
||||
if len(enBlocks) < dataBlocks {
|
||||
logger.LogIf(ctx, fmt.Errorf("diskBlocks(%d)/dataBlocks(%d) - %w", len(enBlocks), dataBlocks, reedsolomon.ErrTooFewShards))
|
||||
return 0, reedsolomon.ErrTooFewShards
|
||||
}
|
||||
|
||||
// Do we have enough data?
|
||||
if int64(getDataBlockLen(enBlocks, dataBlocks)) < length {
|
||||
logger.LogIf(ctx, fmt.Errorf("getDataBlockLen(enBlocks, dataBlocks)(%d)/length(%d) - %w", getDataBlockLen(enBlocks, dataBlocks), length, reedsolomon.ErrShortData))
|
||||
return 0, reedsolomon.ErrShortData
|
||||
}
|
||||
|
||||
@@ -85,11 +81,6 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
|
||||
if write < int64(len(block)) {
|
||||
n, err := dst.Write(block[:write])
|
||||
if err != nil {
|
||||
// The writer will be closed in case of range queries, which will emit ErrClosedPipe.
|
||||
// The reader pipe might be closed at ListObjects io.EOF ignore it.
|
||||
if err != io.ErrClosedPipe && err != io.EOF {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
totalWritten += int64(n)
|
||||
@@ -99,11 +90,6 @@ func writeDataBlocks(ctx context.Context, dst io.Writer, enBlocks [][]byte, data
|
||||
// Copy the block.
|
||||
n, err := dst.Write(block)
|
||||
if err != nil {
|
||||
// The writer will be closed in case of range queries, which will emit ErrClosedPipe.
|
||||
// The reader pipe might be closed at ListObjects io.EOF ignore it.
|
||||
if err != io.ErrClosedPipe && err != io.EOF {
|
||||
logger.LogIf(ctx, err)
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
|
||||
@@ -497,7 +497,6 @@ func (er erasureObjects) nsScanner(ctx context.Context, buckets []BucketInfo, wa
|
||||
cache.Info.SkipHealing = healing
|
||||
cache.Info.NextCycle = wantCycle
|
||||
if cache.Info.Name != bucket.Name {
|
||||
logger.LogIf(ctx, fmt.Errorf("cache name mismatch: %s != %s", cache.Info.Name, bucket.Name))
|
||||
cache.Info = dataUsageCacheInfo{
|
||||
Name: bucket.Name,
|
||||
LastUpdate: time.Time{},
|
||||
|
||||
+15
-3
@@ -34,6 +34,7 @@ import (
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/config"
|
||||
"github.com/minio/minio/internal/config/browser"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
"github.com/minio/minio/internal/handlers"
|
||||
"github.com/minio/minio/internal/kms"
|
||||
"go.uber.org/atomic"
|
||||
@@ -56,6 +57,7 @@ import (
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/pubsub"
|
||||
"github.com/minio/pkg/v2/certs"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
xnet "github.com/minio/pkg/v2/net"
|
||||
)
|
||||
|
||||
@@ -129,6 +131,11 @@ const (
|
||||
tlsClientSessionCacheSize = 100
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Injected to prevent circular dependency.
|
||||
pubsub.GetByteBuffer = grid.GetByteBuffer
|
||||
}
|
||||
|
||||
type poolDisksLayout struct {
|
||||
cmdline string
|
||||
layout [][]string
|
||||
@@ -157,9 +164,10 @@ type serverCtxt struct {
|
||||
ConnReadDeadline time.Duration
|
||||
ConnWriteDeadline time.Duration
|
||||
|
||||
ShutdownTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
ReadHeaderTimeout time.Duration
|
||||
ShutdownTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
ReadHeaderTimeout time.Duration
|
||||
MaxIdleConnsPerHost int
|
||||
|
||||
// The layout of disks as interpreted
|
||||
Layout disksLayout
|
||||
@@ -388,6 +396,8 @@ var (
|
||||
|
||||
globalRemoteTargetTransport http.RoundTripper
|
||||
|
||||
globalHealthChkTransport http.RoundTripper
|
||||
|
||||
globalDNSCache = &dnscache.Resolver{
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
@@ -412,6 +422,8 @@ var (
|
||||
globalLocalDrives []StorageAPI
|
||||
globalLocalDrivesMu sync.RWMutex
|
||||
|
||||
globalDriveMonitoring = env.Get("_MINIO_DRIVE_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn
|
||||
|
||||
// Is MINIO_CI_CD set?
|
||||
globalIsCICD bool
|
||||
|
||||
|
||||
+26
-15
@@ -56,16 +56,31 @@ type apiConfig struct {
|
||||
syncEvents bool
|
||||
}
|
||||
|
||||
const cgroupLimitFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
|
||||
const (
|
||||
cgroupV1MemLimitFile = "/sys/fs/cgroup/memory/memory.limit_in_bytes"
|
||||
cgroupV2MemLimitFile = "/sys/fs/cgroup/memory.max"
|
||||
cgroupMemNoLimit = 9223372036854771712
|
||||
)
|
||||
|
||||
func cgroupLimit(limitFile string) (limit uint64) {
|
||||
buf, err := os.ReadFile(limitFile)
|
||||
func cgroupMemLimit() (limit uint64) {
|
||||
buf, err := os.ReadFile(cgroupV2MemLimitFile)
|
||||
if err != nil {
|
||||
return 9223372036854771712
|
||||
buf, err = os.ReadFile(cgroupV1MemLimitFile)
|
||||
}
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
limit, err = strconv.ParseUint(string(buf), 10, 64)
|
||||
if err != nil {
|
||||
return 9223372036854771712
|
||||
// The kernel can return valid but non integer values
|
||||
// but still, no need to interpret more
|
||||
return 0
|
||||
}
|
||||
if limit == cgroupMemNoLimit {
|
||||
// No limit set, It's the highest positive signed 64-bit
|
||||
// integer (2^63-1), rounded down to multiples of 4096 (2^12),
|
||||
// the most common page size on x86 systems - for cgroup_limits.
|
||||
return 0
|
||||
}
|
||||
return limit
|
||||
}
|
||||
@@ -74,23 +89,19 @@ func availableMemory() (available uint64) {
|
||||
available = 8 << 30 // Default to 8 GiB when we can't find the limits.
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
available = cgroupLimit(cgroupLimitFile)
|
||||
|
||||
// No limit set, It's the highest positive signed 64-bit
|
||||
// integer (2^63-1), rounded down to multiples of 4096 (2^12),
|
||||
// the most common page size on x86 systems - for cgroup_limits.
|
||||
if available != 9223372036854771712 {
|
||||
// This means cgroup memory limit is configured.
|
||||
// Useful in container mode
|
||||
limit := cgroupMemLimit()
|
||||
if limit > 0 {
|
||||
// A valid value is found
|
||||
available = limit
|
||||
return
|
||||
} // no-limit set proceed to set the limits based on virtual memory.
|
||||
|
||||
}
|
||||
} // for all other platforms limits are based on virtual memory.
|
||||
|
||||
memStats, err := mem.VirtualMemory()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
available = memStats.Available / 2
|
||||
return
|
||||
}
|
||||
|
||||
+75
-31
@@ -974,7 +974,7 @@ func (sys *IAMSys) NewServiceAccount(ctx context.Context, parentUser string, gro
|
||||
m[iamPolicyClaimNameSA()] = inheritedPolicyType
|
||||
}
|
||||
|
||||
// Add all the necessary claims for the service accounts.
|
||||
// Add all the necessary claims for the service account.
|
||||
for k, v := range opts.claims {
|
||||
_, ok := m[k]
|
||||
if !ok {
|
||||
@@ -1848,37 +1848,14 @@ func (sys *IAMSys) IsAllowedServiceAccount(args policy.Args, parentUser string)
|
||||
return isOwnerDerived || combinedPolicy.IsAllowed(parentArgs)
|
||||
}
|
||||
|
||||
// Now check if we have a sessionPolicy.
|
||||
spolicy, ok := args.Claims[sessionPolicyNameExtracted]
|
||||
if !ok {
|
||||
return false
|
||||
// 3. If an inline session-policy is present, evaluate it.
|
||||
hasSessionPolicy, isAllowedSP := isAllowedBySessionPolicyForServiceAccount(args)
|
||||
if hasSessionPolicy {
|
||||
return isAllowedSP && (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
|
||||
}
|
||||
|
||||
spolicyStr, ok := spolicy.(string)
|
||||
if !ok {
|
||||
// Sub policy if set, should be a string reject
|
||||
// malformed/malicious requests.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if policy is parseable.
|
||||
subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||
if err != nil {
|
||||
// Log any error in input session policy config.
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return false
|
||||
}
|
||||
|
||||
// This can only happen if policy was set but with an empty JSON.
|
||||
if subPolicy.Version == "" && len(subPolicy.Statements) == 0 {
|
||||
return isOwnerDerived || combinedPolicy.IsAllowed(parentArgs)
|
||||
}
|
||||
|
||||
if subPolicy.Version == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return subPolicy.IsAllowed(parentArgs) && (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
|
||||
// Sub policy not set. Evaluate only the parent policies.
|
||||
return (isOwnerDerived || combinedPolicy.IsAllowed(parentArgs))
|
||||
}
|
||||
|
||||
// IsAllowedSTS is meant for STS based temporary credentials,
|
||||
@@ -1968,6 +1945,67 @@ func (sys *IAMSys) IsAllowedSTS(args policy.Args, parentUser string) bool {
|
||||
return isOwnerDerived || combinedPolicy.IsAllowed(args)
|
||||
}
|
||||
|
||||
func isAllowedBySessionPolicyForServiceAccount(args policy.Args) (hasSessionPolicy bool, isAllowed bool) {
|
||||
hasSessionPolicy = false
|
||||
isAllowed = false
|
||||
|
||||
// Now check if we have a sessionPolicy.
|
||||
spolicy, ok := args.Claims[sessionPolicyNameExtracted]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
hasSessionPolicy = true
|
||||
|
||||
spolicyStr, ok := spolicy.(string)
|
||||
if !ok {
|
||||
// Sub policy if set, should be a string reject
|
||||
// malformed/malicious requests.
|
||||
return
|
||||
}
|
||||
|
||||
// Check if policy is parseable.
|
||||
subPolicy, err := policy.ParseConfig(bytes.NewReader([]byte(spolicyStr)))
|
||||
if err != nil {
|
||||
// Log any error in input session policy config.
|
||||
logger.LogIf(GlobalContext, err)
|
||||
return
|
||||
}
|
||||
|
||||
// SPECIAL CASE: For service accounts, any valid JSON is allowed as a
|
||||
// policy, regardless of whether the number of statements is 0, this
|
||||
// includes `null`, `{}` and `{"Statement": null}`. In fact, MinIO Console
|
||||
// sends `null` when no policy is set and the intended behavior is that the
|
||||
// service account should inherit parent policy.
|
||||
//
|
||||
// However, for a policy like `{"Statement":[]}`, the intention is to not
|
||||
// provide any permissions via the session policy - i.e. the service account
|
||||
// can do nothing (such a JSON could be generated by an external application
|
||||
// as the policy for the service account). Inheriting the parent policy in
|
||||
// such a case, is a security issue. Ideally, we should not allow such
|
||||
// behavior, but for compatibility with the Console, we currently allow it.
|
||||
//
|
||||
// TODO:
|
||||
//
|
||||
// 1. fix console behavior and allow this inheritance for service accounts
|
||||
// created before a certain (TBD) future date.
|
||||
//
|
||||
// 2. do not allow empty statement policies for service accounts.
|
||||
if subPolicy.Version == "" && subPolicy.Statements == nil && subPolicy.ID == "" {
|
||||
hasSessionPolicy = false
|
||||
return
|
||||
}
|
||||
|
||||
// As the session policy exists, even if the parent is the root account, it
|
||||
// must be restricted by it. So, we set `.IsOwner` to false here
|
||||
// unconditionally.
|
||||
sessionPolicyArgs := args
|
||||
sessionPolicyArgs.IsOwner = false
|
||||
|
||||
// Sub policy is set and valid.
|
||||
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
|
||||
}
|
||||
|
||||
func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowed bool) {
|
||||
hasSessionPolicy = false
|
||||
isAllowed = false
|
||||
@@ -2000,8 +2038,14 @@ func isAllowedBySessionPolicy(args policy.Args) (hasSessionPolicy bool, isAllowe
|
||||
return
|
||||
}
|
||||
|
||||
// As the session policy exists, even if the parent is the root account, it
|
||||
// must be restricted by it. So, we set `.IsOwner` to false here
|
||||
// unconditionally.
|
||||
sessionPolicyArgs := args
|
||||
sessionPolicyArgs.IsOwner = false
|
||||
|
||||
// Sub policy is set and valid.
|
||||
return hasSessionPolicy, subPolicy.IsAllowed(args)
|
||||
return hasSessionPolicy, subPolicy.IsAllowed(sessionPolicyArgs)
|
||||
}
|
||||
|
||||
// GetCombinedPolicy returns a combined policy combining all policies
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/pubsub"
|
||||
"github.com/minio/mux"
|
||||
@@ -116,11 +118,32 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
|
||||
|
||||
// Listen Publisher and peer-listen-client uses nonblocking send and hence does not wait for slow receivers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
listenCh := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity()*len(globalEndpoints.Hostnames()))
|
||||
mergeCh := make(chan []byte, globalAPIConfig.getRequestsPoolCapacity()*len(globalEndpoints.Hostnames()))
|
||||
localCh := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity())
|
||||
|
||||
// Convert local messages to JSON and send to mergeCh
|
||||
go func() {
|
||||
buf := bytes.NewBuffer(grid.GetByteBuffer()[:0])
|
||||
enc := json.NewEncoder(buf)
|
||||
tmpEvt := struct{ Records []event.Event }{[]event.Event{{}}}
|
||||
for {
|
||||
select {
|
||||
case ev := <-localCh:
|
||||
buf.Reset()
|
||||
tmpEvt.Records[0] = ev
|
||||
if err := enc.Encode(tmpEvt); err != nil {
|
||||
logger.LogOnceIf(ctx, err, "event: Encode failed")
|
||||
continue
|
||||
}
|
||||
mergeCh <- append(grid.GetByteBuffer()[:0], buf.Bytes()...)
|
||||
case <-ctx.Done():
|
||||
grid.PutByteBuffer(buf.Bytes())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
peers, _ := newPeerRestClients(globalEndpoints)
|
||||
|
||||
err := globalHTTPListen.Subscribe(mask, listenCh, ctx.Done(), func(ev event.Event) bool {
|
||||
err := globalHTTPListen.Subscribe(mask, localCh, ctx.Done(), func(ev event.Event) bool {
|
||||
if ev.S3.Bucket.Name != "" && bucketName != "" {
|
||||
if ev.S3.Bucket.Name != bucketName {
|
||||
return false
|
||||
@@ -139,7 +162,7 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
|
||||
if peer == nil {
|
||||
continue
|
||||
}
|
||||
peer.Listen(listenCh, ctx.Done(), values)
|
||||
peer.Listen(ctx, mergeCh, values)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -170,14 +193,16 @@ func (api objectAPIHandlers) ListenNotificationHandler(w http.ResponseWriter, r
|
||||
enc := json.NewEncoder(w)
|
||||
for {
|
||||
select {
|
||||
case ev := <-listenCh:
|
||||
if err := enc.Encode(struct{ Records []event.Event }{[]event.Event{ev}}); err != nil {
|
||||
case ev := <-mergeCh:
|
||||
_, err := w.Write(ev)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(listenCh) == 0 {
|
||||
if len(mergeCh) == 0 {
|
||||
// Flush if nothing is queued
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
grid.PutByteBuffer(ev)
|
||||
case <-emptyEventTicker:
|
||||
if err := enc.Encode(struct{ Records []event.Event }{}); err != nil {
|
||||
return
|
||||
|
||||
@@ -58,9 +58,10 @@ func renameAllBucketMetacache(epPath string) error {
|
||||
// Other important fields are Limit, Marker.
|
||||
// List ID always derived from the Marker.
|
||||
func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (entries metaCacheEntriesSorted, err error) {
|
||||
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker, z); err != nil {
|
||||
if err := checkListObjsArgs(ctx, o.Bucket, o.Prefix, o.Marker); err != nil {
|
||||
return entries, err
|
||||
}
|
||||
|
||||
// Marker points to before the prefix, just ignore it.
|
||||
if o.Marker < o.Prefix {
|
||||
o.Marker = ""
|
||||
@@ -313,6 +314,9 @@ func (z *erasureServerPools) listMerged(ctx context.Context, o listPathOptions,
|
||||
}
|
||||
|
||||
if isAllNotFound(errs) {
|
||||
if isAllVolumeNotFound(errs) {
|
||||
return errVolumeNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+14
-3
@@ -427,7 +427,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
||||
if !disk.IsOnline() {
|
||||
continue
|
||||
}
|
||||
_, err := disk.ReadVersion(ctx, minioMetaBucket,
|
||||
_, err := disk.ReadVersion(ctx, "", minioMetaBucket,
|
||||
o.objectPath(0), "", ReadOptions{})
|
||||
if err != nil {
|
||||
time.Sleep(retryDelay250)
|
||||
@@ -504,7 +504,7 @@ func (er *erasureObjects) streamMetadataParts(ctx context.Context, o listPathOpt
|
||||
if !disk.IsOnline() {
|
||||
continue
|
||||
}
|
||||
_, err := disk.ReadVersion(ctx, minioMetaBucket,
|
||||
_, err := disk.ReadVersion(ctx, "", minioMetaBucket,
|
||||
o.objectPath(partN), "", ReadOptions{})
|
||||
if err != nil {
|
||||
time.Sleep(retryDelay250)
|
||||
@@ -1057,7 +1057,7 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
||||
for {
|
||||
// Get the top entry from each
|
||||
var current metaCacheEntry
|
||||
var atEOF, fnf, hasErr, agree int
|
||||
var atEOF, fnf, vnf, hasErr, agree int
|
||||
for i := range topEntries {
|
||||
topEntries[i] = metaCacheEntry{}
|
||||
}
|
||||
@@ -1083,6 +1083,11 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
||||
errDiskNotFound.Error():
|
||||
atEOF++
|
||||
fnf++
|
||||
// This is a special case, to handle bucket does
|
||||
// not exist situations.
|
||||
if errors.Is(err, errVolumeNotFound) {
|
||||
vnf++
|
||||
}
|
||||
continue
|
||||
}
|
||||
hasErr++
|
||||
@@ -1140,6 +1145,10 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
||||
return errors.New(strings.Join(combinedErr, ", "))
|
||||
}
|
||||
|
||||
if vnf == len(readers) {
|
||||
return errVolumeNotFound
|
||||
}
|
||||
|
||||
// Break if all at EOF or error.
|
||||
if atEOF+hasErr == len(readers) {
|
||||
if hasErr > 0 && opts.finished != nil {
|
||||
@@ -1147,9 +1156,11 @@ func listPathRaw(ctx context.Context, opts listPathRawOptions) (err error) {
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if fnf == len(readers) {
|
||||
return errFileNotFound
|
||||
}
|
||||
|
||||
if agree == len(readers) {
|
||||
// Everybody agreed
|
||||
for _, r := range readers {
|
||||
|
||||
@@ -73,12 +73,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
if !skipAccessChecks(opts.Bucket) {
|
||||
// Stat a volume entry.
|
||||
if err = Access(volumeDir); err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return errVolumeNotFound
|
||||
} else if isSysErrIO(err) {
|
||||
return errFaultyDisk
|
||||
}
|
||||
return err
|
||||
return convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +164,7 @@ func (s *xlStorage) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Writ
|
||||
if s.walkMu != nil {
|
||||
s.walkMu.Lock()
|
||||
}
|
||||
entries, err := s.ListDir(ctx, opts.Bucket, current, -1)
|
||||
entries, err := s.ListDir(ctx, "", opts.Bucket, current, -1)
|
||||
if s.walkMu != nil {
|
||||
s.walkMu.Unlock()
|
||||
}
|
||||
|
||||
+14
-34
@@ -78,7 +78,6 @@ func init() {
|
||||
getMinioHealingMetrics(MetricsGroupOpts{dependGlobalBackgroundHealState: true}),
|
||||
getWebhookMetrics(),
|
||||
getTierMetrics(),
|
||||
getReplicationNodeMetrics(MetricsGroupOpts{dependGlobalObjectAPI: true, dependBucketTargetSys: true}),
|
||||
}
|
||||
|
||||
allMetricsGroups := func() (allMetrics []*MetricsGroup) {
|
||||
@@ -2155,58 +2154,45 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
if globalReplicationStats != nil {
|
||||
qs := globalReplicationStats.getNodeQueueStatsSummary()
|
||||
activeWorkersCount := Metric{
|
||||
Description: getClusterReplActiveWorkersCountMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplActiveWorkersCountMD(),
|
||||
}
|
||||
avgActiveWorkersCount := Metric{
|
||||
Description: getClusterReplAvgActiveWorkersCountMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplAvgActiveWorkersCountMD(),
|
||||
}
|
||||
maxActiveWorkersCount := Metric{
|
||||
Description: getClusterReplMaxActiveWorkersCountMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplMaxActiveWorkersCountMD(),
|
||||
}
|
||||
currInQueueCount := Metric{
|
||||
Description: getClusterReplCurrQueuedOperationsMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplCurrQueuedOperationsMD(),
|
||||
}
|
||||
currInQueueBytes := Metric{
|
||||
Description: getClusterReplCurrQueuedBytesMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplCurrQueuedBytesMD(),
|
||||
}
|
||||
|
||||
currTransferRate := Metric{
|
||||
Description: getClusterReplCurrentTransferRateMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplCurrentTransferRateMD(),
|
||||
}
|
||||
avgQueueCount := Metric{
|
||||
Description: getClusterReplAvgQueuedOperationsMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplAvgQueuedOperationsMD(),
|
||||
}
|
||||
avgQueueBytes := Metric{
|
||||
Description: getClusterReplAvgQueuedBytesMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplAvgQueuedBytesMD(),
|
||||
}
|
||||
maxQueueCount := Metric{
|
||||
Description: getClusterReplMaxQueuedOperationsMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplMaxQueuedOperationsMD(),
|
||||
}
|
||||
maxQueueBytes := Metric{
|
||||
Description: getClusterReplMaxQueuedBytesMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplMaxQueuedBytesMD(),
|
||||
}
|
||||
avgTransferRate := Metric{
|
||||
Description: getClusterReplAvgTransferRateMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplAvgTransferRateMD(),
|
||||
}
|
||||
maxTransferRate := Metric{
|
||||
Description: getClusterReplMaxTransferRateMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Description: getClusterReplMaxTransferRateMD(),
|
||||
}
|
||||
mrfCount := Metric{
|
||||
Description: getClusterReplMRFFailedOperationsMD(),
|
||||
VariableLabels: map[string]string{serverName: qs.NodeName},
|
||||
Value: float64(qs.MRFStats.LastFailedCount),
|
||||
Description: getClusterReplMRFFailedOperationsMD(),
|
||||
Value: float64(qs.MRFStats.LastFailedCount),
|
||||
}
|
||||
|
||||
if qs.QStats.Avg.Count > 0 || qs.QStats.Curr.Count > 0 {
|
||||
@@ -2250,7 +2236,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
Description: getClusterRepLinkLatencyCurrMD(),
|
||||
VariableLabels: map[string]string{
|
||||
"endpoint": ep,
|
||||
serverName: globalLocalNodeName,
|
||||
},
|
||||
}
|
||||
m.Value = float64(health.latency.curr / time.Millisecond)
|
||||
@@ -2261,7 +2246,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
Description: getClusterRepLinkLatencyAvgMD(),
|
||||
VariableLabels: map[string]string{
|
||||
"endpoint": ep,
|
||||
serverName: globalLocalNodeName,
|
||||
},
|
||||
}
|
||||
m.Value = float64(health.latency.avg / time.Millisecond)
|
||||
@@ -2272,7 +2256,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
Description: getClusterRepLinkLatencyMaxMD(),
|
||||
VariableLabels: map[string]string{
|
||||
"endpoint": ep,
|
||||
serverName: globalLocalNodeName,
|
||||
},
|
||||
}
|
||||
m.Value = float64(health.latency.peak / time.Millisecond)
|
||||
@@ -2282,7 +2265,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
Description: getClusterRepLinkOnlineMD(),
|
||||
VariableLabels: map[string]string{
|
||||
"endpoint": ep,
|
||||
serverName: globalLocalNodeName,
|
||||
},
|
||||
}
|
||||
online := Offline
|
||||
@@ -2295,7 +2277,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
Description: getClusterRepLinkCurrOfflineDurationMD(),
|
||||
VariableLabels: map[string]string{
|
||||
"endpoint": ep,
|
||||
serverName: globalLocalNodeName,
|
||||
},
|
||||
}
|
||||
currDowntime := time.Duration(0)
|
||||
@@ -2309,7 +2290,6 @@ func getReplicationNodeMetrics(opts MetricsGroupOpts) *MetricsGroup {
|
||||
Description: getClusterRepLinkTotalOfflineDurationMD(),
|
||||
VariableLabels: map[string]string{
|
||||
"endpoint": ep,
|
||||
serverName: globalLocalNodeName,
|
||||
},
|
||||
}
|
||||
dwntime := currDowntime
|
||||
|
||||
+4
-1
@@ -47,7 +47,7 @@ type mrfState struct {
|
||||
ctx context.Context
|
||||
pools *erasureServerPools
|
||||
|
||||
mu sync.Mutex
|
||||
mu sync.RWMutex
|
||||
opCh chan partialOperation
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@ func (m *mrfState) addPartialOp(op partialOperation) {
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
select {
|
||||
case m.opCh <- op:
|
||||
default:
|
||||
|
||||
@@ -172,11 +172,11 @@ func (d *naughtyDisk) WalkDir(ctx context.Context, opts WalkDirOptions, wr io.Wr
|
||||
return d.disk.WalkDir(ctx, opts, wr)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) {
|
||||
func (d *naughtyDisk) ListDir(ctx context.Context, origvolume, volume, dirPath string, count int) (entries []string, err error) {
|
||||
if err := d.calcError(); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
return d.disk.ListDir(ctx, volume, dirPath, count)
|
||||
return d.disk.ListDir(ctx, origvolume, volume, dirPath, count)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) ReadFile(ctx context.Context, volume string, path string, offset int64, buf []byte, verifier *BitrotVerifier) (n int64, err error) {
|
||||
@@ -193,11 +193,11 @@ func (d *naughtyDisk) ReadFileStream(ctx context.Context, volume, path string, o
|
||||
return d.disk.ReadFileStream(ctx, volume, path, offset, length)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error {
|
||||
func (d *naughtyDisk) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) error {
|
||||
if err := d.calcError(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.disk.CreateFile(ctx, volume, path, size, reader)
|
||||
return d.disk.CreateFile(ctx, origvolume, volume, path, size, reader)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) AppendFile(ctx context.Context, volume string, path string, buf []byte) error {
|
||||
@@ -246,11 +246,11 @@ func (d *naughtyDisk) DeleteVersions(ctx context.Context, volume string, version
|
||||
return d.disk.DeleteVersions(ctx, volume, versions, opts)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) (err error) {
|
||||
func (d *naughtyDisk) WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) (err error) {
|
||||
if err := d.calcError(); err != nil {
|
||||
return err
|
||||
}
|
||||
return d.disk.WriteMetadata(ctx, volume, path, fi)
|
||||
return d.disk.WriteMetadata(ctx, origvolume, volume, path, fi)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo, opts UpdateMetadataOpts) (err error) {
|
||||
@@ -267,11 +267,11 @@ func (d *naughtyDisk) DeleteVersion(ctx context.Context, volume, path string, fi
|
||||
return d.disk.DeleteVersion(ctx, volume, path, fi, forceDelMarker, opts)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) ReadVersion(ctx context.Context, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
func (d *naughtyDisk) ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
if err := d.calcError(); err != nil {
|
||||
return FileInfo{}, err
|
||||
}
|
||||
return d.disk.ReadVersion(ctx, volume, path, versionID, opts)
|
||||
return d.disk.ReadVersion(ctx, origvolume, volume, path, versionID, opts)
|
||||
}
|
||||
|
||||
func (d *naughtyDisk) WriteAll(ctx context.Context, volume string, path string, b []byte) (err error) {
|
||||
|
||||
@@ -40,7 +40,7 @@ func checkDelObjArgs(ctx context.Context, bucket, object string) error {
|
||||
// Checks bucket and object name validity, returns nil if both are valid.
|
||||
func checkBucketAndObjectNames(ctx context.Context, bucket, object string) error {
|
||||
// Verify if bucket is valid.
|
||||
if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketName(bucket) != nil {
|
||||
if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketNameStrict(bucket) != nil {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
// Verify if object is valid.
|
||||
@@ -58,15 +58,12 @@ func checkBucketAndObjectNames(ctx context.Context, bucket, object string) error
|
||||
}
|
||||
|
||||
// Checks for all ListObjects arguments validity.
|
||||
func checkListObjsArgs(ctx context.Context, bucket, prefix, marker string, obj getBucketInfoI) error {
|
||||
// Verify if bucket exists before validating object name.
|
||||
// This is done on purpose since the order of errors is
|
||||
// important here bucket does not exist error should
|
||||
// happen before we return an error for invalid object name.
|
||||
// FIXME: should be moved to handler layer.
|
||||
if err := checkBucketExist(ctx, bucket, obj); err != nil {
|
||||
return err
|
||||
func checkListObjsArgs(ctx context.Context, bucket, prefix, marker string) error {
|
||||
// Verify if bucket is valid.
|
||||
if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketNameStrict(bucket) != nil {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
|
||||
// Validates object prefix validity after bucket exists.
|
||||
if !IsValidObjectPrefix(prefix) {
|
||||
logger.LogIf(ctx, ObjectNameInvalid{
|
||||
@@ -82,8 +79,8 @@ func checkListObjsArgs(ctx context.Context, bucket, prefix, marker string, obj g
|
||||
}
|
||||
|
||||
// Checks for all ListMultipartUploads arguments validity.
|
||||
func checkListMultipartArgs(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string, obj ObjectLayer) error {
|
||||
if err := checkListObjsArgs(ctx, bucket, prefix, keyMarker, obj); err != nil {
|
||||
func checkListMultipartArgs(ctx context.Context, bucket, prefix, keyMarker, uploadIDMarker, delimiter string) error {
|
||||
if err := checkListObjsArgs(ctx, bucket, prefix, keyMarker); err != nil {
|
||||
return err
|
||||
}
|
||||
if uploadIDMarker != "" {
|
||||
@@ -143,14 +140,11 @@ func checkAbortMultipartArgs(ctx context.Context, bucket, object, uploadID strin
|
||||
return checkMultipartObjectArgs(ctx, bucket, object, uploadID, obj)
|
||||
}
|
||||
|
||||
// Checks Object arguments validity, also validates if bucket exists.
|
||||
// Checks Object arguments validity.
|
||||
func checkObjectArgs(ctx context.Context, bucket, object string, obj ObjectLayer) error {
|
||||
// Verify if bucket exists before validating object name.
|
||||
// This is done on purpose since the order of errors is
|
||||
// important here bucket does not exist error should
|
||||
// happen before we return an error for invalid object name.
|
||||
if err := checkBucketExist(ctx, bucket, obj); err != nil {
|
||||
return err
|
||||
// Verify if bucket is valid.
|
||||
if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketNameStrict(bucket) != nil {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
|
||||
if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil {
|
||||
@@ -168,8 +162,13 @@ func checkObjectArgs(ctx context.Context, bucket, object string, obj ObjectLayer
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks for PutObject arguments validity, also validates if bucket exists.
|
||||
// Checks for PutObject arguments validity.
|
||||
func checkPutObjectArgs(ctx context.Context, bucket, object string) error {
|
||||
// Verify if bucket is valid.
|
||||
if !isMinioMetaBucketName(bucket) && s3utils.CheckValidBucketNameStrict(bucket) != nil {
|
||||
return BucketNameInvalid{Bucket: bucket}
|
||||
}
|
||||
|
||||
if err := checkObjectNameForLengthAndSlash(bucket, object); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -182,16 +181,3 @@ func checkPutObjectArgs(ctx context.Context, bucket, object string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type getBucketInfoI interface {
|
||||
GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (bucketInfo BucketInfo, err error)
|
||||
}
|
||||
|
||||
// Checks whether bucket exists and returns appropriate error if not.
|
||||
func checkBucketExist(ctx context.Context, bucket string, obj getBucketInfoI) error {
|
||||
_, err := obj.GetBucketInfo(ctx, bucket, BucketOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -109,6 +109,8 @@ type ObjectOptions struct {
|
||||
// participating in a rebalance operation. Typically set for 'write' operations.
|
||||
SkipRebalancing bool
|
||||
|
||||
DataMovement bool // indicates an going decommisionning or rebalacing
|
||||
|
||||
PrefixEnabledFn func(prefix string) bool // function which returns true if versioning is enabled on prefix
|
||||
|
||||
// IndexCB will return any index created but the compression.
|
||||
|
||||
@@ -155,16 +155,21 @@ func testListObjectsVersionedFolders(obj ObjectLayer, instanceType string, t1 Te
|
||||
for i, testCase := range testCases {
|
||||
testCase := testCase
|
||||
t.Run(fmt.Sprintf("%s-Test%d", instanceType, i+1), func(t *testing.T) {
|
||||
t.Log("ListObjects, bucket:", testCase.bucketName, "prefix:",
|
||||
testCase.prefix, "marker:", testCase.marker, "delimiter:",
|
||||
testCase.delimiter, "maxkeys:", testCase.maxKeys)
|
||||
var err error
|
||||
var resultL ListObjectsInfo
|
||||
var resultV ListObjectVersionsInfo
|
||||
if testCase.versioned {
|
||||
t.Log("ListObjectVersions, bucket:", testCase.bucketName, "prefix:",
|
||||
testCase.prefix, "marker:", testCase.marker, "delimiter:",
|
||||
testCase.delimiter, "maxkeys:", testCase.maxKeys)
|
||||
|
||||
resultV, err = obj.ListObjectVersions(context.Background(), testCase.bucketName,
|
||||
testCase.prefix, testCase.marker, "", testCase.delimiter, testCase.maxKeys)
|
||||
} else {
|
||||
t.Log("ListObjects, bucket:", testCase.bucketName, "prefix:",
|
||||
testCase.prefix, "marker:", testCase.marker, "delimiter:",
|
||||
testCase.delimiter, "maxkeys:", testCase.maxKeys)
|
||||
|
||||
resultL, err = obj.ListObjects(context.Background(), testCase.bucketName,
|
||||
testCase.prefix, testCase.marker, testCase.delimiter, testCase.maxKeys)
|
||||
}
|
||||
@@ -819,16 +824,14 @@ func _testListObjects(obj ObjectLayer, instanceType string, t1 TestErrHandler, v
|
||||
shouldPass bool
|
||||
}{
|
||||
// Test cases with invalid bucket names ( Test number 1-4 ).
|
||||
{".test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: ".test"}, false},
|
||||
{"Test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Using an existing file for bucket name, but its not a directory (5).
|
||||
{"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false},
|
||||
{".test", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
|
||||
{"Test", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
|
||||
// Valid bucket names, but they do not exist (6-8).
|
||||
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
{"volatile-bucket-1", "", "", "", 1000, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", 1000, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", 1000, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
// If marker is *after* the last possible object from the prefix it should return an empty list.
|
||||
{"test-bucket-list-object", "Asia", "europe-object", "", 0, ListObjectsInfo{}, nil, true},
|
||||
// If the marker is *before* the first possible object from the prefix it should return the first object.
|
||||
@@ -1564,16 +1567,14 @@ func testListObjectVersions(obj ObjectLayer, instanceType string, t1 TestErrHand
|
||||
shouldPass bool
|
||||
}{
|
||||
// Test cases with invalid bucket names ( Test number 1-4).
|
||||
{".test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: ".test"}, false},
|
||||
{"Test", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
// Using an existing file for bucket name, but its not a directory (5).
|
||||
{"simple-file.txt", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "simple-file.txt"}, false},
|
||||
{".test", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
|
||||
{"Test", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", 0, ListObjectsInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
|
||||
// Valid bucket names, but they do not exist (6-8).
|
||||
{"volatile-bucket-1", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", 0, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
{"volatile-bucket-1", "", "", "", 1000, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", 1000, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", "", 1000, ListObjectsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
// If marker is *after* the last possible object from the prefix it should return an empty list.
|
||||
{"test-bucket-list-object", "Asia", "europe-object", "", 0, ListObjectsInfo{}, nil, true},
|
||||
// Setting a non-existing directory to be prefix (10-11).
|
||||
|
||||
@@ -113,7 +113,7 @@ func testObjectAbortMultipartUpload(obj ObjectLayer, instanceType string, t Test
|
||||
uploadID string
|
||||
expectedErrType error
|
||||
}{
|
||||
{"--", object, uploadID, BucketNotFound{}},
|
||||
{"--", object, uploadID, BucketNameInvalid{}},
|
||||
{"foo", object, uploadID, BucketNotFound{}},
|
||||
{bucket, object, "foo-foo", InvalidUploadID{}},
|
||||
{bucket, object, uploadID, nil},
|
||||
@@ -194,6 +194,19 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
|
||||
// Failed to create NewMultipartUpload, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
err = obj.MakeBucket(context.Background(), "abc", MakeBucketOptions{})
|
||||
if err != nil {
|
||||
// Failed to create newbucket, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
resN, err := obj.NewMultipartUpload(context.Background(), "abc", "def", opts)
|
||||
if err != nil {
|
||||
// Failed to create NewMultipartUpload, abort.
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
uploadID := res.UploadID
|
||||
// Creating a dummy bucket for tests.
|
||||
err = obj.MakeBucket(context.Background(), "unused-bucket", MakeBucketOptions{})
|
||||
@@ -202,6 +215,8 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
|
||||
t.Fatalf("%s : %s", instanceType, err.Error())
|
||||
}
|
||||
|
||||
obj.DeleteBucket(context.Background(), "abc", DeleteBucketOptions{})
|
||||
|
||||
// Collection of non-exhaustive PutObjectPart test cases, valid errors
|
||||
// and success responses.
|
||||
testCases := []struct {
|
||||
@@ -221,19 +236,19 @@ func testObjectAPIPutObjectPart(obj ObjectLayer, instanceType string, t TestErrH
|
||||
}{
|
||||
// Test case 1-4.
|
||||
// Cases with invalid bucket name.
|
||||
{bucketName: ".test", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket not found: .test")},
|
||||
{bucketName: "------", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket not found: ------")},
|
||||
{bucketName: ".test", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket name invalid: .test")},
|
||||
{bucketName: "------", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket name invalid: ------")},
|
||||
{
|
||||
bucketName: "$this-is-not-valid-too", objName: "obj", PartID: 1,
|
||||
expectedError: fmt.Errorf("%s", "Bucket not found: $this-is-not-valid-too"),
|
||||
expectedError: fmt.Errorf("%s", "Bucket name invalid: $this-is-not-valid-too"),
|
||||
},
|
||||
{bucketName: "a", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket not found: a")},
|
||||
{bucketName: "a", objName: "obj", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket name invalid: a")},
|
||||
// Test case - 5.
|
||||
// Case with invalid object names.
|
||||
{bucketName: bucket, PartID: 1, expectedError: fmt.Errorf("%s", "Object name invalid: minio-bucket/")},
|
||||
// Test case - 6.
|
||||
// Valid object and bucket names but non-existent bucket.
|
||||
{bucketName: "abc", objName: "def", PartID: 1, expectedError: fmt.Errorf("%s", "Bucket not found: abc")},
|
||||
{bucketName: "abc", objName: "def", uploadID: resN.UploadID, PartID: 1, expectedError: fmt.Errorf("%s", "Bucket not found: abc")},
|
||||
// Test Case - 7.
|
||||
// Existing bucket, but using a bucket on which NewMultipartUpload is not Initiated.
|
||||
{bucketName: "unused-bucket", objName: "def", uploadID: "xyz", PartID: 1, expectedError: fmt.Errorf("%s", "Invalid upload id xyz")},
|
||||
@@ -1041,10 +1056,10 @@ func testListMultipartUploads(obj ObjectLayer, instanceType string, t TestErrHan
|
||||
shouldPass bool
|
||||
}{
|
||||
// Test cases with invalid bucket names ( Test number 1-4 ).
|
||||
{".test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: ".test"}, false},
|
||||
{"Test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
{".test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
|
||||
{"Test", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
|
||||
{"---", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: "---"}, false},
|
||||
{"ad", "", "", "", "", 0, ListMultipartsInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", "", "", 0, ListMultipartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
@@ -1357,15 +1372,15 @@ func testListObjectPartsDiskNotFound(obj ObjectLayer, instanceType string, disks
|
||||
shouldPass bool
|
||||
}{
|
||||
// Test cases with invalid bucket names (Test number 1-4).
|
||||
{".test", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: ".test"}, false},
|
||||
{"Test", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
{".test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
|
||||
{"Test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
|
||||
{"---", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "---"}, false},
|
||||
{"ad", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
|
||||
// Test cases for listing uploadID with single part.
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
{"volatile-bucket-1", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
// Test case for Asserting for invalid objectName (Test number 8).
|
||||
{bucketNames[0], "", "", 0, 0, ListPartsInfo{}, ObjectNameInvalid{Bucket: bucketNames[0]}, false},
|
||||
// Asserting for Invalid UploadID (Test number 9).
|
||||
@@ -1594,15 +1609,15 @@ func testListObjectParts(obj ObjectLayer, instanceType string, t TestErrHandler)
|
||||
shouldPass bool
|
||||
}{
|
||||
// Test cases with invalid bucket names (Test number 1-4).
|
||||
{".test", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: ".test"}, false},
|
||||
{"Test", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "ad"}, false},
|
||||
{".test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: ".test"}, false},
|
||||
{"Test", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "Test"}, false},
|
||||
{"---", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "---"}, false},
|
||||
{"ad", "", "", 0, 0, ListPartsInfo{}, BucketNameInvalid{Bucket: "ad"}, false},
|
||||
// Test cases for listing uploadID with single part.
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
{"volatile-bucket-1", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "test1", "", 0, 0, ListPartsInfo{}, BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
// Test case for Asserting for invalid objectName (Test number 8).
|
||||
{bucketNames[0], "", "", 0, 0, ListPartsInfo{}, ObjectNameInvalid{Bucket: bucketNames[0]}, false},
|
||||
// Asserting for Invalid UploadID (Test number 9).
|
||||
@@ -1809,15 +1824,15 @@ func testObjectCompleteMultipartUpload(obj ObjectLayer, instanceType string, t T
|
||||
shouldPass bool
|
||||
}{
|
||||
// Test cases with invalid bucket names (Test number 1-4).
|
||||
{".test", "", "", []CompletePart{}, "", BucketNotFound{Bucket: ".test"}, false},
|
||||
{"Test", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "Test"}, false},
|
||||
{"---", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "---"}, false},
|
||||
{"ad", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "ad"}, false},
|
||||
{".test", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: ".test"}, false},
|
||||
{"Test", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: "Test"}, false},
|
||||
{"---", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: "---"}, false},
|
||||
{"ad", "", "", []CompletePart{}, "", BucketNameInvalid{Bucket: "ad"}, false},
|
||||
// Test cases for listing uploadID with single part.
|
||||
// Valid bucket names, but they do not exist (Test number 5-7).
|
||||
{"volatile-bucket-1", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
{"volatile-bucket-1", "test1", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-1"}, false},
|
||||
{"volatile-bucket-2", "test1", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-2"}, false},
|
||||
{"volatile-bucket-3", "test1", "", []CompletePart{}, "", BucketNotFound{Bucket: "volatile-bucket-3"}, false},
|
||||
// Test case for Asserting for invalid objectName (Test number 8).
|
||||
{bucketNames[0], "", "", []CompletePart{}, "", ObjectNameInvalid{Bucket: bucketNames[0]}, false},
|
||||
// Asserting for Invalid UploadID (Test number 9).
|
||||
|
||||
@@ -81,13 +81,13 @@ func testObjectAPIPutObject(obj ObjectLayer, instanceType string, t TestErrHandl
|
||||
expectedError error
|
||||
}{
|
||||
// Cases with invalid bucket name.
|
||||
0: {bucketName: ".test", objName: "obj", inputData: []byte(""), expectedError: BucketNotFound{Bucket: ".test"}},
|
||||
1: {bucketName: "------", objName: "obj", inputData: []byte(""), expectedError: BucketNotFound{Bucket: "------"}},
|
||||
0: {bucketName: ".test", objName: "obj", inputData: []byte(""), expectedError: BucketNameInvalid{Bucket: ".test"}},
|
||||
1: {bucketName: "------", objName: "obj", inputData: []byte(""), expectedError: BucketNameInvalid{Bucket: "------"}},
|
||||
2: {
|
||||
bucketName: "$this-is-not-valid-too", objName: "obj", inputData: []byte(""),
|
||||
expectedError: BucketNotFound{Bucket: "$this-is-not-valid-too"},
|
||||
expectedError: BucketNameInvalid{Bucket: "$this-is-not-valid-too"},
|
||||
},
|
||||
3: {bucketName: "a", objName: "obj", inputData: []byte(""), expectedError: BucketNotFound{Bucket: "a"}},
|
||||
3: {bucketName: "a", objName: "obj", inputData: []byte(""), expectedError: BucketNameInvalid{Bucket: "a"}},
|
||||
|
||||
// Case with invalid object names.
|
||||
4: {bucketName: bucket, inputData: []byte(""), expectedError: ObjectNameInvalid{Bucket: bucket, Object: ""}},
|
||||
|
||||
@@ -111,7 +111,7 @@ func testPathTraversalExploit(obj ObjectLayer, instanceType, bucketName string,
|
||||
z := obj.(*erasureServerPools)
|
||||
xl := z.serverPools[0].sets[0]
|
||||
erasureDisks := xl.getDisks()
|
||||
parts, errs := readAllFileInfo(ctx, erasureDisks, bucketName, objectName, "", false, false)
|
||||
parts, errs := readAllFileInfo(ctx, erasureDisks, "", bucketName, objectName, "", false, false)
|
||||
for i := range parts {
|
||||
if errs[i] == nil {
|
||||
if parts[i].Name == objectName {
|
||||
|
||||
+226
-223
@@ -22,19 +22,20 @@ import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
"github.com/minio/pkg/v2/logger/message/log"
|
||||
@@ -46,6 +47,66 @@ import (
|
||||
type peerRESTClient struct {
|
||||
host *xnet.Host
|
||||
restClient *rest.Client
|
||||
gridHost string
|
||||
// Function that returns the grid connection for this peer when initialized.
|
||||
// Will return nil if the grid connection is not initialized yet.
|
||||
gridConn func() *grid.Connection
|
||||
}
|
||||
|
||||
// Returns a peer rest client.
|
||||
func newPeerRESTClient(peer *xnet.Host, gridHost string) *peerRESTClient {
|
||||
scheme := "http"
|
||||
if globalIsTLS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
serverURL := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: peer.String(),
|
||||
Path: peerRESTPath,
|
||||
}
|
||||
|
||||
restClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
|
||||
// Use a separate client to avoid recursive calls.
|
||||
healthClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
|
||||
healthClient.NoMetrics = true
|
||||
|
||||
// Construct a new health function.
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout)
|
||||
defer cancel()
|
||||
respBody, err := healthClient.Call(ctx, peerRESTMethodHealth, nil, nil, -1)
|
||||
xhttp.DrainBody(respBody)
|
||||
return !isNetworkError(err)
|
||||
}
|
||||
var gridConn atomic.Pointer[grid.Connection]
|
||||
|
||||
return &peerRESTClient{
|
||||
host: peer, restClient: restClient, gridHost: gridHost,
|
||||
gridConn: func() *grid.Connection {
|
||||
// Lazy initialization of grid connection.
|
||||
// When we create this peer client, the grid connection is likely not yet initialized.
|
||||
if gridHost == "" {
|
||||
logger.LogOnceIf(context.Background(), fmt.Errorf("gridHost is empty for peer %s", peer.String()), peer.String()+":gridHost")
|
||||
return nil
|
||||
}
|
||||
gc := gridConn.Load()
|
||||
if gc != nil {
|
||||
return gc
|
||||
}
|
||||
gm := globalGrid.Load()
|
||||
if gm == nil {
|
||||
return nil
|
||||
}
|
||||
gc = gm.Connection(gridHost)
|
||||
if gc == nil {
|
||||
logger.LogOnceIf(context.Background(), fmt.Errorf("gridHost %q not found for peer %s", gridHost, peer.String()), peer.String()+":gridHost")
|
||||
return nil
|
||||
}
|
||||
gridConn.Store(gc)
|
||||
return gc
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
||||
@@ -254,7 +315,7 @@ func (client *peerRESTClient) GetResourceMetrics(ctx context.Context) (<-chan Me
|
||||
go func(ch chan<- Metric) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
xioutil.SafeClose(ch)
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
var metric Metric
|
||||
@@ -347,134 +408,133 @@ func (client *peerRESTClient) GetAllBucketStats() (BucketStatsMap, error) {
|
||||
|
||||
// LoadBucketMetadata - load bucket metadata
|
||||
func (client *peerRESTClient) LoadBucketMetadata(bucket string) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTBucket, bucket)
|
||||
respBody, err := client.call(peerRESTMethodLoadBucketMetadata, values, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := loadBucketMetadataHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTBucket: bucket,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteBucketMetadata - Delete bucket metadata
|
||||
func (client *peerRESTClient) DeleteBucketMetadata(bucket string) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTBucket, bucket)
|
||||
respBody, err := client.call(peerRESTMethodDeleteBucketMetadata, values, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := deleteBucketMetadataHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTBucket: bucket,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeletePolicy - delete a specific canned policy.
|
||||
func (client *peerRESTClient) DeletePolicy(policyName string) (err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTPolicy, policyName)
|
||||
|
||||
respBody, err := client.call(peerRESTMethodDeletePolicy, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err = deletePolicyHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTPolicy: policyName,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadPolicy - reload a specific canned policy.
|
||||
func (client *peerRESTClient) LoadPolicy(policyName string) (err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTPolicy, policyName)
|
||||
|
||||
respBody, err := client.call(peerRESTMethodLoadPolicy, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err = loadPolicyHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTPolicy: policyName,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadPolicyMapping - reload a specific policy mapping
|
||||
func (client *peerRESTClient) LoadPolicyMapping(userOrGroup string, userType IAMUserType, isGroup bool) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTUserOrGroup, userOrGroup)
|
||||
values.Set(peerRESTUserType, strconv.Itoa(int(userType)))
|
||||
if isGroup {
|
||||
values.Set(peerRESTIsGroup, "")
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
respBody, err := client.call(peerRESTMethodLoadPolicyMapping, values, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := loadPolicyMappingHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTUserOrGroup: userOrGroup,
|
||||
peerRESTUserType: strconv.Itoa(int(userType)),
|
||||
peerRESTIsGroup: strconv.FormatBool(isGroup),
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUser - delete a specific user.
|
||||
func (client *peerRESTClient) DeleteUser(accessKey string) (err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTUser, accessKey)
|
||||
|
||||
respBody, err := client.call(peerRESTMethodDeleteUser, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err = deleteUserHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTUser: accessKey,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteServiceAccount - delete a specific service account.
|
||||
func (client *peerRESTClient) DeleteServiceAccount(accessKey string) (err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTUser, accessKey)
|
||||
|
||||
respBody, err := client.call(peerRESTMethodDeleteServiceAccount, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err = deleteSvcActHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTUser: accessKey,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadUser - reload a specific user.
|
||||
func (client *peerRESTClient) LoadUser(accessKey string, temp bool) (err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTUser, accessKey)
|
||||
values.Set(peerRESTUserTemp, strconv.FormatBool(temp))
|
||||
|
||||
respBody, err := client.call(peerRESTMethodLoadUser, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err = loadUserHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTUser: accessKey,
|
||||
peerRESTUserTemp: strconv.FormatBool(temp),
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadServiceAccount - reload a specific service account.
|
||||
func (client *peerRESTClient) LoadServiceAccount(accessKey string) (err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTUser, accessKey)
|
||||
|
||||
respBody, err := client.call(peerRESTMethodLoadServiceAccount, values, nil, -1)
|
||||
if err != nil {
|
||||
return
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err = loadSvcActHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTUser: accessKey,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadGroup - send load group command to peers.
|
||||
func (client *peerRESTClient) LoadGroup(group string) error {
|
||||
values := make(url.Values)
|
||||
values.Set(peerRESTGroup, group)
|
||||
respBody, err := client.call(peerRESTMethodLoadGroup, values, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
|
||||
_, err := loadGroupHandler.Call(context.Background(), conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTGroup: group,
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// VerifyBinary - sends verify binary message to remote peers.
|
||||
@@ -530,15 +590,17 @@ func (client *peerRESTClient) BackgroundHealStatus() (madmin.BgHealState, error)
|
||||
|
||||
// GetLocalDiskIDs - get a peer's local disks' IDs.
|
||||
func (client *peerRESTClient) GetLocalDiskIDs(ctx context.Context) (diskIDs []string) {
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodGetLocalDiskIDs, nil, nil, -1)
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := getLocalDiskIDsHandler.Call(ctx, conn, grid.NewMSS())
|
||||
if err != nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
if err = gob.NewDecoder(respBody).Decode(&diskIDs); err != nil {
|
||||
return nil
|
||||
}
|
||||
return diskIDs
|
||||
|
||||
return resp.IDs
|
||||
}
|
||||
|
||||
// GetMetacacheListing - get a new or existing metacache.
|
||||
@@ -581,129 +643,98 @@ func (client *peerRESTClient) UpdateMetacacheListing(ctx context.Context, m meta
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) ReloadPoolMeta(ctx context.Context) error {
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodReloadPoolMeta, nil, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := reloadPoolMetaHandler.Call(ctx, conn, grid.NewMSSWith(map[string]string{}))
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) StopRebalance(ctx context.Context) error {
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodStopRebalance, nil, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := stopRebalanceHandler.Call(ctx, conn, grid.NewMSSWith(map[string]string{}))
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) LoadRebalanceMeta(ctx context.Context, startRebalance bool) error {
|
||||
values := url.Values{}
|
||||
values.Set(peerRESTStartRebalance, strconv.FormatBool(startRebalance))
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodLoadRebalanceMeta, values, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := loadRebalanceMetaHandler.Call(ctx, conn, grid.NewMSSWith(map[string]string{
|
||||
peerRESTStartRebalance: strconv.FormatBool(startRebalance),
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) LoadTransitionTierConfig(ctx context.Context) error {
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodLoadTransitionTierConfig, nil, nil, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
return nil
|
||||
_, err := loadTransitionTierConfigHandler.Call(ctx, conn, grid.NewMSSWith(map[string]string{}))
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) doTrace(traceCh chan<- madmin.TraceInfo, doneCh <-chan struct{}, traceOpts madmin.ServiceTraceOpts) {
|
||||
values := make(url.Values)
|
||||
traceOpts.AddParams(values)
|
||||
|
||||
// To cancel the REST request in case doneCh gets closed.
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
|
||||
cancelCh := make(chan struct{})
|
||||
defer xioutil.SafeClose(cancelCh)
|
||||
go func() {
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-cancelCh:
|
||||
// There was an error in the REST request.
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodTrace, values, nil, -1)
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
if err != nil {
|
||||
func (client *peerRESTClient) doTrace(ctx context.Context, traceCh chan<- []byte, traceOpts madmin.ServiceTraceOpts) {
|
||||
gridConn := client.gridConn()
|
||||
if gridConn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
dec := gob.NewDecoder(respBody)
|
||||
for {
|
||||
var info madmin.TraceInfo
|
||||
if err = dec.Decode(&info); err != nil {
|
||||
return
|
||||
}
|
||||
if len(info.NodeName) > 0 {
|
||||
select {
|
||||
case traceCh <- info:
|
||||
default:
|
||||
// Do not block on slow receivers.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) doListen(listenCh chan<- event.Event, doneCh <-chan struct{}, v url.Values) {
|
||||
// To cancel the REST request in case doneCh gets closed.
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
|
||||
cancelCh := make(chan struct{})
|
||||
defer xioutil.SafeClose(cancelCh)
|
||||
go func() {
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-cancelCh:
|
||||
// There was an error in the REST request.
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
respBody, err := client.callWithContext(ctx, peerRESTMethodListen, v, nil, -1)
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
payload, err := json.Marshal(traceOpts)
|
||||
if err != nil {
|
||||
logger.LogIf(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
dec := gob.NewDecoder(respBody)
|
||||
for {
|
||||
var ev event.Event
|
||||
if err := dec.Decode(&ev); err != nil {
|
||||
return
|
||||
}
|
||||
if len(ev.EventVersion) > 0 {
|
||||
select {
|
||||
case listenCh <- ev:
|
||||
default:
|
||||
// Do not block on slow receivers.
|
||||
}
|
||||
}
|
||||
st, err := gridConn.NewStream(ctx, grid.HandlerTrace, payload)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.Results(func(b []byte) error {
|
||||
select {
|
||||
case traceCh <- b:
|
||||
default:
|
||||
// Do not block on slow receivers.
|
||||
// Just recycle the buffer.
|
||||
grid.PutByteBuffer(b)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (client *peerRESTClient) doListen(ctx context.Context, listenCh chan<- []byte, v url.Values) {
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
st, err := listenHandler.Call(ctx, conn, grid.NewURLValuesWith(v))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
st.Results(func(b *grid.Bytes) error {
|
||||
select {
|
||||
case listenCh <- *b:
|
||||
default:
|
||||
// Do not block on slow receivers.
|
||||
b.Recycle()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Listen - listen on peers.
|
||||
func (client *peerRESTClient) Listen(listenCh chan<- event.Event, doneCh <-chan struct{}, v url.Values) {
|
||||
func (client *peerRESTClient) Listen(ctx context.Context, listenCh chan<- []byte, v url.Values) {
|
||||
go func() {
|
||||
for {
|
||||
client.doListen(listenCh, doneCh, v)
|
||||
client.doListen(ctx, listenCh, v)
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
// There was error in the REST request, retry after sometime as probably the peer is down.
|
||||
@@ -714,12 +745,13 @@ func (client *peerRESTClient) Listen(listenCh chan<- event.Event, doneCh <-chan
|
||||
}
|
||||
|
||||
// Trace - send http trace request to peer nodes
|
||||
func (client *peerRESTClient) Trace(traceCh chan<- madmin.TraceInfo, doneCh <-chan struct{}, traceOpts madmin.ServiceTraceOpts) {
|
||||
func (client *peerRESTClient) Trace(ctx context.Context, traceCh chan<- []byte, traceOpts madmin.ServiceTraceOpts) {
|
||||
go func() {
|
||||
for {
|
||||
client.doTrace(traceCh, doneCh, traceOpts)
|
||||
// Blocks until context is canceled or an error occurs.
|
||||
client.doTrace(ctx, traceCh, traceOpts)
|
||||
select {
|
||||
case <-doneCh:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
// There was error in the REST request, retry after sometime as probably the peer is down.
|
||||
@@ -734,7 +766,7 @@ func (client *peerRESTClient) doConsoleLog(logCh chan log.Info, doneCh <-chan st
|
||||
ctx, cancel := context.WithCancel(GlobalContext)
|
||||
|
||||
cancelCh := make(chan struct{})
|
||||
defer xioutil.SafeClose(cancelCh)
|
||||
defer close(cancelCh)
|
||||
go func() {
|
||||
select {
|
||||
case <-doneCh:
|
||||
@@ -789,6 +821,7 @@ func newPeerRestClients(endpoints EndpointServerPools) (remote, all []*peerRESTC
|
||||
// Only useful in distributed setups
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
hosts := endpoints.hostsSorted()
|
||||
remote = make([]*peerRESTClient, 0, len(hosts))
|
||||
all = make([]*peerRESTClient, len(hosts))
|
||||
@@ -796,7 +829,7 @@ func newPeerRestClients(endpoints EndpointServerPools) (remote, all []*peerRESTC
|
||||
if host == nil {
|
||||
continue
|
||||
}
|
||||
all[i] = newPeerRESTClient(host)
|
||||
all[i] = newPeerRESTClient(host, endpoints.FindGridHostsFromPeer(host))
|
||||
remote = append(remote, all[i])
|
||||
}
|
||||
if len(all) != len(remote)+1 {
|
||||
@@ -805,36 +838,6 @@ func newPeerRestClients(endpoints EndpointServerPools) (remote, all []*peerRESTC
|
||||
return remote, all
|
||||
}
|
||||
|
||||
// Returns a peer rest client.
|
||||
func newPeerRESTClient(peer *xnet.Host) *peerRESTClient {
|
||||
scheme := "http"
|
||||
if globalIsTLS {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
serverURL := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: peer.String(),
|
||||
Path: peerRESTPath,
|
||||
}
|
||||
|
||||
restClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
|
||||
// Use a separate client to avoid recursive calls.
|
||||
healthClient := rest.NewClient(serverURL, globalInternodeTransport, newCachedAuthToken())
|
||||
healthClient.NoMetrics = true
|
||||
|
||||
// Construct a new health function.
|
||||
restClient.HealthCheckFn = func() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), restClient.HealthCheckTimeout)
|
||||
defer cancel()
|
||||
respBody, err := healthClient.Call(ctx, peerRESTMethodHealth, nil, nil, -1)
|
||||
xhttp.DrainBody(respBody)
|
||||
return !isNetworkError(err)
|
||||
}
|
||||
|
||||
return &peerRESTClient{host: peer, restClient: restClient}
|
||||
}
|
||||
|
||||
// MonitorBandwidth - send http trace request to peer nodes
|
||||
func (client *peerRESTClient) MonitorBandwidth(ctx context.Context, buckets []string) (*bandwidth.BucketBandwidthReport, error) {
|
||||
values := make(url.Values)
|
||||
@@ -861,7 +864,7 @@ func (client *peerRESTClient) GetPeerMetrics(ctx context.Context) (<-chan Metric
|
||||
go func(ch chan<- Metric) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
xioutil.SafeClose(ch)
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
var metric Metric
|
||||
@@ -888,7 +891,7 @@ func (client *peerRESTClient) GetPeerBucketMetrics(ctx context.Context) (<-chan
|
||||
go func(ch chan<- Metric) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
xioutil.SafeClose(ch)
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
var metric Metric
|
||||
@@ -1026,7 +1029,7 @@ func (client *peerRESTClient) GetReplicationMRF(ctx context.Context, bucket stri
|
||||
go func(ch chan madmin.ReplicationMRF) {
|
||||
defer func() {
|
||||
xhttp.DrainBody(respBody)
|
||||
xioutil.SafeClose(ch)
|
||||
close(ch)
|
||||
}()
|
||||
for {
|
||||
var entry madmin.ReplicationMRF
|
||||
|
||||
+315
-320
@@ -18,6 +18,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
@@ -37,6 +38,7 @@ import (
|
||||
"github.com/minio/madmin-go/v3"
|
||||
b "github.com/minio/minio/internal/bucket/bandwidth"
|
||||
"github.com/minio/minio/internal/event"
|
||||
"github.com/minio/minio/internal/grid"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/pubsub"
|
||||
@@ -59,192 +61,151 @@ func (s *peerRESTServer) GetLocksHandler(w http.ResponseWriter, r *http.Request)
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(globalLockServer.DupLockMap()))
|
||||
}
|
||||
|
||||
// DeletePolicyHandler - deletes a policy on the server.
|
||||
func (s *peerRESTServer) DeletePolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
var (
|
||||
deletePolicyHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerDeletePolicy, grid.NewMSS, grid.NewNoPayload)
|
||||
loadPolicyHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadPolicy, grid.NewMSS, grid.NewNoPayload)
|
||||
loadPolicyMappingHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadPolicyMapping, grid.NewMSS, grid.NewNoPayload)
|
||||
deleteSvcActHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerDeleteServiceAccount, grid.NewMSS, grid.NewNoPayload)
|
||||
loadSvcActHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadServiceAccount, grid.NewMSS, grid.NewNoPayload)
|
||||
deleteUserHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerDeleteUser, grid.NewMSS, grid.NewNoPayload)
|
||||
loadUserHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadUser, grid.NewMSS, grid.NewNoPayload)
|
||||
loadGroupHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadGroup, grid.NewMSS, grid.NewNoPayload)
|
||||
)
|
||||
|
||||
// DeletePolicyHandler - deletes a policy on the server.
|
||||
func (s *peerRESTServer) DeletePolicyHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
policyName := vars[peerRESTPolicy]
|
||||
policyName := mss.Get(peerRESTPolicy)
|
||||
if policyName == "" {
|
||||
s.writeErrorResponse(w, errors.New("policyName is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("policyName is missing"))
|
||||
}
|
||||
|
||||
if err := globalIAMSys.DeletePolicy(r.Context(), policyName, false); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err := globalIAMSys.DeletePolicy(context.Background(), policyName, false); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadPolicyHandler - reloads a policy on the server.
|
||||
func (s *peerRESTServer) LoadPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadPolicyHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
policyName := vars[peerRESTPolicy]
|
||||
policyName := mss.Get(peerRESTPolicy)
|
||||
if policyName == "" {
|
||||
s.writeErrorResponse(w, errors.New("policyName is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("policyName is missing"))
|
||||
}
|
||||
|
||||
if err := globalIAMSys.LoadPolicy(r.Context(), objAPI, policyName); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err := globalIAMSys.LoadPolicy(context.Background(), objAPI, policyName); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadPolicyMappingHandler - reloads a policy mapping on the server.
|
||||
func (s *peerRESTServer) LoadPolicyMappingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadPolicyMappingHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
userOrGroup := r.Form.Get(peerRESTUserOrGroup)
|
||||
userOrGroup := mss.Get(peerRESTUserOrGroup)
|
||||
if userOrGroup == "" {
|
||||
s.writeErrorResponse(w, errors.New("user-or-group is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("user-or-group is missing"))
|
||||
}
|
||||
|
||||
userType, err := strconv.Atoi(r.Form.Get(peerRESTUserType))
|
||||
userType, err := strconv.Atoi(mss.Get(peerRESTUserType))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, fmt.Errorf("user-type `%s` is invalid: %w", r.Form.Get(peerRESTUserType), err))
|
||||
return
|
||||
return np, grid.NewRemoteErr(fmt.Errorf("user-type `%s` is invalid: %w", mss.Get(peerRESTUserType), err))
|
||||
}
|
||||
|
||||
_, isGroup := r.Form[peerRESTIsGroup]
|
||||
if err := globalIAMSys.LoadPolicyMapping(r.Context(), objAPI, userOrGroup, IAMUserType(userType), isGroup); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
isGroup := mss.Get(peerRESTIsGroup) == "true"
|
||||
if err := globalIAMSys.LoadPolicyMapping(context.Background(), objAPI, userOrGroup, IAMUserType(userType), isGroup); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteServiceAccountHandler - deletes a service account on the server.
|
||||
func (s *peerRESTServer) DeleteServiceAccountHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) DeleteServiceAccountHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
accessKey := vars[peerRESTUser]
|
||||
accessKey := mss.Get(peerRESTUser)
|
||||
if accessKey == "" {
|
||||
s.writeErrorResponse(w, errors.New("service account name is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("service account name is missing"))
|
||||
}
|
||||
|
||||
if err := globalIAMSys.DeleteServiceAccount(r.Context(), accessKey, false); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err := globalIAMSys.DeleteServiceAccount(context.Background(), accessKey, false); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadServiceAccountHandler - reloads a service account on the server.
|
||||
func (s *peerRESTServer) LoadServiceAccountHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadServiceAccountHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
accessKey := vars[peerRESTUser]
|
||||
accessKey := mss.Get(peerRESTUser)
|
||||
if accessKey == "" {
|
||||
s.writeErrorResponse(w, errors.New("service account parameter is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("service account name is missing"))
|
||||
}
|
||||
|
||||
if err := globalIAMSys.LoadServiceAccount(r.Context(), accessKey); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err := globalIAMSys.LoadServiceAccount(context.Background(), accessKey); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeleteUserHandler - deletes a user on the server.
|
||||
func (s *peerRESTServer) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) DeleteUserHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
accessKey := vars[peerRESTUser]
|
||||
accessKey := mss.Get(peerRESTUser)
|
||||
if accessKey == "" {
|
||||
s.writeErrorResponse(w, errors.New("username is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("username is missing"))
|
||||
}
|
||||
|
||||
if err := globalIAMSys.DeleteUser(r.Context(), accessKey, false); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err := globalIAMSys.DeleteUser(context.Background(), accessKey, false); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadUserHandler - reloads a user on the server.
|
||||
func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadUserHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
accessKey := vars[peerRESTUser]
|
||||
accessKey := mss.Get(peerRESTUser)
|
||||
if accessKey == "" {
|
||||
s.writeErrorResponse(w, errors.New("username is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("username is missing"))
|
||||
}
|
||||
|
||||
temp, err := strconv.ParseBool(vars[peerRESTUserTemp])
|
||||
temp, err := strconv.ParseBool(mss.Get(peerRESTUserTemp))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
userType := regUser
|
||||
@@ -252,32 +213,31 @@ func (s *peerRESTServer) LoadUserHandler(w http.ResponseWriter, r *http.Request)
|
||||
userType = stsUser
|
||||
}
|
||||
|
||||
if err = globalIAMSys.LoadUser(r.Context(), objAPI, accessKey, userType); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err = globalIAMSys.LoadUser(context.Background(), objAPI, accessKey, userType); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// LoadGroupHandler - reloads group along with members list.
|
||||
func (s *peerRESTServer) LoadGroupHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadGroupHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
vars := mux.Vars(r)
|
||||
group := vars[peerRESTGroup]
|
||||
err := globalIAMSys.LoadGroup(r.Context(), objAPI, group)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
group := mss.Get(peerRESTGroup)
|
||||
if group == "" {
|
||||
return np, grid.NewRemoteErr(errors.New("group is missing"))
|
||||
}
|
||||
|
||||
err := globalIAMSys.LoadGroup(context.Background(), objAPI, group)
|
||||
if err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// StartProfilingHandler - Issues the start profiling command.
|
||||
@@ -564,18 +524,13 @@ func (s *peerRESTServer) GetSysErrorsHandler(w http.ResponseWriter, r *http.Requ
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(info))
|
||||
}
|
||||
|
||||
// DeleteBucketMetadataHandler - Delete in memory bucket metadata
|
||||
func (s *peerRESTServer) DeleteBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
var deleteBucketMetadataHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerDeleteBucketMetadata, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucketName := vars[peerRESTBucket]
|
||||
// DeleteBucketMetadataHandler - Delete in memory bucket metadata
|
||||
func (s *peerRESTServer) DeleteBucketMetadataHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
bucketName := mss.Get(peerRESTBucket)
|
||||
if bucketName == "" {
|
||||
s.writeErrorResponse(w, errors.New("Bucket name is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("Bucket name is missing"))
|
||||
}
|
||||
|
||||
globalReplicationStats.Delete(bucketName)
|
||||
@@ -587,24 +542,7 @@ func (s *peerRESTServer) DeleteBucketMetadataHandler(w http.ResponseWriter, r *h
|
||||
if localMetacacheMgr != nil {
|
||||
localMetacacheMgr.deleteBucketCache(bucketName)
|
||||
}
|
||||
}
|
||||
|
||||
// ReloadSiteReplicationConfigHandler - reloads site replication configuration from the disks
|
||||
func (s *peerRESTServer) ReloadSiteReplicationConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := newContext(r, w, "LoadSiteReplication")
|
||||
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(r.Context(), globalSiteReplicationSys.Init(ctx, objAPI))
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllBucketStatsHandler - fetches bucket replication stats for all buckets from this peer.
|
||||
@@ -662,30 +600,23 @@ func (s *peerRESTServer) GetSRMetricsHandler(w http.ResponseWriter, r *http.Requ
|
||||
logger.LogIf(r.Context(), msgp.Encode(w, &sm))
|
||||
}
|
||||
|
||||
// LoadBucketMetadataHandler - reloads in memory bucket metadata
|
||||
func (s *peerRESTServer) LoadBucketMetadataHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
var loadBucketMetadataHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadBucketMetadata, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
vars := mux.Vars(r)
|
||||
bucketName := vars[peerRESTBucket]
|
||||
// LoadBucketMetadataHandler - reloads in memory bucket metadata
|
||||
func (s *peerRESTServer) LoadBucketMetadataHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
bucketName := mss.Get(peerRESTBucket)
|
||||
if bucketName == "" {
|
||||
s.writeErrorResponse(w, errors.New("Bucket name is missing"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("Bucket name is missing"))
|
||||
}
|
||||
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
meta, err := loadBucketMetadata(r.Context(), objAPI, bucketName)
|
||||
meta, err := loadBucketMetadata(context.Background(), objAPI, bucketName)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
globalBucketMetadataSys.Set(bucketName, meta)
|
||||
@@ -697,6 +628,8 @@ func (s *peerRESTServer) LoadBucketMetadataHandler(w http.ResponseWriter, r *htt
|
||||
if meta.bucketTargetConfig != nil {
|
||||
globalBucketTargetSys.UpdateAllTargets(bucketName, meta.bucketTargetConfig)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) GetMetacacheListingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -800,31 +733,24 @@ func (s *peerRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
s.IsValid(w, r)
|
||||
}
|
||||
|
||||
var getLocalDiskIDsHandler = grid.NewSingleHandler[*grid.MSS, *LocalDiskIDs](grid.HandlerGetLocalDiskIDs, grid.NewMSS, func() *LocalDiskIDs {
|
||||
return &LocalDiskIDs{}
|
||||
})
|
||||
|
||||
// GetLocalDiskIDs - Return disk IDs of all the local disks.
|
||||
func (s *peerRESTServer) GetLocalDiskIDs(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := newContext(r, w, "GetLocalDiskIDs")
|
||||
|
||||
func (s *peerRESTServer) GetLocalDiskIDs(mss *grid.MSS) (*LocalDiskIDs, *grid.RemoteErr) {
|
||||
objLayer := newObjectLayerFn()
|
||||
|
||||
// Service not initialized yet
|
||||
if objLayer == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return nil, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
z, ok := objLayer.(*erasureServerPools)
|
||||
if !ok {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return nil, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
ids := getLocalDiskIDs(z)
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(ids))
|
||||
return &LocalDiskIDs{IDs: getLocalDiskIDs(z)}, nil
|
||||
}
|
||||
|
||||
// VerifyBinary - verifies the downloaded binary is in-tact
|
||||
@@ -976,25 +902,23 @@ func (s *peerRESTServer) SignalServiceHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
// Set an output capacity of 100 for listenHandler
|
||||
// There is another buffer that will buffer events.
|
||||
var listenHandler = grid.NewStream[*grid.URLValues, grid.NoPayload, *grid.Bytes](grid.HandlerListen,
|
||||
grid.NewURLValues, nil, grid.NewBytes).WithOutCapacity(100)
|
||||
|
||||
// ListenHandler sends http trace messages back to peer rest client
|
||||
func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
values := r.Form
|
||||
|
||||
func (s *peerRESTServer) ListenHandler(ctx context.Context, v *grid.URLValues, out chan<- *grid.Bytes) *grid.RemoteErr {
|
||||
values := v.Values()
|
||||
defer v.Recycle()
|
||||
var prefix string
|
||||
if len(values[peerRESTListenPrefix]) > 1 {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
return
|
||||
return grid.NewRemoteErrString("invalid request (peerRESTListenPrefix)")
|
||||
}
|
||||
|
||||
globalAPIConfig.getRequestsPoolCapacity()
|
||||
if len(values[peerRESTListenPrefix]) == 1 {
|
||||
if err := event.ValidateFilterRuleValue(values[peerRESTListenPrefix][0]); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
prefix = values[peerRESTListenPrefix][0]
|
||||
@@ -1002,14 +926,12 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var suffix string
|
||||
if len(values[peerRESTListenSuffix]) > 1 {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
return
|
||||
return grid.NewRemoteErrString("invalid request (peerRESTListenSuffix)")
|
||||
}
|
||||
|
||||
if len(values[peerRESTListenSuffix]) == 1 {
|
||||
if err := event.ValidateFilterRuleValue(values[peerRESTListenSuffix][0]); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
suffix = values[peerRESTListenSuffix][0]
|
||||
@@ -1022,8 +944,7 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
for _, ev := range values[peerRESTListenEvents] {
|
||||
eventName, err := event.ParseName(ev)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
mask.MergeMaskable(eventName)
|
||||
eventNames = append(eventNames, eventName)
|
||||
@@ -1031,13 +952,10 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rulesMap := event.NewRulesMap(eventNames, pattern, event.TargetID{ID: mustGetUUID()})
|
||||
|
||||
doneCh := r.Context().Done()
|
||||
|
||||
// Listen Publisher uses nonblocking publish and hence does not wait for slow subscribers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
ch := make(chan event.Event, globalAPIConfig.getRequestsPoolCapacity())
|
||||
|
||||
err := globalHTTPListen.Subscribe(mask, ch, doneCh, func(ev event.Event) bool {
|
||||
err := globalHTTPListen.Subscribe(mask, ch, ctx.Done(), func(ev event.Event) bool {
|
||||
if ev.S3.Bucket.Name != "" && values.Get(peerRESTListenBucket) != "" {
|
||||
if ev.S3.Bucket.Name != values.Get(peerRESTListenBucket) {
|
||||
return false
|
||||
@@ -1046,83 +964,56 @@ func (s *peerRESTServer) ListenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return rulesMap.MatchSimple(ev.EventName, ev.S3.Object.Key)
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||
defer keepAliveTicker.Stop()
|
||||
|
||||
enc := gob.NewEncoder(w)
|
||||
// Process until remote disconnects.
|
||||
// Blocks on upstream (out) congestion.
|
||||
// We have however a dynamic downstream buffer (ch).
|
||||
buf := bytes.NewBuffer(grid.GetByteBuffer())
|
||||
enc := json.NewEncoder(buf)
|
||||
tmpEvt := struct{ Records []event.Event }{[]event.Event{{}}}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
grid.PutByteBuffer(buf.Bytes())
|
||||
return nil
|
||||
case ev := <-ch:
|
||||
if err := enc.Encode(ev); err != nil {
|
||||
return
|
||||
buf.Reset()
|
||||
tmpEvt.Records[0] = ev
|
||||
if err := enc.Encode(tmpEvt); err != nil {
|
||||
logger.LogOnceIf(ctx, err, "event: Encode failed")
|
||||
continue
|
||||
}
|
||||
if len(ch) == 0 {
|
||||
// Flush if nothing is queued
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
case <-keepAliveTicker.C:
|
||||
if err := enc.Encode(&event.Event{}); err != nil {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
out <- grid.NewBytesWithCopyOf(buf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TraceHandler sends http trace messages back to peer rest client
|
||||
func (s *peerRESTServer) TraceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) TraceHandler(ctx context.Context, payload []byte, _ <-chan []byte, out chan<- []byte) *grid.RemoteErr {
|
||||
var traceOpts madmin.ServiceTraceOpts
|
||||
err := traceOpts.ParseParams(r)
|
||||
err := json.Unmarshal(payload, &traceOpts)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, errors.New("Invalid request"))
|
||||
return
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
// Trace Publisher uses nonblocking publish and hence does not wait for slow subscribers.
|
||||
// Use buffered channel to take care of burst sends or slow w.Write()
|
||||
ch := make(chan madmin.TraceInfo, 100000)
|
||||
err = globalTrace.Subscribe(traceOpts.TraceTypes(), ch, r.Context().Done(), func(entry madmin.TraceInfo) bool {
|
||||
err = globalTrace.SubscribeJSON(traceOpts.TraceTypes(), out, ctx.Done(), func(entry madmin.TraceInfo) bool {
|
||||
return shouldTrace(entry, traceOpts)
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
// Publish bootstrap events that have already occurred before client could subscribe.
|
||||
if traceOpts.TraceTypes().Contains(madmin.TraceBootstrap) {
|
||||
go globalBootstrapTracer.Publish(r.Context(), globalTrace)
|
||||
}
|
||||
|
||||
keepAliveTicker := time.NewTicker(500 * time.Millisecond)
|
||||
defer keepAliveTicker.Stop()
|
||||
|
||||
enc := gob.NewEncoder(w)
|
||||
for {
|
||||
select {
|
||||
case entry := <-ch:
|
||||
if err := enc.Encode(entry); err != nil {
|
||||
return
|
||||
}
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
case <-keepAliveTicker.C:
|
||||
if err := enc.Encode(&madmin.TraceInfo{}); err != nil {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
go globalBootstrapTracer.Publish(ctx, globalTrace)
|
||||
}
|
||||
// Wait for remote to cancel.
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) BackgroundHealStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -1141,92 +1032,101 @@ func (s *peerRESTServer) BackgroundHealStatusHandler(w http.ResponseWriter, r *h
|
||||
logger.LogIf(ctx, gob.NewEncoder(w).Encode(state))
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) ReloadPoolMetaHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
return
|
||||
}
|
||||
var reloadSiteReplicationConfigHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerReloadSiteReplicationConfig, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
// ReloadSiteReplicationConfigHandler - reloads site replication configuration from the disks
|
||||
func (s *peerRESTServer) ReloadSiteReplicationConfigHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
pools, ok := objAPI.(*erasureServerPools)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := pools.ReloadPoolMeta(r.Context()); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
logger.LogIf(context.Background(), globalSiteReplicationSys.Init(context.Background(), objAPI))
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) StopRebalanceHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
var reloadPoolMetaHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerReloadPoolMeta, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
func (s *peerRESTServer) ReloadPoolMetaHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
pools, ok := objAPI.(*erasureServerPools)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := pools.ReloadPoolMeta(context.Background()); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var stopRebalanceHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerStopRebalance, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
func (s *peerRESTServer) StopRebalanceHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
pools, ok := objAPI.(*erasureServerPools)
|
||||
if !ok {
|
||||
s.writeErrorResponse(w, errors.New("not a multiple pools setup"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("not a pooled setup"))
|
||||
}
|
||||
|
||||
pools.StopRebalance()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadRebalanceMetaHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
return
|
||||
}
|
||||
var loadRebalanceMetaHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadRebalanceMeta, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
func (s *peerRESTServer) LoadRebalanceMetaHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
s.writeErrorResponse(w, errServerNotInitialized)
|
||||
return
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
pools, ok := objAPI.(*erasureServerPools)
|
||||
if !ok {
|
||||
s.writeErrorResponse(w, errors.New("not a multiple pools setup"))
|
||||
return
|
||||
return np, grid.NewRemoteErr(errors.New("not a pooled setup"))
|
||||
}
|
||||
|
||||
startRebalanceStr := r.Form.Get(peerRESTStartRebalance)
|
||||
startRebalance, err := strconv.ParseBool(startRebalanceStr)
|
||||
startRebalance, err := strconv.ParseBool(mss.Get(peerRESTStartRebalance))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
if err := pools.loadRebalanceMeta(r.Context()); err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
if err := pools.loadRebalanceMeta(context.Background()); err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
if startRebalance {
|
||||
go pools.StartRebalance()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *peerRESTServer) LoadTransitionTierConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
s.writeErrorResponse(w, errors.New("invalid request"))
|
||||
return
|
||||
var loadTransitionTierConfigHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadTransitionTierConfig, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
func (s *peerRESTServer) LoadTransitionTierConfigHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI == nil {
|
||||
return np, grid.NewRemoteErr(errServerNotInitialized)
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := globalTierConfigMgr.Reload(context.Background(), newObjectLayerFn())
|
||||
if err != nil {
|
||||
logger.LogIf(context.Background(), fmt.Errorf("Failed to reload remote tier config %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ConsoleLogHandler sends console logs of this node back to peer rest client
|
||||
@@ -1521,8 +1421,91 @@ func (s *peerRESTServer) NetSpeedTestHandler(w http.ResponseWriter, r *http.Requ
|
||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(result))
|
||||
}
|
||||
|
||||
var healBucketHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerHealBucket, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
func (s *peerRESTServer) HealBucketHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
bucket := mss.Get(peerS3Bucket)
|
||||
if isMinioMetaBucket(bucket) {
|
||||
return np, grid.NewRemoteErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
bucketDeleted := mss.Get(peerS3BucketDeleted) == "true"
|
||||
_, err := healBucketLocal(context.Background(), bucket, madmin.HealOpts{
|
||||
Remove: bucketDeleted,
|
||||
})
|
||||
if err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return np, nil
|
||||
}
|
||||
|
||||
var headBucketHandler = grid.NewSingleHandler[*grid.MSS, *VolInfo](grid.HandlerHeadBucket, grid.NewMSS, func() *VolInfo { return &VolInfo{} })
|
||||
|
||||
// HeadBucketHandler implements peer BuckeInfo call, returns bucket create date.
|
||||
func (s *peerRESTServer) HeadBucketHandler(mss *grid.MSS) (info *VolInfo, nerr *grid.RemoteErr) {
|
||||
bucket := mss.Get(peerS3Bucket)
|
||||
if isMinioMetaBucket(bucket) {
|
||||
return info, grid.NewRemoteErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
bucketDeleted := mss.Get(peerS3BucketDeleted) == "true"
|
||||
|
||||
bucketInfo, err := getBucketInfoLocal(context.Background(), bucket, BucketOptions{
|
||||
Deleted: bucketDeleted,
|
||||
})
|
||||
if err != nil {
|
||||
return info, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return &VolInfo{
|
||||
Name: bucketInfo.Name,
|
||||
Created: bucketInfo.Created,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var deleteBucketHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerDeleteBucket, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
// DeleteBucketHandler implements peer delete bucket call.
|
||||
func (s *peerRESTServer) DeleteBucketHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
bucket := mss.Get(peerS3Bucket)
|
||||
if isMinioMetaBucket(bucket) {
|
||||
return np, grid.NewRemoteErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
forceDelete := mss.Get(peerS3BucketForceDelete) == "true"
|
||||
err := deleteBucketLocal(context.Background(), bucket, DeleteBucketOptions{
|
||||
Force: forceDelete,
|
||||
})
|
||||
if err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return np, nil
|
||||
}
|
||||
|
||||
var makeBucketHandler = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerMakeBucket, grid.NewMSS, grid.NewNoPayload)
|
||||
|
||||
// MakeBucketHandler implements peer create bucket call.
|
||||
func (s *peerRESTServer) MakeBucketHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) {
|
||||
bucket := mss.Get(peerS3Bucket)
|
||||
if isMinioMetaBucket(bucket) {
|
||||
return np, grid.NewRemoteErr(errInvalidArgument)
|
||||
}
|
||||
|
||||
forceCreate := mss.Get(peerS3BucketForceCreate) == "true"
|
||||
err := makeBucketLocal(context.Background(), bucket, MakeBucketOptions{
|
||||
ForceCreate: forceCreate,
|
||||
})
|
||||
if err != nil {
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
return np, nil
|
||||
}
|
||||
|
||||
// registerPeerRESTHandlers - register peer rest router.
|
||||
func registerPeerRESTHandlers(router *mux.Router) {
|
||||
func registerPeerRESTHandlers(router *mux.Router, gm *grid.Manager) {
|
||||
h := func(f http.HandlerFunc) http.HandlerFunc {
|
||||
return collectInternodeStats(httpTraceHdrs(f))
|
||||
}
|
||||
@@ -1545,43 +1528,55 @@ func registerPeerRESTHandlers(router *mux.Router) {
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodNetHwInfo).HandlerFunc(h(server.GetNetInfoHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodCPUInfo).HandlerFunc(h(server.GetCPUsHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetAllBucketStats).HandlerFunc(h(server.GetAllBucketStatsHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeleteBucketMetadata).HandlerFunc(h(server.DeleteBucketMetadataHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadBucketMetadata).HandlerFunc(h(server.LoadBucketMetadataHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetBucketStats).HandlerFunc(h(server.GetBucketStatsHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSignalService).HandlerFunc(h(server.SignalServiceHandler)).Queries(restQueries(peerRESTSignal)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodVerifyBinary).HandlerFunc(h(server.VerifyBinaryHandler)).Queries(restQueries(peerRESTURL, peerRESTSha256Sum, peerRESTReleaseInfo)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodCommitBinary).HandlerFunc(h(server.CommitBinaryHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeletePolicy).HandlerFunc(h(server.DeletePolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicy).HandlerFunc(h(server.LoadPolicyHandler)).Queries(restQueries(peerRESTPolicy)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadPolicyMapping).HandlerFunc(h(server.LoadPolicyMappingHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeleteUser).HandlerFunc(h(server.DeleteUserHandler)).Queries(restQueries(peerRESTUser)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDeleteServiceAccount).HandlerFunc(h(server.DeleteServiceAccountHandler)).Queries(restQueries(peerRESTUser)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadUser).HandlerFunc(h(server.LoadUserHandler)).Queries(restQueries(peerRESTUser, peerRESTUserTemp)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadServiceAccount).HandlerFunc(h(server.LoadServiceAccountHandler)).Queries(restQueries(peerRESTUser)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadGroup).HandlerFunc(h(server.LoadGroupHandler)).Queries(restQueries(peerRESTGroup)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetReplicationMRF).HandlerFunc(httpTraceHdrs(server.GetReplicationMRFHandler)).Queries(restQueries(peerRESTBucket)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetSRMetrics).HandlerFunc(h(server.GetSRMetricsHandler))
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodStartProfiling).HandlerFunc(h(server.StartProfilingHandler)).Queries(restQueries(peerRESTProfiler)...)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDownloadProfilingData).HandlerFunc(h(server.DownloadProfilingDataHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodTrace).HandlerFunc(server.TraceHandler)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodListen).HandlerFunc(h(server.ListenHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodBackgroundHealStatus).HandlerFunc(server.BackgroundHealStatusHandler)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLog).HandlerFunc(server.ConsoleLogHandler)
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetLocalDiskIDs).HandlerFunc(h(server.GetLocalDiskIDs))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetBandwidth).HandlerFunc(h(server.GetBandwidth))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetMetacacheListing).HandlerFunc(h(server.GetMetacacheListingHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodUpdateMetacacheListing).HandlerFunc(h(server.UpdateMetacacheListingHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetPeerMetrics).HandlerFunc(h(server.GetPeerMetrics))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetPeerBucketMetrics).HandlerFunc(h(server.GetPeerBucketMetrics))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadTransitionTierConfig).HandlerFunc(h(server.LoadTransitionTierConfigHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodSpeedTest).HandlerFunc(h(server.SpeedTestHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDriveSpeedTest).HandlerFunc(h(server.DriveSpeedTestHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodNetperf).HandlerFunc(h(server.NetSpeedTestHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodDevNull).HandlerFunc(h(server.DevNull))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodReloadSiteReplicationConfig).HandlerFunc(h(server.ReloadSiteReplicationConfigHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodReloadPoolMeta).HandlerFunc(h(server.ReloadPoolMetaHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodLoadRebalanceMeta).HandlerFunc(h(server.LoadRebalanceMetaHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodStopRebalance).HandlerFunc(h(server.StopRebalanceHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerRESTVersionPrefix + peerRESTMethodGetLastDayTierStats).HandlerFunc(h(server.GetLastDayTierStatsHandler))
|
||||
|
||||
logger.FatalIf(makeBucketHandler.Register(gm, server.MakeBucketHandler), "unable to register handler")
|
||||
logger.FatalIf(deleteBucketHandler.Register(gm, server.DeleteBucketHandler), "unable to register handler")
|
||||
logger.FatalIf(headBucketHandler.Register(gm, server.HeadBucketHandler), "unable to register handler")
|
||||
logger.FatalIf(healBucketHandler.Register(gm, server.HealBucketHandler), "unable to register handler")
|
||||
|
||||
logger.FatalIf(deletePolicyHandler.Register(gm, server.DeletePolicyHandler), "unable to register handler")
|
||||
logger.FatalIf(loadPolicyHandler.Register(gm, server.LoadPolicyHandler), "unable to register handler")
|
||||
logger.FatalIf(loadPolicyMappingHandler.Register(gm, server.LoadPolicyMappingHandler), "unable to register handler")
|
||||
logger.FatalIf(deleteUserHandler.Register(gm, server.DeleteUserHandler), "unable to register handler")
|
||||
logger.FatalIf(deleteSvcActHandler.Register(gm, server.DeleteServiceAccountHandler), "unable to register handler")
|
||||
logger.FatalIf(loadUserHandler.Register(gm, server.LoadUserHandler), "unable to register handler")
|
||||
logger.FatalIf(loadSvcActHandler.Register(gm, server.LoadServiceAccountHandler), "unable to register handler")
|
||||
logger.FatalIf(loadGroupHandler.Register(gm, server.LoadGroupHandler), "unable to register handler")
|
||||
|
||||
logger.FatalIf(loadTransitionTierConfigHandler.Register(gm, server.LoadTransitionTierConfigHandler), "unable to register handler")
|
||||
logger.FatalIf(reloadPoolMetaHandler.Register(gm, server.ReloadPoolMetaHandler), "unable to register handler")
|
||||
logger.FatalIf(loadRebalanceMetaHandler.Register(gm, server.LoadRebalanceMetaHandler), "unable to register handler")
|
||||
logger.FatalIf(stopRebalanceHandler.Register(gm, server.StopRebalanceHandler), "unable to register handler")
|
||||
logger.FatalIf(reloadSiteReplicationConfigHandler.Register(gm, server.ReloadSiteReplicationConfigHandler), "unable to register handler")
|
||||
logger.FatalIf(loadBucketMetadataHandler.Register(gm, server.LoadBucketMetadataHandler), "unable to register handler")
|
||||
logger.FatalIf(deleteBucketMetadataHandler.Register(gm, server.DeleteBucketMetadataHandler), "unable to register handler")
|
||||
logger.FatalIf(getLocalDiskIDsHandler.Register(gm, server.GetLocalDiskIDs), "unable to register handler")
|
||||
logger.FatalIf(listenHandler.RegisterNoInput(gm, server.ListenHandler), "unable to register handler")
|
||||
logger.FatalIf(gm.RegisterStreamingHandler(grid.HandlerTrace, grid.StreamHandler{
|
||||
Handle: server.TraceHandler,
|
||||
Subroute: "",
|
||||
OutCapacity: 100000,
|
||||
InCapacity: 0,
|
||||
}), "unable to register handler")
|
||||
}
|
||||
|
||||
+99
-50
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2022 MinIO, Inc.
|
||||
// Copyright (c) 2015-2023 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -21,14 +21,18 @@ import (
|
||||
"context"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
grid "github.com/minio/minio/internal/grid"
|
||||
xhttp "github.com/minio/minio/internal/http"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/minio/internal/rest"
|
||||
"github.com/minio/pkg/v2/sync/errgroup"
|
||||
"golang.org/x/exp/slices"
|
||||
@@ -49,12 +53,12 @@ type peerS3Client interface {
|
||||
}
|
||||
|
||||
type localPeerS3Client struct {
|
||||
host string
|
||||
node Node
|
||||
pools []int
|
||||
}
|
||||
|
||||
func (l *localPeerS3Client) GetHost() string {
|
||||
return l.host
|
||||
return l.node.Host
|
||||
}
|
||||
|
||||
func (l *localPeerS3Client) SetPools(p []int) {
|
||||
@@ -88,9 +92,13 @@ func (l localPeerS3Client) DeleteBucket(ctx context.Context, bucket string, opts
|
||||
|
||||
// client to talk to peer Nodes.
|
||||
type remotePeerS3Client struct {
|
||||
host string
|
||||
node Node
|
||||
pools []int
|
||||
restClient *rest.Client
|
||||
|
||||
// Function that returns the grid connection for this peer when initialized.
|
||||
// Will return nil if the grid connection is not initialized yet.
|
||||
gridConn func() *grid.Connection
|
||||
}
|
||||
|
||||
// Wrapper to restClient.Call to handle network errors, in case of network error the connection is marked disconnected
|
||||
@@ -126,7 +134,7 @@ type S3PeerSys struct {
|
||||
// NewS3PeerSys - creates new S3 peer calls.
|
||||
func NewS3PeerSys(endpoints EndpointServerPools) *S3PeerSys {
|
||||
return &S3PeerSys{
|
||||
peerClients: newPeerS3Clients(endpoints.GetNodes()),
|
||||
peerClients: newPeerS3Clients(endpoints),
|
||||
poolsCount: len(endpoints),
|
||||
}
|
||||
}
|
||||
@@ -358,37 +366,47 @@ func (client *remotePeerS3Client) ListBuckets(ctx context.Context, opts BucketOp
|
||||
}
|
||||
|
||||
func (client *remotePeerS3Client) HealBucket(ctx context.Context, bucket string, opts madmin.HealOpts) (madmin.HealResultItem, error) {
|
||||
v := url.Values{}
|
||||
v.Set(peerS3Bucket, bucket)
|
||||
v.Set(peerS3BucketDeleted, strconv.FormatBool(opts.Remove))
|
||||
|
||||
respBody, err := client.call(peerS3MethodHealBucket, v, nil, -1)
|
||||
if err != nil {
|
||||
return madmin.HealResultItem{}, err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return madmin.HealResultItem{}, nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
var res madmin.HealResultItem
|
||||
err = gob.NewDecoder(respBody).Decode(&res)
|
||||
mss := grid.NewMSSWith(map[string]string{
|
||||
peerS3Bucket: bucket,
|
||||
peerS3BucketDeleted: strconv.FormatBool(opts.Remove),
|
||||
})
|
||||
|
||||
return res, err
|
||||
_, err := healBucketHandler.Call(ctx, conn, mss)
|
||||
|
||||
// Initialize heal result info
|
||||
return madmin.HealResultItem{
|
||||
Type: madmin.HealItemBucket,
|
||||
Bucket: bucket,
|
||||
SetCount: -1, // explicitly set an invalid value -1, for bucket heal scenario
|
||||
}, toStorageErr(err)
|
||||
}
|
||||
|
||||
// GetBucketInfo returns bucket stat info from a peer
|
||||
func (client *remotePeerS3Client) GetBucketInfo(ctx context.Context, bucket string, opts BucketOptions) (BucketInfo, error) {
|
||||
v := url.Values{}
|
||||
v.Set(peerS3Bucket, bucket)
|
||||
v.Set(peerS3BucketDeleted, strconv.FormatBool(opts.Deleted))
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return BucketInfo{}, nil
|
||||
}
|
||||
|
||||
respBody, err := client.call(peerS3MethodGetBucketInfo, v, nil, -1)
|
||||
mss := grid.NewMSSWith(map[string]string{
|
||||
peerS3Bucket: bucket,
|
||||
peerS3BucketDeleted: strconv.FormatBool(opts.Deleted),
|
||||
})
|
||||
|
||||
volInfo, err := headBucketHandler.Call(ctx, conn, mss)
|
||||
if err != nil {
|
||||
return BucketInfo{}, err
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
var bucketInfo BucketInfo
|
||||
err = gob.NewDecoder(respBody).Decode(&bucketInfo)
|
||||
return bucketInfo, err
|
||||
return BucketInfo{
|
||||
Name: volInfo.Name,
|
||||
Created: volInfo.Created,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MakeBucket creates bucket across all peers
|
||||
@@ -421,17 +439,18 @@ func (sys *S3PeerSys) MakeBucket(ctx context.Context, bucket string, opts MakeBu
|
||||
|
||||
// MakeBucket creates a bucket on a peer
|
||||
func (client *remotePeerS3Client) MakeBucket(ctx context.Context, bucket string, opts MakeBucketOptions) error {
|
||||
v := url.Values{}
|
||||
v.Set(peerS3Bucket, bucket)
|
||||
v.Set(peerS3BucketForceCreate, strconv.FormatBool(opts.ForceCreate))
|
||||
|
||||
respBody, err := client.call(peerS3MethodMakeBucket, v, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
return nil
|
||||
mss := grid.NewMSSWith(map[string]string{
|
||||
peerS3Bucket: bucket,
|
||||
peerS3BucketForceCreate: strconv.FormatBool(opts.ForceCreate),
|
||||
})
|
||||
|
||||
_, err := makeBucketHandler.Call(ctx, conn, mss)
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
// DeleteBucket deletes bucket across all peers
|
||||
@@ -466,21 +485,22 @@ func (sys *S3PeerSys) DeleteBucket(ctx context.Context, bucket string, opts Dele
|
||||
|
||||
// DeleteBucket deletes bucket on a peer
|
||||
func (client *remotePeerS3Client) DeleteBucket(ctx context.Context, bucket string, opts DeleteBucketOptions) error {
|
||||
v := url.Values{}
|
||||
v.Set(peerS3Bucket, bucket)
|
||||
v.Set(peerS3BucketForceDelete, strconv.FormatBool(opts.Force))
|
||||
|
||||
respBody, err := client.call(peerS3MethodDeleteBucket, v, nil, -1)
|
||||
if err != nil {
|
||||
return err
|
||||
conn := client.gridConn()
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
defer xhttp.DrainBody(respBody)
|
||||
|
||||
return nil
|
||||
mss := grid.NewMSSWith(map[string]string{
|
||||
peerS3Bucket: bucket,
|
||||
peerS3BucketForceDelete: strconv.FormatBool(opts.Force),
|
||||
})
|
||||
|
||||
_, err := deleteBucketHandler.Call(ctx, conn, mss)
|
||||
return toStorageErr(err)
|
||||
}
|
||||
|
||||
func (client remotePeerS3Client) GetHost() string {
|
||||
return client.host
|
||||
return client.node.Host
|
||||
}
|
||||
|
||||
func (client remotePeerS3Client) GetPools() []int {
|
||||
@@ -493,21 +513,23 @@ func (client *remotePeerS3Client) SetPools(p []int) {
|
||||
}
|
||||
|
||||
// newPeerS3Clients creates new peer clients.
|
||||
func newPeerS3Clients(nodes []Node) (peers []peerS3Client) {
|
||||
func newPeerS3Clients(endpoints EndpointServerPools) (peers []peerS3Client) {
|
||||
nodes := endpoints.GetNodes()
|
||||
peers = make([]peerS3Client, len(nodes))
|
||||
for i, node := range nodes {
|
||||
if node.IsLocal {
|
||||
peers[i] = &localPeerS3Client{host: node.Host}
|
||||
peers[i] = &localPeerS3Client{node: node}
|
||||
} else {
|
||||
peers[i] = newPeerS3Client(node.Host)
|
||||
peers[i] = newPeerS3Client(node)
|
||||
}
|
||||
peers[i].SetPools(node.Pools)
|
||||
}
|
||||
return
|
||||
|
||||
return peers
|
||||
}
|
||||
|
||||
// Returns a peer S3 client.
|
||||
func newPeerS3Client(peer string) peerS3Client {
|
||||
func newPeerS3Client(node Node) peerS3Client {
|
||||
scheme := "http"
|
||||
if globalIsTLS {
|
||||
scheme = "https"
|
||||
@@ -515,7 +537,7 @@ func newPeerS3Client(peer string) peerS3Client {
|
||||
|
||||
serverURL := &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: peer,
|
||||
Host: node.Host,
|
||||
Path: peerS3Path,
|
||||
}
|
||||
|
||||
@@ -533,5 +555,32 @@ func newPeerS3Client(peer string) peerS3Client {
|
||||
return !isNetworkError(err)
|
||||
}
|
||||
|
||||
return &remotePeerS3Client{host: peer, restClient: restClient}
|
||||
var gridConn atomic.Pointer[grid.Connection]
|
||||
|
||||
return &remotePeerS3Client{
|
||||
node: node, restClient: restClient,
|
||||
gridConn: func() *grid.Connection {
|
||||
// Lazy initialization of grid connection.
|
||||
// When we create this peer client, the grid connection is likely not yet initialized.
|
||||
if node.GridHost == "" {
|
||||
logger.LogOnceIf(context.Background(), fmt.Errorf("gridHost is empty for peer %s", node.Host), node.Host+":gridHost")
|
||||
return nil
|
||||
}
|
||||
gc := gridConn.Load()
|
||||
if gc != nil {
|
||||
return gc
|
||||
}
|
||||
gm := globalGrid.Load()
|
||||
if gm == nil {
|
||||
return nil
|
||||
}
|
||||
gc = gm.Connection(node.GridHost)
|
||||
if gc == nil {
|
||||
logger.LogOnceIf(context.Background(), fmt.Errorf("gridHost %s not found for peer %s", node.GridHost, node.Host), node.Host+":gridHost")
|
||||
return nil
|
||||
}
|
||||
gridConn.Store(gc)
|
||||
return gc
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,90 +385,6 @@ func (s *peerS3Server) ListBucketsHandler(w http.ResponseWriter, r *http.Request
|
||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(buckets))
|
||||
}
|
||||
|
||||
func (s *peerS3Server) HealBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
|
||||
|
||||
bucket := r.Form.Get(peerS3Bucket)
|
||||
if isMinioMetaBucket(bucket) {
|
||||
s.writeErrorResponse(w, errInvalidArgument)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := healBucketLocal(r.Context(), bucket, madmin.HealOpts{
|
||||
Remove: bucketDeleted,
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(res))
|
||||
}
|
||||
|
||||
// GetBucketInfoHandler implements peer BuckeInfo call, returns bucket create date.
|
||||
func (s *peerS3Server) GetBucketInfoHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
bucket := r.Form.Get(peerS3Bucket)
|
||||
bucketDeleted := r.Form.Get(peerS3BucketDeleted) == "true"
|
||||
bucketInfo, err := getBucketInfoLocal(r.Context(), bucket, BucketOptions{
|
||||
Deleted: bucketDeleted,
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.LogIf(r.Context(), gob.NewEncoder(w).Encode(bucketInfo))
|
||||
}
|
||||
|
||||
// DeleteBucketHandler implements peer delete bucket call.
|
||||
func (s *peerS3Server) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
bucket := r.Form.Get(peerS3Bucket)
|
||||
if isMinioMetaBucket(bucket) {
|
||||
s.writeErrorResponse(w, errInvalidArgument)
|
||||
return
|
||||
}
|
||||
|
||||
forceDelete := r.Form.Get(peerS3BucketForceDelete) == "true"
|
||||
|
||||
err := deleteBucketLocal(r.Context(), bucket, DeleteBucketOptions{
|
||||
Force: forceDelete,
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MakeBucketHandler implements peer create bucket call.
|
||||
func (s *peerS3Server) MakeBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
bucket := r.Form.Get(peerS3Bucket)
|
||||
forceCreate := r.Form.Get(peerS3BucketForceCreate) == "true"
|
||||
|
||||
err := makeBucketLocal(r.Context(), bucket, MakeBucketOptions{
|
||||
ForceCreate: forceCreate,
|
||||
})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// registerPeerS3Handlers - register peer s3 router.
|
||||
func registerPeerS3Handlers(router *mux.Router) {
|
||||
server := &peerS3Server{}
|
||||
@@ -479,9 +395,5 @@ func registerPeerS3Handlers(router *mux.Router) {
|
||||
}
|
||||
|
||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodHealth).HandlerFunc(h(server.HealthHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodMakeBucket).HandlerFunc(h(server.MakeBucketHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodDeleteBucket).HandlerFunc(h(server.DeleteBucketHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodGetBucketInfo).HandlerFunc(h(server.GetBucketInfoHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodListBuckets).HandlerFunc(h(server.ListBucketsHandler))
|
||||
subrouter.Methods(http.MethodPost).Path(peerS3VersionPrefix + peerS3MethodHealBucket).HandlerFunc(h(server.HealBucketHandler))
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func testPostPolicyReservedBucketExploit(obj ObjectLayer, instanceType string, d
|
||||
z := obj.(*erasureServerPools)
|
||||
xl := z.serverPools[0].sets[0]
|
||||
erasureDisks := xl.getDisks()
|
||||
parts, errs := readAllFileInfo(ctx, erasureDisks, bucketName, objectName+"/upload.txt", "", false, false)
|
||||
parts, errs := readAllFileInfo(ctx, erasureDisks, "", bucketName, objectName+"/upload.txt", "", false, false)
|
||||
for i := range parts {
|
||||
if errs[i] == nil {
|
||||
if parts[i].Name == objectName+"/upload.txt" {
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ func registerDistErasureRouters(router *mux.Router, endpointServerPools Endpoint
|
||||
registerStorageRESTHandlers(router, endpointServerPools, globalGrid.Load())
|
||||
|
||||
// Register peer REST router only if its a distributed setup.
|
||||
registerPeerRESTHandlers(router)
|
||||
registerPeerRESTHandlers(router, globalGrid.Load())
|
||||
|
||||
// Register peer S3 router only if its a distributed setup.
|
||||
registerPeerS3Handlers(router)
|
||||
|
||||
+9
-2
@@ -135,6 +135,13 @@ var ServerFlags = []cli.Flag{
|
||||
Value: 10 * time.Minute,
|
||||
EnvVar: "MINIO_DNS_CACHE_TTL",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "max-idle-conns-per-host",
|
||||
Usage: "set a custom max idle connections per host value",
|
||||
Hidden: true,
|
||||
Value: 2048,
|
||||
EnvVar: "MINIO_MAX_IDLE_CONNS_PER_HOST",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "ftp",
|
||||
Usage: "enable and configure an FTP(Secure) server",
|
||||
@@ -330,9 +337,9 @@ func serverHandleCmdArgs(ctxt serverCtxt) {
|
||||
// allow transport to be HTTP/1.1 for proxying.
|
||||
globalProxyTransport = NewCustomHTTPProxyTransport()()
|
||||
globalProxyEndpoints = GetProxyEndpoints(globalEndpoints)
|
||||
globalInternodeTransport = NewInternodeHTTPTransport()()
|
||||
globalInternodeTransport = NewInternodeHTTPTransport(ctxt.MaxIdleConnsPerHost)()
|
||||
globalRemoteTargetTransport = NewRemoteTargetHTTPTransport(false)()
|
||||
|
||||
globalHealthChkTransport = NewHTTPTransport()
|
||||
globalForwarder = handlers.NewForwarder(&handlers.Forwarder{
|
||||
PassHost: true,
|
||||
RoundTripper: NewHTTPTransportWithTimeout(1 * time.Hour),
|
||||
|
||||
@@ -158,8 +158,8 @@ func checkKeyValid(r *http.Request, accessKey string) (auth.Credentials, bool, A
|
||||
// Check if the access key is part of users credentials.
|
||||
u, ok := globalIAMSys.GetUser(r.Context(), accessKey)
|
||||
if !ok {
|
||||
// Credentials will be invalid but and disabled
|
||||
// return a different error in such a scenario.
|
||||
// Credentials could be valid but disabled - return a different
|
||||
// error in such a scenario.
|
||||
if u.Credentials.Status == auth.AccountOff {
|
||||
return cred, false, ErrAccessKeyDisabled
|
||||
}
|
||||
|
||||
@@ -393,6 +393,7 @@ type DeleteVersionHandlerParams struct {
|
||||
type MetadataHandlerParams struct {
|
||||
DiskID string `msg:"id"`
|
||||
Volume string `msg:"v"`
|
||||
OrigVolume string `msg:"ov"`
|
||||
FilePath string `msg:"fp"`
|
||||
UpdateOpts UpdateMetadataOpts `msg:"uo"`
|
||||
FI FileInfo `msg:"fi"`
|
||||
@@ -450,3 +451,8 @@ type ReadAllHandlerParams struct {
|
||||
type RenameDataResp struct {
|
||||
Signature uint64 `msg:"sig"`
|
||||
}
|
||||
|
||||
// LocalDiskIDs - GetLocalIDs response.
|
||||
type LocalDiskIDs struct {
|
||||
IDs []string
|
||||
}
|
||||
|
||||
@@ -2912,6 +2912,148 @@ func (z *FilesInfo) Msgsize() (s int) {
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *LocalDiskIDs) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, err = dc.ReadMapHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, err = dc.ReadMapKeyPtr()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "IDs":
|
||||
var zb0002 uint32
|
||||
zb0002, err = dc.ReadArrayHeader()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "IDs")
|
||||
return
|
||||
}
|
||||
if cap(z.IDs) >= int(zb0002) {
|
||||
z.IDs = (z.IDs)[:zb0002]
|
||||
} else {
|
||||
z.IDs = make([]string, zb0002)
|
||||
}
|
||||
for za0001 := range z.IDs {
|
||||
z.IDs[za0001], err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "IDs", za0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *LocalDiskIDs) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 1
|
||||
// write "IDs"
|
||||
err = en.Append(0x81, 0xa3, 0x49, 0x44, 0x73)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteArrayHeader(uint32(len(z.IDs)))
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "IDs")
|
||||
return
|
||||
}
|
||||
for za0001 := range z.IDs {
|
||||
err = en.WriteString(z.IDs[za0001])
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "IDs", za0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *LocalDiskIDs) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 1
|
||||
// string "IDs"
|
||||
o = append(o, 0x81, 0xa3, 0x49, 0x44, 0x73)
|
||||
o = msgp.AppendArrayHeader(o, uint32(len(z.IDs)))
|
||||
for za0001 := range z.IDs {
|
||||
o = msgp.AppendString(o, z.IDs[za0001])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (z *LocalDiskIDs) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var field []byte
|
||||
_ = field
|
||||
var zb0001 uint32
|
||||
zb0001, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
for zb0001 > 0 {
|
||||
zb0001--
|
||||
field, bts, err = msgp.ReadMapKeyZC(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
switch msgp.UnsafeString(field) {
|
||||
case "IDs":
|
||||
var zb0002 uint32
|
||||
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "IDs")
|
||||
return
|
||||
}
|
||||
if cap(z.IDs) >= int(zb0002) {
|
||||
z.IDs = (z.IDs)[:zb0002]
|
||||
} else {
|
||||
z.IDs = make([]string, zb0002)
|
||||
}
|
||||
for za0001 := range z.IDs {
|
||||
z.IDs[za0001], bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "IDs", za0001)
|
||||
return
|
||||
}
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *LocalDiskIDs) Msgsize() (s int) {
|
||||
s = 1 + 4 + msgp.ArrayHeaderSize
|
||||
for za0001 := range z.IDs {
|
||||
s += msgp.StringPrefixSize + len(z.IDs[za0001])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DecodeMsg implements msgp.Decodable
|
||||
func (z *MetadataHandlerParams) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
var field []byte
|
||||
@@ -2942,6 +3084,12 @@ func (z *MetadataHandlerParams) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Volume")
|
||||
return
|
||||
}
|
||||
case "ov":
|
||||
z.OrigVolume, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "OrigVolume")
|
||||
return
|
||||
}
|
||||
case "fp":
|
||||
z.FilePath, err = dc.ReadString()
|
||||
if err != nil {
|
||||
@@ -2996,9 +3144,9 @@ func (z *MetadataHandlerParams) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *MetadataHandlerParams) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 5
|
||||
// map header, size 6
|
||||
// write "id"
|
||||
err = en.Append(0x85, 0xa2, 0x69, 0x64)
|
||||
err = en.Append(0x86, 0xa2, 0x69, 0x64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -3017,6 +3165,16 @@ func (z *MetadataHandlerParams) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Volume")
|
||||
return
|
||||
}
|
||||
// write "ov"
|
||||
err = en.Append(0xa2, 0x6f, 0x76)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.OrigVolume)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "OrigVolume")
|
||||
return
|
||||
}
|
||||
// write "fp"
|
||||
err = en.Append(0xa2, 0x66, 0x70)
|
||||
if err != nil {
|
||||
@@ -3059,13 +3217,16 @@ func (z *MetadataHandlerParams) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *MetadataHandlerParams) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 5
|
||||
// map header, size 6
|
||||
// string "id"
|
||||
o = append(o, 0x85, 0xa2, 0x69, 0x64)
|
||||
o = append(o, 0x86, 0xa2, 0x69, 0x64)
|
||||
o = msgp.AppendString(o, z.DiskID)
|
||||
// string "v"
|
||||
o = append(o, 0xa1, 0x76)
|
||||
o = msgp.AppendString(o, z.Volume)
|
||||
// string "ov"
|
||||
o = append(o, 0xa2, 0x6f, 0x76)
|
||||
o = msgp.AppendString(o, z.OrigVolume)
|
||||
// string "fp"
|
||||
o = append(o, 0xa2, 0x66, 0x70)
|
||||
o = msgp.AppendString(o, z.FilePath)
|
||||
@@ -3115,6 +3276,12 @@ func (z *MetadataHandlerParams) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Volume")
|
||||
return
|
||||
}
|
||||
case "ov":
|
||||
z.OrigVolume, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "OrigVolume")
|
||||
return
|
||||
}
|
||||
case "fp":
|
||||
z.FilePath, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
@@ -3170,7 +3337,7 @@ func (z *MetadataHandlerParams) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *MetadataHandlerParams) Msgsize() (s int) {
|
||||
s = 1 + 3 + msgp.StringPrefixSize + len(z.DiskID) + 2 + msgp.StringPrefixSize + len(z.Volume) + 3 + msgp.StringPrefixSize + len(z.FilePath) + 3 + 1 + 3 + msgp.BoolSize + 3 + z.FI.Msgsize()
|
||||
s = 1 + 3 + msgp.StringPrefixSize + len(z.DiskID) + 2 + msgp.StringPrefixSize + len(z.Volume) + 3 + msgp.StringPrefixSize + len(z.OrigVolume) + 3 + msgp.StringPrefixSize + len(z.FilePath) + 3 + 1 + 3 + msgp.BoolSize + 3 + z.FI.Msgsize()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1252,6 +1252,119 @@ func BenchmarkDecodeFilesInfo(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalLocalDiskIDs(t *testing.T) {
|
||||
v := LocalDiskIDs{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
left, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left)
|
||||
}
|
||||
|
||||
left, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(left) > 0 {
|
||||
t.Errorf("%d bytes left over after Skip(): %q", len(left), left)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMarshalMsgLocalDiskIDs(b *testing.B) {
|
||||
v := LocalDiskIDs{}
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.MarshalMsg(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAppendMsgLocalDiskIDs(b *testing.B) {
|
||||
v := LocalDiskIDs{}
|
||||
bts := make([]byte, 0, v.Msgsize())
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
bts, _ = v.MarshalMsg(bts[0:0])
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkUnmarshalLocalDiskIDs(b *testing.B) {
|
||||
v := LocalDiskIDs{}
|
||||
bts, _ := v.MarshalMsg(nil)
|
||||
b.ReportAllocs()
|
||||
b.SetBytes(int64(len(bts)))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := v.UnmarshalMsg(bts)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeDecodeLocalDiskIDs(t *testing.T) {
|
||||
v := LocalDiskIDs{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
|
||||
m := v.Msgsize()
|
||||
if buf.Len() > m {
|
||||
t.Log("WARNING: TestEncodeDecodeLocalDiskIDs Msgsize() is inaccurate")
|
||||
}
|
||||
|
||||
vn := LocalDiskIDs{}
|
||||
err := msgp.Decode(&buf, &vn)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
msgp.Encode(&buf, &v)
|
||||
err = msgp.NewReader(&buf).Skip()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEncodeLocalDiskIDs(b *testing.B) {
|
||||
v := LocalDiskIDs{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
en := msgp.NewWriter(msgp.Nowhere)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
v.EncodeMsg(en)
|
||||
}
|
||||
en.Flush()
|
||||
}
|
||||
|
||||
func BenchmarkDecodeLocalDiskIDs(b *testing.B) {
|
||||
v := LocalDiskIDs{}
|
||||
var buf bytes.Buffer
|
||||
msgp.Encode(&buf, &v)
|
||||
b.SetBytes(int64(buf.Len()))
|
||||
rd := msgp.NewEndlessReader(buf.Bytes(), b)
|
||||
dc := msgp.NewReader(rd)
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
err := v.DecodeMsg(dc)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalUnmarshalMetadataHandlerParams(t *testing.T) {
|
||||
v := MetadataHandlerParams{}
|
||||
bts, err := v.MarshalMsg(nil)
|
||||
|
||||
@@ -82,17 +82,17 @@ type StorageAPI interface {
|
||||
// Metadata operations
|
||||
DeleteVersion(ctx context.Context, volume, path string, fi FileInfo, forceDelMarker bool, opts DeleteOptions) error
|
||||
DeleteVersions(ctx context.Context, volume string, versions []FileInfoVersions, opts DeleteOptions) []error
|
||||
WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error
|
||||
WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) error
|
||||
UpdateMetadata(ctx context.Context, volume, path string, fi FileInfo, opts UpdateMetadataOpts) error
|
||||
ReadVersion(ctx context.Context, volume, path, versionID string, opts ReadOptions) (FileInfo, error)
|
||||
ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (FileInfo, error)
|
||||
ReadXL(ctx context.Context, volume, path string, readData bool) (RawFileInfo, error)
|
||||
RenameData(ctx context.Context, srcVolume, srcPath string, fi FileInfo, dstVolume, dstPath string, opts RenameOptions) (uint64, error)
|
||||
|
||||
// File operations.
|
||||
ListDir(ctx context.Context, volume, dirPath string, count int) ([]string, error)
|
||||
ListDir(ctx context.Context, origvolume, volume, dirPath string, count int) ([]string, error)
|
||||
ReadFile(ctx context.Context, volume string, path string, offset int64, buf []byte, verifier *BitrotVerifier) (n int64, err error)
|
||||
AppendFile(ctx context.Context, volume string, path string, buf []byte) (err error)
|
||||
CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error
|
||||
CreateFile(ctx context.Context, origvolume, olume, path string, size int64, reader io.Reader) error
|
||||
ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error)
|
||||
RenameFile(ctx context.Context, srcVolume, srcPath, dstVolume, dstPath string) error
|
||||
CheckParts(ctx context.Context, volume string, path string, fi FileInfo) error
|
||||
@@ -198,7 +198,7 @@ func (p *unrecognizedDisk) DeleteVol(ctx context.Context, volume string, forceDe
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
func (p *unrecognizedDisk) ListDir(ctx context.Context, volume, dirPath string, count int) ([]string, error) {
|
||||
func (p *unrecognizedDisk) ListDir(ctx context.Context, origvolume, volume, dirPath string, count int) ([]string, error) {
|
||||
return nil, errDiskNotFound
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ func (p *unrecognizedDisk) AppendFile(ctx context.Context, volume string, path s
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
func (p *unrecognizedDisk) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error {
|
||||
func (p *unrecognizedDisk) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) error {
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
@@ -260,11 +260,11 @@ func (p *unrecognizedDisk) UpdateMetadata(ctx context.Context, volume, path stri
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
func (p *unrecognizedDisk) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) (err error) {
|
||||
func (p *unrecognizedDisk) WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) (err error) {
|
||||
return errDiskNotFound
|
||||
}
|
||||
|
||||
func (p *unrecognizedDisk) ReadVersion(ctx context.Context, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
func (p *unrecognizedDisk) ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
return fi, errDiskNotFound
|
||||
}
|
||||
|
||||
|
||||
+25
-14
@@ -148,6 +148,8 @@ func toStorageErr(err error) error {
|
||||
return errDiskOngoingReq
|
||||
case grid.ErrUnknownHandler.Error():
|
||||
return errInconsistentDisk
|
||||
case grid.ErrDisconnected.Error():
|
||||
return errDiskNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -346,6 +348,7 @@ func (client *storageRESTClient) StatVol(ctx context.Context, volume string) (vo
|
||||
return vol, toStorageErr(err)
|
||||
}
|
||||
vol = *v
|
||||
// Performs shallow copy, so we can reuse.
|
||||
storageStatVolHandler.PutResponse(v)
|
||||
return vol, nil
|
||||
}
|
||||
@@ -366,11 +369,13 @@ func (client *storageRESTClient) AppendFile(ctx context.Context, volume string,
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) error {
|
||||
func (client *storageRESTClient) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) error {
|
||||
values := make(url.Values)
|
||||
values.Set(storageRESTVolume, volume)
|
||||
values.Set(storageRESTFilePath, path)
|
||||
values.Set(storageRESTLength, strconv.Itoa(int(size)))
|
||||
values.Set(storageRESTOrigVolume, origvolume)
|
||||
|
||||
respBody, err := client.call(ctx, storageRESTMethodCreateFile, values, io.NopCloser(reader), size)
|
||||
defer xhttp.DrainBody(respBody)
|
||||
if err != nil {
|
||||
@@ -380,12 +385,13 @@ func (client *storageRESTClient) CreateFile(ctx context.Context, volume, path st
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) error {
|
||||
func (client *storageRESTClient) WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) error {
|
||||
_, err := storageWriteMetadataHandler.Call(ctx, client.gridConn, &MetadataHandlerParams{
|
||||
DiskID: client.diskID,
|
||||
Volume: volume,
|
||||
FilePath: path,
|
||||
FI: fi,
|
||||
DiskID: client.diskID,
|
||||
OrigVolume: origvolume,
|
||||
Volume: volume,
|
||||
FilePath: path,
|
||||
FI: fi,
|
||||
})
|
||||
return toStorageErr(err)
|
||||
}
|
||||
@@ -452,6 +458,7 @@ func (client *storageRESTClient) RenameData(ctx context.Context, srcVolume, srcP
|
||||
if err != nil {
|
||||
return 0, toStorageErr(err)
|
||||
}
|
||||
defer storageRenameDataHandler.PutResponse(resp)
|
||||
return resp.Signature, nil
|
||||
}
|
||||
|
||||
@@ -478,16 +485,17 @@ func readMsgpReaderPoolPut(r *msgp.Reader) {
|
||||
}
|
||||
}
|
||||
|
||||
func (client *storageRESTClient) ReadVersion(ctx context.Context, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
func (client *storageRESTClient) ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
// Use websocket when not reading data.
|
||||
if !opts.ReadData {
|
||||
resp, err := storageReadVersionHandler.Call(ctx, client.gridConn, grid.NewMSSWith(map[string]string{
|
||||
storageRESTDiskID: client.diskID,
|
||||
storageRESTVolume: volume,
|
||||
storageRESTFilePath: path,
|
||||
storageRESTVersionID: versionID,
|
||||
storageRESTReadData: strconv.FormatBool(opts.ReadData),
|
||||
storageRESTHealing: strconv.FormatBool(opts.Healing),
|
||||
storageRESTDiskID: client.diskID,
|
||||
storageRESTOrigVolume: origvolume,
|
||||
storageRESTVolume: volume,
|
||||
storageRESTFilePath: path,
|
||||
storageRESTVersionID: versionID,
|
||||
storageRESTReadData: strconv.FormatBool(opts.ReadData),
|
||||
storageRESTHealing: strconv.FormatBool(opts.Healing),
|
||||
}))
|
||||
if err != nil {
|
||||
return fi, toStorageErr(err)
|
||||
@@ -496,6 +504,7 @@ func (client *storageRESTClient) ReadVersion(ctx context.Context, volume, path,
|
||||
}
|
||||
|
||||
values := make(url.Values)
|
||||
values.Set(storageRESTOrigVolume, origvolume)
|
||||
values.Set(storageRESTVolume, volume)
|
||||
values.Set(storageRESTFilePath, path)
|
||||
values.Set(storageRESTVersionID, versionID)
|
||||
@@ -612,11 +621,13 @@ func (client *storageRESTClient) ReadFile(ctx context.Context, volume string, pa
|
||||
}
|
||||
|
||||
// ListDir - lists a directory.
|
||||
func (client *storageRESTClient) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) {
|
||||
func (client *storageRESTClient) ListDir(ctx context.Context, origvolume, volume, dirPath string, count int) (entries []string, err error) {
|
||||
values := make(url.Values)
|
||||
values.Set(storageRESTVolume, volume)
|
||||
values.Set(storageRESTDirPath, dirPath)
|
||||
values.Set(storageRESTCount, strconv.Itoa(count))
|
||||
values.Set(storageRESTOrigVolume, origvolume)
|
||||
|
||||
respBody, err := client.call(ctx, storageRESTMethodListDir, values, nil, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -20,7 +20,9 @@ package cmd
|
||||
//go:generate msgp -file $GOFILE -unexported
|
||||
|
||||
const (
|
||||
storageRESTVersion = "v55" // ReadAll, RenameFile migrate to websockets
|
||||
// Added orig-volume support for CreateFile, WriteMetadata, ReadVersion, ListDir
|
||||
// this is needed for performance optimization on bucket checks.
|
||||
storageRESTVersion = "v56"
|
||||
storageRESTVersionPrefix = SlashSeparator + storageRESTVersion
|
||||
storageRESTPrefix = minioReservedBucketPath + "/storage"
|
||||
)
|
||||
@@ -43,7 +45,6 @@ const (
|
||||
storageRESTMethodStatInfoFile = "/statfile"
|
||||
storageRESTMethodReadMultiple = "/readmultiple"
|
||||
storageRESTMethodCleanAbandoned = "/cleanabandoned"
|
||||
storageRESTMethodLinkXL = "/linkxl"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -69,6 +70,7 @@ const (
|
||||
storageRESTGlob = "glob"
|
||||
storageRESTMetrics = "metrics"
|
||||
storageRESTDriveQuorum = "drive-quorum"
|
||||
storageRESTOrigVolume = "orig-volume"
|
||||
)
|
||||
|
||||
type nsScannerOptions struct {
|
||||
|
||||
@@ -201,7 +201,7 @@ func (s *storageRESTServer) HealthHandler(w http.ResponseWriter, r *http.Request
|
||||
// DiskInfo types.
|
||||
// DiskInfo.Metrics elements are shared, so we cannot reuse.
|
||||
var storageDiskInfoHandler = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} },
|
||||
func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse()
|
||||
func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true)
|
||||
|
||||
// DiskInfoHandler - returns disk info.
|
||||
func (s *storageRESTServer) DiskInfoHandler(opts *DiskInfoOptions) (*DiskInfo, *grid.RemoteErr) {
|
||||
@@ -330,8 +330,10 @@ func (s *storageRESTServer) CreateFileHandler(w http.ResponseWriter, r *http.Req
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
|
||||
volume := r.Form.Get(storageRESTVolume)
|
||||
filePath := r.Form.Get(storageRESTFilePath)
|
||||
origvolume := r.Form.Get(storageRESTOrigVolume)
|
||||
|
||||
fileSizeStr := r.Form.Get(storageRESTLength)
|
||||
fileSize, err := strconv.Atoi(fileSizeStr)
|
||||
@@ -341,7 +343,7 @@ func (s *storageRESTServer) CreateFileHandler(w http.ResponseWriter, r *http.Req
|
||||
}
|
||||
|
||||
done, body := keepHTTPReqResponseAlive(w, r)
|
||||
done(s.getStorage().CreateFile(r.Context(), volume, filePath, int64(fileSize), body))
|
||||
done(s.getStorage().CreateFile(r.Context(), origvolume, volume, filePath, int64(fileSize), body))
|
||||
}
|
||||
|
||||
var storageDeleteVersionHandler = grid.NewSingleHandler[*DeleteVersionHandlerParams, grid.NoPayload](grid.HandlerDeleteVersion, func() *DeleteVersionHandlerParams {
|
||||
@@ -371,6 +373,7 @@ func (s *storageRESTServer) ReadVersionHandlerWS(params *grid.MSS) (*FileInfo, *
|
||||
if !s.checkID(params.Get(storageRESTDiskID)) {
|
||||
return nil, grid.NewRemoteErr(errDiskNotFound)
|
||||
}
|
||||
origvolume := params.Get(storageRESTOrigVolume)
|
||||
volume := params.Get(storageRESTVolume)
|
||||
filePath := params.Get(storageRESTFilePath)
|
||||
versionID := params.Get(storageRESTVersionID)
|
||||
@@ -384,7 +387,7 @@ func (s *storageRESTServer) ReadVersionHandlerWS(params *grid.MSS) (*FileInfo, *
|
||||
return nil, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
fi, err := s.getStorage().ReadVersion(context.Background(), volume, filePath, versionID, ReadOptions{ReadData: readData, Healing: healing})
|
||||
fi, err := s.getStorage().ReadVersion(context.Background(), origvolume, volume, filePath, versionID, ReadOptions{ReadData: readData, Healing: healing})
|
||||
if err != nil {
|
||||
return nil, grid.NewRemoteErr(err)
|
||||
}
|
||||
@@ -396,6 +399,7 @@ func (s *storageRESTServer) ReadVersionHandler(w http.ResponseWriter, r *http.Re
|
||||
if !s.IsValid(w, r) {
|
||||
return
|
||||
}
|
||||
origvolume := r.Form.Get(storageRESTOrigVolume)
|
||||
volume := r.Form.Get(storageRESTVolume)
|
||||
filePath := r.Form.Get(storageRESTFilePath)
|
||||
versionID := r.Form.Get(storageRESTVersionID)
|
||||
@@ -409,7 +413,7 @@ func (s *storageRESTServer) ReadVersionHandler(w http.ResponseWriter, r *http.Re
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
fi, err := s.getStorage().ReadVersion(r.Context(), volume, filePath, versionID, ReadOptions{ReadData: readData, Healing: healing})
|
||||
fi, err := s.getStorage().ReadVersion(r.Context(), origvolume, volume, filePath, versionID, ReadOptions{ReadData: readData, Healing: healing})
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
@@ -427,10 +431,12 @@ func (s *storageRESTServer) WriteMetadataHandler(p *MetadataHandlerParams) (np g
|
||||
if !s.checkID(p.DiskID) {
|
||||
return grid.NewNPErr(errDiskNotFound)
|
||||
}
|
||||
|
||||
volume := p.Volume
|
||||
filePath := p.FilePath
|
||||
origvolume := p.OrigVolume
|
||||
|
||||
err := s.getStorage().WriteMetadata(context.Background(), volume, filePath, p.FI)
|
||||
err := s.getStorage().WriteMetadata(context.Background(), origvolume, volume, filePath, p.FI)
|
||||
return np, grid.NewRemoteErr(err)
|
||||
}
|
||||
|
||||
@@ -489,7 +495,7 @@ func (s *storageRESTServer) CheckPartsHandler(p *CheckPartsHandlerParams) (grid.
|
||||
|
||||
var storageReadAllHandler = grid.NewSingleHandler[*ReadAllHandlerParams, *grid.Bytes](grid.HandlerReadAll, func() *ReadAllHandlerParams {
|
||||
return &ReadAllHandlerParams{}
|
||||
}, grid.NewBytes)
|
||||
}, grid.NewBytes).AllowCallRequestPool(true)
|
||||
|
||||
// ReadAllHandler - read all the contents of a file.
|
||||
func (s *storageRESTServer) ReadAllHandler(p *ReadAllHandlerParams) (*grid.Bytes, *grid.RemoteErr) {
|
||||
@@ -650,13 +656,14 @@ func (s *storageRESTServer) ListDirHandler(w http.ResponseWriter, r *http.Reques
|
||||
}
|
||||
volume := r.Form.Get(storageRESTVolume)
|
||||
dirPath := r.Form.Get(storageRESTDirPath)
|
||||
origvolume := r.Form.Get(storageRESTOrigVolume)
|
||||
count, err := strconv.Atoi(r.Form.Get(storageRESTCount))
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := s.getStorage().ListDir(r.Context(), volume, dirPath, count)
|
||||
entries, err := s.getStorage().ListDir(r.Context(), origvolume, volume, dirPath, count)
|
||||
if err != nil {
|
||||
s.writeErrorResponse(w, err)
|
||||
return
|
||||
@@ -666,7 +673,7 @@ func (s *storageRESTServer) ListDirHandler(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
var storageDeleteFileHandler = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams {
|
||||
return &DeleteFileHandlerParams{}
|
||||
}, grid.NewNoPayload)
|
||||
}, grid.NewNoPayload).AllowCallRequestPool(true)
|
||||
|
||||
// DeleteFileHandler - delete a file.
|
||||
func (s *storageRESTServer) DeleteFileHandler(p *DeleteFileHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
|
||||
@@ -744,7 +751,7 @@ func (s *storageRESTServer) RenameDataHandler(p *RenameDataHandlerParams) (*Rena
|
||||
|
||||
var storageRenameFileHandler = grid.NewSingleHandler[*RenameFileHandlerParams, grid.NoPayload](grid.HandlerRenameFile, func() *RenameFileHandlerParams {
|
||||
return &RenameFileHandlerParams{}
|
||||
}, grid.NewNoPayload)
|
||||
}, grid.NewNoPayload).AllowCallRequestPool(true)
|
||||
|
||||
// RenameFileHandler - rename a file from source to destination
|
||||
func (s *storageRESTServer) RenameFileHandler(p *RenameFileHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
|
||||
|
||||
@@ -97,7 +97,7 @@ func testStorageAPIListDir(t *testing.T, storage StorageAPI) {
|
||||
}
|
||||
|
||||
for i, testCase := range testCases {
|
||||
result, err := storage.ListDir(context.Background(), testCase.volumeName, testCase.prefix, -1)
|
||||
result, err := storage.ListDir(context.Background(), "", testCase.volumeName, testCase.prefix, -1)
|
||||
expectErr := (err != nil)
|
||||
|
||||
if expectErr != testCase.expectErr {
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ func writeSTSErrorResponse(ctx context.Context, w http.ResponseWriter, errCode S
|
||||
}
|
||||
switch errCode {
|
||||
case ErrSTSInternalError, ErrSTSUpstreamError:
|
||||
logger.LogIf(ctx, err, logger.Minio)
|
||||
logger.LogIf(ctx, err, logger.ErrorKind)
|
||||
}
|
||||
encodedErrorResponse := encodeResponse(stsErrorResponse)
|
||||
writeResponse(w, stsErr.HTTPStatusCode, encodedErrorResponse, mimeXML)
|
||||
|
||||
@@ -909,14 +909,6 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccounts(c *check) {
|
||||
// 3. Check S3 access
|
||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||
|
||||
// 4. Check that svc account can restrict the policy, and that the
|
||||
// session policy can be updated.
|
||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 4. Check that service account's secret key and account status can be
|
||||
// updated.
|
||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 5. Check that service account can be deleted.
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
@@ -1101,14 +1093,6 @@ func (s *TestSuiteIAM) TestLDAPSTSServiceAccountsWithGroups(c *check) {
|
||||
// 3. Check S3 access
|
||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||
|
||||
// 4. Check that svc account can restrict the policy, and that the
|
||||
// session policy can be updated.
|
||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 4. Check that service account's secret key and account status can be
|
||||
// updated.
|
||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 5. Check that service account can be deleted.
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
@@ -1358,14 +1342,6 @@ func (s *TestSuiteIAM) TestOpenIDServiceAcc(c *check) {
|
||||
// 3. Check S3 access
|
||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||
|
||||
// 4. Check that svc account can restrict the policy, and that the
|
||||
// session policy can be updated.
|
||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 4. Check that service account's secret key and account status can be
|
||||
// updated.
|
||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 5. Check that service account can be deleted.
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
@@ -1658,14 +1634,6 @@ func (s *TestSuiteIAM) TestOpenIDServiceAccWithRolePolicy(c *check) {
|
||||
// 3. Check S3 access
|
||||
c.assertSvcAccS3Access(ctx, s, cr, bucket)
|
||||
|
||||
// 4. Check that svc account can restrict the policy, and that the
|
||||
// session policy can be updated.
|
||||
c.assertSvcAccSessionPolicyUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 4. Check that service account's secret key and account status can be
|
||||
// updated.
|
||||
c.assertSvcAccSecretKeyAndStatusUpdate(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
|
||||
// 5. Check that service account can be deleted.
|
||||
c.assertSvcAccDeletion(ctx, s, userAdmClient, value.AccessKeyID, bucket)
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ func TestMain(m *testing.M) {
|
||||
|
||||
// Disable printing console messages during tests.
|
||||
color.Output = io.Discard
|
||||
// Minimum is error logs for testing
|
||||
logger.MinimumLogLevel = logger.ErrorLvl
|
||||
// Disable Error logging in testing.
|
||||
logger.DisableErrorLog = true
|
||||
|
||||
// Uncomment the following line to see trace logs during unit tests.
|
||||
// logger.AddTarget(console.New())
|
||||
@@ -111,7 +111,7 @@ func TestMain(m *testing.M) {
|
||||
// Initialize globalConsoleSys system
|
||||
globalConsoleSys = NewConsoleLogger(context.Background())
|
||||
|
||||
globalInternodeTransport = NewInternodeHTTPTransport()()
|
||||
globalInternodeTransport = NewInternodeHTTPTransport(0)()
|
||||
|
||||
initHelp()
|
||||
|
||||
|
||||
+2
-2
@@ -587,7 +587,7 @@ func GetDefaultConnSettings() xhttp.ConnSettings {
|
||||
|
||||
// NewInternodeHTTPTransport returns a transport for internode MinIO
|
||||
// connections.
|
||||
func NewInternodeHTTPTransport() func() http.RoundTripper {
|
||||
func NewInternodeHTTPTransport(maxIdleConnsPerHost int) func() http.RoundTripper {
|
||||
lookupHost := globalDNSCache.LookupHost
|
||||
if IsKubernetes() || IsDocker() {
|
||||
lookupHost = nil
|
||||
@@ -601,7 +601,7 @@ func NewInternodeHTTPTransport() func() http.RoundTripper {
|
||||
CurvePreferences: fips.TLSCurveIDs(),
|
||||
EnableHTTP2: false,
|
||||
TCPOptions: globalTCPOptions,
|
||||
}.NewInternodeHTTPTransport()
|
||||
}.NewInternodeHTTPTransport(maxIdleConnsPerHost)
|
||||
}
|
||||
|
||||
// NewCustomHTTPProxyTransport is used only for proxied requests, specifically
|
||||
|
||||
@@ -117,6 +117,15 @@ const (
|
||||
capacityXMLObject = ".system-d26a9498-cb7c-4a87-a44a-8ae204f5ba6c/capacity.xml"
|
||||
)
|
||||
|
||||
func isVeeamSOSAPIObject(object string) bool {
|
||||
switch object {
|
||||
case systemXMLObject, capacityXMLObject:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func veeamSOSAPIHeadObject(ctx context.Context, bucket, object string, opts ObjectOptions) (ObjectInfo, error) {
|
||||
gr, err := veeamSOSAPIGetObject(ctx, bucket, object, nil, opts)
|
||||
if gr != nil {
|
||||
|
||||
+30
-183
@@ -31,10 +31,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/config"
|
||||
xioutil "github.com/minio/minio/internal/ioutil"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
"github.com/minio/pkg/v2/env"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=storageMetric -trimprefix=storageMetric $GOFILE
|
||||
@@ -89,14 +87,7 @@ type xlStorageDiskIDCheck struct {
|
||||
diskID string
|
||||
storage *xlStorage
|
||||
health *diskHealthTracker
|
||||
|
||||
// driveStartChecking is a threshold above which we will start to check
|
||||
// the state of disks, generally this value is less than driveMaxConcurrent
|
||||
driveStartChecking int
|
||||
|
||||
// driveMaxConcurrent represents maximum number of running concurrent
|
||||
// operations for local and (incoming) remote disk operations.
|
||||
driveMaxConcurrent int
|
||||
healthCheck bool
|
||||
|
||||
metricsCache timedValue
|
||||
diskCtx context.Context
|
||||
@@ -127,8 +118,6 @@ func (p *xlStorageDiskIDCheck) getMetrics() DiskMetrics {
|
||||
}
|
||||
|
||||
// Do not need this value to be cached.
|
||||
diskMetric.TotalTokens = uint32(p.driveMaxConcurrent)
|
||||
diskMetric.TotalWaiting = uint32(p.health.waiting.Load())
|
||||
diskMetric.TotalErrorsTimeout = p.totalErrsTimeout.Load()
|
||||
diskMetric.TotalErrorsAvailability = p.totalErrsAvailability.Load()
|
||||
|
||||
@@ -189,57 +178,20 @@ func (e *lockedLastMinuteLatency) total() AccElem {
|
||||
return e.lastMinuteLatency.getTotal()
|
||||
}
|
||||
|
||||
var maxConcurrentOnce sync.Once
|
||||
|
||||
func newXLStorageDiskIDCheck(storage *xlStorage, healthCheck bool) *xlStorageDiskIDCheck {
|
||||
// driveMaxConcurrent represents maximum number of running concurrent
|
||||
// operations for local and (incoming) remote disk operations.
|
||||
//
|
||||
// this value is a placeholder it is overridden via ENV for custom settings
|
||||
// or this default value is used to pick the correct value HDDs v/s NVMe's
|
||||
driveMaxConcurrent := -1
|
||||
maxConcurrentOnce.Do(func() {
|
||||
s := env.Get("_MINIO_DRIVE_MAX_CONCURRENT", "")
|
||||
if s == "" {
|
||||
s = env.Get("_MINIO_DISK_MAX_CONCURRENT", "")
|
||||
}
|
||||
if s != "" {
|
||||
driveMaxConcurrent, _ = strconv.Atoi(s)
|
||||
}
|
||||
})
|
||||
|
||||
if driveMaxConcurrent <= 0 {
|
||||
driveMaxConcurrent = 512
|
||||
if storage.rotational {
|
||||
driveMaxConcurrent = int(storage.nrRequests) / 2
|
||||
if driveMaxConcurrent < 32 {
|
||||
driveMaxConcurrent = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
driveStartChecking := 16 + driveMaxConcurrent/8
|
||||
if driveStartChecking > driveMaxConcurrent {
|
||||
driveStartChecking = driveMaxConcurrent
|
||||
}
|
||||
|
||||
xl := xlStorageDiskIDCheck{
|
||||
storage: storage,
|
||||
health: newDiskHealthTracker(driveMaxConcurrent),
|
||||
driveMaxConcurrent: driveMaxConcurrent,
|
||||
driveStartChecking: driveStartChecking,
|
||||
}
|
||||
|
||||
if driveQuorum {
|
||||
xl.totalWrites.Store(xl.storage.getWriteAttribute())
|
||||
xl.totalDeletes.Store(xl.storage.getDeleteAttribute())
|
||||
storage: storage,
|
||||
health: newDiskHealthTracker(),
|
||||
healthCheck: healthCheck && globalDriveMonitoring,
|
||||
}
|
||||
|
||||
xl.totalWrites.Store(xl.storage.getWriteAttribute())
|
||||
xl.totalDeletes.Store(xl.storage.getDeleteAttribute())
|
||||
xl.diskCtx, xl.diskCancel = context.WithCancel(context.TODO())
|
||||
for i := range xl.apiLatencies[:] {
|
||||
xl.apiLatencies[i] = &lockedLastMinuteLatency{}
|
||||
}
|
||||
if healthCheck && diskActiveMonitoring {
|
||||
if xl.healthCheck {
|
||||
go xl.monitorDiskWritable(xl.diskCtx)
|
||||
}
|
||||
return &xl
|
||||
@@ -347,11 +299,8 @@ func (p *xlStorageDiskIDCheck) DiskInfo(ctx context.Context, opts DiskInfoOption
|
||||
defer si(&err)
|
||||
|
||||
if opts.NoOp {
|
||||
if driveQuorum {
|
||||
info.Metrics.TotalWrites = p.totalWrites.Load()
|
||||
info.Metrics.TotalDeletes = p.totalDeletes.Load()
|
||||
}
|
||||
info.Metrics.TotalTokens = uint32(p.driveMaxConcurrent)
|
||||
info.Metrics.TotalWrites = p.totalWrites.Load()
|
||||
info.Metrics.TotalDeletes = p.totalDeletes.Load()
|
||||
info.Metrics.TotalWaiting = uint32(p.health.waiting.Load())
|
||||
info.Metrics.TotalErrorsTimeout = p.totalErrsTimeout.Load()
|
||||
info.Metrics.TotalErrorsAvailability = p.totalErrsAvailability.Load()
|
||||
@@ -362,11 +311,8 @@ func (p *xlStorageDiskIDCheck) DiskInfo(ctx context.Context, opts DiskInfoOption
|
||||
if opts.Metrics {
|
||||
info.Metrics = p.getMetrics()
|
||||
}
|
||||
if driveQuorum {
|
||||
info.Metrics.TotalWrites = p.totalWrites.Load()
|
||||
info.Metrics.TotalDeletes = p.totalDeletes.Load()
|
||||
}
|
||||
info.Metrics.TotalTokens = uint32(p.driveMaxConcurrent)
|
||||
info.Metrics.TotalWrites = p.totalWrites.Load()
|
||||
info.Metrics.TotalDeletes = p.totalDeletes.Load()
|
||||
info.Metrics.TotalWaiting = uint32(p.health.waiting.Load())
|
||||
info.Metrics.TotalErrorsTimeout = p.totalErrsTimeout.Load()
|
||||
info.Metrics.TotalErrorsAvailability = p.totalErrsAvailability.Load()
|
||||
@@ -445,14 +391,14 @@ func (p *xlStorageDiskIDCheck) DeleteVol(ctx context.Context, volume string, for
|
||||
return w.Run(func() error { return p.storage.DeleteVol(ctx, volume, forceDelete) })
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ListDir(ctx context.Context, volume, dirPath string, count int) (s []string, err error) {
|
||||
func (p *xlStorageDiskIDCheck) ListDir(ctx context.Context, origvolume, volume, dirPath string, count int) (s []string, err error) {
|
||||
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricListDir, volume, dirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer done(&err)
|
||||
|
||||
return p.storage.ListDir(ctx, volume, dirPath, count)
|
||||
return p.storage.ListDir(ctx, origvolume, volume, dirPath, count)
|
||||
}
|
||||
|
||||
// Legacy API - does not have any deadlines
|
||||
@@ -482,14 +428,14 @@ func (p *xlStorageDiskIDCheck) AppendFile(ctx context.Context, volume string, pa
|
||||
})
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) CreateFile(ctx context.Context, volume, path string, size int64, reader io.Reader) (err error) {
|
||||
func (p *xlStorageDiskIDCheck) CreateFile(ctx context.Context, origvolume, volume, path string, size int64, reader io.Reader) (err error) {
|
||||
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricCreateFile, volume, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer done(&err)
|
||||
|
||||
return p.storage.CreateFile(ctx, volume, path, size, io.NopCloser(reader))
|
||||
return p.storage.CreateFile(ctx, origvolume, volume, path, size, io.NopCloser(reader))
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadFileStream(ctx context.Context, volume, path string, offset, length int64) (io.ReadCloser, error) {
|
||||
@@ -521,7 +467,7 @@ func (p *xlStorageDiskIDCheck) RenameData(ctx context.Context, srcVolume, srcPat
|
||||
return 0, err
|
||||
}
|
||||
defer func() {
|
||||
if driveQuorum && err == nil && !skipAccessChecks(dstVolume) {
|
||||
if err == nil && !skipAccessChecks(dstVolume) {
|
||||
p.storage.setWriteAttribute(p.totalWrites.Add(1))
|
||||
}
|
||||
done(&err)
|
||||
@@ -571,7 +517,7 @@ func (p *xlStorageDiskIDCheck) DeleteVersions(ctx context.Context, volume string
|
||||
return errs
|
||||
}
|
||||
defer func() {
|
||||
if driveQuorum && !skipAccessChecks(volume) {
|
||||
if !skipAccessChecks(volume) {
|
||||
var permanentDeletes uint64
|
||||
var deleteMarkers uint64
|
||||
|
||||
@@ -638,7 +584,7 @@ func (p *xlStorageDiskIDCheck) DeleteVersion(ctx context.Context, volume, path s
|
||||
defer func() {
|
||||
defer done(&err)
|
||||
|
||||
if driveQuorum && err == nil && !skipAccessChecks(volume) {
|
||||
if err == nil && !skipAccessChecks(volume) {
|
||||
if opts.UndoWrite {
|
||||
p.storage.setWriteAttribute(p.totalWrites.Add(^uint64(0)))
|
||||
return
|
||||
@@ -669,7 +615,7 @@ func (p *xlStorageDiskIDCheck) UpdateMetadata(ctx context.Context, volume, path
|
||||
return w.Run(func() error { return p.storage.UpdateMetadata(ctx, volume, path, fi, opts) })
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) (err error) {
|
||||
func (p *xlStorageDiskIDCheck) WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) (err error) {
|
||||
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricWriteMetadata, volume, path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -677,10 +623,10 @@ func (p *xlStorageDiskIDCheck) WriteMetadata(ctx context.Context, volume, path s
|
||||
defer done(&err)
|
||||
|
||||
w := xioutil.NewDeadlineWorker(globalDriveConfig.GetMaxTimeout())
|
||||
return w.Run(func() error { return p.storage.WriteMetadata(ctx, volume, path, fi) })
|
||||
return w.Run(func() error { return p.storage.WriteMetadata(ctx, origvolume, volume, path, fi) })
|
||||
}
|
||||
|
||||
func (p *xlStorageDiskIDCheck) ReadVersion(ctx context.Context, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
func (p *xlStorageDiskIDCheck) ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
ctx, done, err := p.TrackDiskHealth(ctx, storageMetricReadVersion, volume, path)
|
||||
if err != nil {
|
||||
return fi, err
|
||||
@@ -688,7 +634,7 @@ func (p *xlStorageDiskIDCheck) ReadVersion(ctx context.Context, volume, path, ve
|
||||
defer done(&err)
|
||||
|
||||
return xioutil.WithDeadline[FileInfo](ctx, globalDriveConfig.GetMaxTimeout(), func(ctx context.Context) (result FileInfo, err error) {
|
||||
return p.storage.ReadVersion(ctx, volume, path, versionID, opts)
|
||||
return p.storage.ReadVersion(ctx, origvolume, volume, path, versionID, opts)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -828,19 +774,6 @@ const (
|
||||
diskHealthFaulty
|
||||
)
|
||||
|
||||
// diskActiveMonitoring indicates if we have enabled "active" disk monitoring
|
||||
var diskActiveMonitoring = true
|
||||
|
||||
// Indicates if users want to enable drive_quorum feature
|
||||
var driveQuorum bool
|
||||
|
||||
func init() {
|
||||
diskActiveMonitoring = (env.Get("_MINIO_DRIVE_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn) ||
|
||||
(env.Get("_MINIO_DISK_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn)
|
||||
|
||||
driveQuorum = env.Get("_MINIO_DRIVE_QUORUM", config.EnableOff) == config.EnableOn
|
||||
}
|
||||
|
||||
type diskHealthTracker struct {
|
||||
// atomic time of last success
|
||||
lastSuccess int64
|
||||
@@ -851,24 +784,17 @@ type diskHealthTracker struct {
|
||||
// Atomic status of disk.
|
||||
status atomic.Int32
|
||||
|
||||
// Atomic number of requests waiting for a token.
|
||||
// Atomic number indicates if a disk is hung
|
||||
waiting atomic.Int32
|
||||
|
||||
// Concurrency tokens.
|
||||
tokens chan struct{}
|
||||
}
|
||||
|
||||
// newDiskHealthTracker creates a new disk health tracker.
|
||||
func newDiskHealthTracker(driveMaxConcurrent int) *diskHealthTracker {
|
||||
func newDiskHealthTracker() *diskHealthTracker {
|
||||
d := diskHealthTracker{
|
||||
lastSuccess: time.Now().UnixNano(),
|
||||
lastStarted: time.Now().UnixNano(),
|
||||
tokens: make(chan struct{}, driveMaxConcurrent),
|
||||
}
|
||||
d.status.Store(diskHealthOK)
|
||||
for i := 0; i < driveMaxConcurrent; i++ {
|
||||
d.tokens <- struct{}{}
|
||||
}
|
||||
return &d
|
||||
}
|
||||
|
||||
@@ -910,9 +836,8 @@ func (p *xlStorageDiskIDCheck) TrackDiskHealth(ctx context.Context, s storageMet
|
||||
return ctx, done, ctx.Err()
|
||||
}
|
||||
|
||||
// Return early if disk is faulty already.
|
||||
if err := p.checkHealth(ctx); err != nil {
|
||||
return ctx, done, err
|
||||
if p.health.status.Load() != diskHealthOK {
|
||||
return ctx, done, errFaultyDisk
|
||||
}
|
||||
|
||||
// Verify if the disk is not stale
|
||||
@@ -928,27 +853,19 @@ func (p *xlStorageDiskIDCheck) TrackDiskHealth(ctx context.Context, s storageMet
|
||||
return ctx, done, nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if contextCanceled(ctx) {
|
||||
return ctx, done, ctx.Err()
|
||||
case <-p.health.tokens:
|
||||
// Fast path, got token.
|
||||
default:
|
||||
// We ran out of tokens, check health before blocking.
|
||||
err = p.waitForToken(ctx)
|
||||
if err != nil {
|
||||
return ctx, done, err
|
||||
}
|
||||
}
|
||||
// We only progress here if we got a token.
|
||||
|
||||
atomic.StoreInt64(&p.health.lastStarted, time.Now().UnixNano())
|
||||
p.health.waiting.Add(1)
|
||||
|
||||
ctx = context.WithValue(ctx, healthDiskCtxKey{}, &healthDiskCtxValue{lastSuccess: &p.health.lastSuccess})
|
||||
si := p.updateStorageMetrics(s, paths...)
|
||||
var once sync.Once
|
||||
return ctx, func(errp *error) {
|
||||
p.health.waiting.Add(-1)
|
||||
once.Do(func() {
|
||||
p.health.tokens <- struct{}{}
|
||||
if errp != nil {
|
||||
err := *errp
|
||||
if err == nil || errors.Is(err, io.EOF) {
|
||||
@@ -960,71 +877,6 @@ func (p *xlStorageDiskIDCheck) TrackDiskHealth(ctx context.Context, s storageMet
|
||||
}, nil
|
||||
}
|
||||
|
||||
// waitForToken will wait for a token, while periodically
|
||||
// checking the disk status.
|
||||
// If nil is returned a token was picked up.
|
||||
func (p *xlStorageDiskIDCheck) waitForToken(ctx context.Context) (err error) {
|
||||
p.health.waiting.Add(1)
|
||||
defer p.health.waiting.Add(-1)
|
||||
|
||||
// Avoid stampeding herd...
|
||||
ticker := time.NewTicker(5*time.Second + time.Duration(rand.Int63n(int64(5*time.Second))))
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
err = p.checkHealth(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// Ticker expired, check health again.
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-p.health.tokens:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkHealth should only be called when tokens have run out.
|
||||
// This will check if disk should be taken offline.
|
||||
func (p *xlStorageDiskIDCheck) checkHealth(ctx context.Context) (err error) {
|
||||
if p.health.status.Load() == diskHealthFaulty {
|
||||
return errFaultyDisk
|
||||
}
|
||||
// Check if there are tokens.
|
||||
if p.driveMaxConcurrent-len(p.health.tokens) < p.driveStartChecking {
|
||||
return nil
|
||||
}
|
||||
|
||||
const maxTimeSinceLastSuccess = 30 * time.Second
|
||||
const minTimeSinceLastOpStarted = 15 * time.Second
|
||||
|
||||
// To avoid stampeding herd (100s of simultaneous starting requests)
|
||||
// there must be a delay between the last started request and now
|
||||
// for the last lastSuccess to be useful.
|
||||
t := time.Since(time.Unix(0, atomic.LoadInt64(&p.health.lastStarted)))
|
||||
if t < minTimeSinceLastOpStarted {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If also more than 15 seconds since last success, take disk offline.
|
||||
t = time.Since(time.Unix(0, atomic.LoadInt64(&p.health.lastSuccess)))
|
||||
if t > maxTimeSinceLastSuccess {
|
||||
if p.health.status.CompareAndSwap(diskHealthOK, diskHealthFaulty) {
|
||||
logger.LogAlwaysIf(ctx, fmt.Errorf("node(%s): taking drive %s offline, time since last response %v", globalLocalNodeName, p.storage.String(), t.Round(time.Millisecond)))
|
||||
p.health.waiting.Add(1)
|
||||
go p.monitorDiskStatus(0, mustGetUUID())
|
||||
}
|
||||
return errFaultyDisk
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make sure we do not write O_DIRECT aligned I/O because WrIteAll() ends
|
||||
// up using O_DIRECT codepath which internally utilizes p.health.tokens
|
||||
// we need to avoid using incoming I/O tokens as part of the healthcheck
|
||||
// monitoring I/O.
|
||||
var toWrite = []byte{2048: 42}
|
||||
|
||||
// monitorDiskStatus should be called once when a drive has been marked offline.
|
||||
@@ -1038,11 +890,6 @@ func (p *xlStorageDiskIDCheck) monitorDiskStatus(spent time.Duration, fn string)
|
||||
return
|
||||
}
|
||||
|
||||
if len(p.health.tokens) == 0 {
|
||||
// Queue is still full, no need to check.
|
||||
continue
|
||||
}
|
||||
|
||||
err := p.storage.WriteAll(context.Background(), minioMetaTmpBucket, fn, toWrite)
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -1638,9 +1638,9 @@ func (x *xlMetaV2) AddVersion(fi FileInfo) error {
|
||||
if len(k) > len(ReservedMetadataPrefixLower) && strings.EqualFold(k[:len(ReservedMetadataPrefixLower)], ReservedMetadataPrefixLower) {
|
||||
// Skip tierFVID, tierFVMarker keys; it's used
|
||||
// only for creating free-version.
|
||||
// Skip xMinIOHealing, it's used only in RenameData
|
||||
// Also skip xMinIOHealing, xMinIODataMov as used only in RenameData
|
||||
switch k {
|
||||
case tierFVIDKey, tierFVMarkerKey, xMinIOHealing:
|
||||
case tierFVIDKey, tierFVMarkerKey, xMinIOHealing, xMinIODataMov:
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+65
-27
@@ -1016,11 +1016,23 @@ func (s *xlStorage) DeleteVol(ctx context.Context, volume string, forceDelete bo
|
||||
|
||||
// ListDir - return all the entries at the given directory path.
|
||||
// If an entry is a directory it will be returned with a trailing SlashSeparator.
|
||||
func (s *xlStorage) ListDir(ctx context.Context, volume, dirPath string, count int) (entries []string, err error) {
|
||||
func (s *xlStorage) ListDir(ctx context.Context, origvolume, volume, dirPath string, count int) (entries []string, err error) {
|
||||
if contextCanceled(ctx) {
|
||||
return nil, ctx.Err()
|
||||
}
|
||||
|
||||
if origvolume != "" {
|
||||
if !skipAccessChecks(origvolume) {
|
||||
origvolumeDir, err := s.getVolDir(origvolume)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = Access(origvolumeDir); err != nil {
|
||||
return nil, convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify if volume is valid and it exists.
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
@@ -1034,15 +1046,9 @@ func (s *xlStorage) ListDir(ctx context.Context, volume, dirPath string, count i
|
||||
entries, err = readDir(dirPathAbs)
|
||||
}
|
||||
if err != nil {
|
||||
if err == errFileNotFound {
|
||||
if !skipAccessChecks(volume) {
|
||||
if ierr := Access(volumeDir); ierr != nil {
|
||||
if osIsNotExist(ierr) {
|
||||
return nil, errVolumeNotFound
|
||||
} else if isSysErrIO(ierr) {
|
||||
return nil, errFaultyDisk
|
||||
}
|
||||
}
|
||||
if errors.Is(err, errFileNotFound) && !skipAccessChecks(volume) {
|
||||
if ierr := Access(volumeDir); ierr != nil {
|
||||
return nil, convertAccessError(ierr, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
@@ -1225,7 +1231,7 @@ func (s *xlStorage) DeleteVersion(ctx context.Context, volume, path string, fi F
|
||||
metaDataPoolPut(buf) // Never used, return it
|
||||
if fi.Deleted && forceDelMarker {
|
||||
// Create a new xl.meta with a delete marker in it
|
||||
return s.WriteMetadata(ctx, volume, path, fi)
|
||||
return s.WriteMetadata(ctx, "", volume, path, fi)
|
||||
}
|
||||
|
||||
s.RLock()
|
||||
@@ -1344,8 +1350,22 @@ func (s *xlStorage) UpdateMetadata(ctx context.Context, volume, path string, fi
|
||||
}
|
||||
|
||||
// WriteMetadata - writes FileInfo metadata for path at `xl.meta`
|
||||
func (s *xlStorage) WriteMetadata(ctx context.Context, volume, path string, fi FileInfo) (err error) {
|
||||
func (s *xlStorage) WriteMetadata(ctx context.Context, origvolume, volume, path string, fi FileInfo) (err error) {
|
||||
if fi.Fresh {
|
||||
if origvolume != "" {
|
||||
origvolumeDir, err := s.getVolDir(origvolume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !skipAccessChecks(origvolume) {
|
||||
// Stat a volume entry.
|
||||
if err = Access(origvolumeDir); err != nil {
|
||||
return convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xlMeta xlMetaV2
|
||||
if err := xlMeta.AddVersion(fi); err != nil {
|
||||
return err
|
||||
@@ -1521,7 +1541,21 @@ type ReadOptions struct {
|
||||
// ReadVersion - reads metadata and returns FileInfo at path `xl.meta`
|
||||
// for all objects less than `32KiB` this call returns data as well
|
||||
// along with metadata.
|
||||
func (s *xlStorage) ReadVersion(ctx context.Context, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
func (s *xlStorage) ReadVersion(ctx context.Context, origvolume, volume, path, versionID string, opts ReadOptions) (fi FileInfo, err error) {
|
||||
if origvolume != "" {
|
||||
origvolumeDir, err := s.getVolDir(origvolume)
|
||||
if err != nil {
|
||||
return fi, err
|
||||
}
|
||||
|
||||
if !skipAccessChecks(origvolume) {
|
||||
// Stat a volume entry.
|
||||
if err = Access(origvolumeDir); err != nil {
|
||||
return fi, convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return fi, err
|
||||
@@ -1968,7 +2002,21 @@ func (c closeWrapper) Close() error {
|
||||
}
|
||||
|
||||
// CreateFile - creates the file.
|
||||
func (s *xlStorage) CreateFile(ctx context.Context, volume, path string, fileSize int64, r io.Reader) (err error) {
|
||||
func (s *xlStorage) CreateFile(ctx context.Context, origvolume, volume, path string, fileSize int64, r io.Reader) (err error) {
|
||||
if origvolume != "" {
|
||||
origvolumeDir, err := s.getVolDir(origvolume)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !skipAccessChecks(origvolume) {
|
||||
// Stat a volume entry.
|
||||
if err = Access(origvolumeDir); err != nil {
|
||||
return convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
volumeDir, err := s.getVolDir(volume)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -2355,23 +2403,13 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
||||
if !skipAccessChecks(srcVolume) {
|
||||
// Stat a volume entry.
|
||||
if err = Access(srcVolumeDir); err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return 0, errVolumeNotFound
|
||||
} else if isSysErrIO(err) {
|
||||
return 0, errFaultyDisk
|
||||
}
|
||||
return 0, err
|
||||
return 0, convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
|
||||
if !skipAccessChecks(dstVolume) {
|
||||
if err = Access(dstVolumeDir); err != nil {
|
||||
if osIsNotExist(err) {
|
||||
return 0, errVolumeNotFound
|
||||
} else if isSysErrIO(err) {
|
||||
return 0, errFaultyDisk
|
||||
}
|
||||
return 0, err
|
||||
return 0, convertAccessError(err, errVolumeAccessDenied)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2591,7 +2629,7 @@ func (s *xlStorage) RenameData(ctx context.Context, srcVolume, srcPath string, f
|
||||
}
|
||||
diskHealthCheckOK(ctx, err)
|
||||
|
||||
if !fi.Versioned && !fi.Healing() {
|
||||
if !fi.Versioned && !fi.DataMov() && !fi.Healing() {
|
||||
// Use https://man7.org/linux/man-pages/man2/rename.2.html if possible on unversioned bucket.
|
||||
if err := Rename2(pathutil.Join(srcVolumeDir, srcPath), pathutil.Join(dstVolumeDir, dstPath)); err == nil {
|
||||
return sign, nil
|
||||
|
||||
@@ -235,7 +235,7 @@ func TestXLStorageReadVersionLegacy(t *testing.T) {
|
||||
t.Fatalf("Unable to create a file \"as-file\", %s", err)
|
||||
}
|
||||
|
||||
fi, err := xlStorage.ReadVersion(context.Background(), "exists-legacy", "as-file", "", ReadOptions{})
|
||||
fi, err := xlStorage.ReadVersion(context.Background(), "", "exists-legacy", "as-file", "", ReadOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to read older 'xl.json' content: %s", err)
|
||||
}
|
||||
@@ -325,7 +325,7 @@ func TestXLStorageReadVersion(t *testing.T) {
|
||||
|
||||
// Run through all the test cases and validate for ReadVersion.
|
||||
for i, testCase := range testCases {
|
||||
_, err = xlStorage.ReadVersion(context.Background(), testCase.volume, testCase.path, "", ReadOptions{})
|
||||
_, err = xlStorage.ReadVersion(context.Background(), "", testCase.volume, testCase.path, "", ReadOptions{})
|
||||
if err != testCase.err {
|
||||
t.Fatalf("TestXLStorage %d: Expected err \"%s\", got err \"%s\"", i+1, testCase.err, err)
|
||||
}
|
||||
@@ -858,7 +858,7 @@ func TestXLStorageListDir(t *testing.T) {
|
||||
|
||||
for i, testCase := range testCases {
|
||||
var dirList []string
|
||||
dirList, err = xlStorage.ListDir(context.Background(), testCase.srcVol, testCase.srcPath, -1)
|
||||
dirList, err = xlStorage.ListDir(context.Background(), "", testCase.srcVol, testCase.srcPath, -1)
|
||||
if err != testCase.expectedErr {
|
||||
t.Errorf("TestXLStorage case %d: Expected: \"%s\", got: \"%s\"", i+1, testCase.expectedErr, err)
|
||||
}
|
||||
@@ -1657,7 +1657,7 @@ func TestXLStorageDeleteVersion(t *testing.T) {
|
||||
Checksums: nil,
|
||||
},
|
||||
}
|
||||
if err := xl.WriteMetadata(ctx, volume, object, fi); err != nil {
|
||||
if err := xl.WriteMetadata(ctx, "", volume, object, fi); err != nil {
|
||||
t.Fatalf("Unable to create object, %s", err)
|
||||
}
|
||||
}
|
||||
@@ -1666,7 +1666,7 @@ func TestXLStorageDeleteVersion(t *testing.T) {
|
||||
t.Helper()
|
||||
for i := range versions {
|
||||
shouldExist := !deleted[i]
|
||||
fi, err := xl.ReadVersion(ctx, volume, object, versions[i], ReadOptions{})
|
||||
fi, err := xl.ReadVersion(ctx, "", volume, object, versions[i], ReadOptions{})
|
||||
if shouldExist {
|
||||
if err != nil {
|
||||
t.Fatalf("Version %s should exist, but got err %v", versions[i], err)
|
||||
@@ -1713,7 +1713,7 @@ func TestXLStorageDeleteVersion(t *testing.T) {
|
||||
checkVerExist(t)
|
||||
|
||||
// Meta should be deleted now...
|
||||
fi, err := xl.ReadVersion(ctx, volume, object, "", ReadOptions{})
|
||||
fi, err := xl.ReadVersion(ctx, "", volume, object, "", ReadOptions{})
|
||||
if err != errFileNotFound {
|
||||
t.Fatalf("Object %s should not exist, but returned: %#v", object, fi)
|
||||
}
|
||||
@@ -1871,7 +1871,7 @@ func TestXLStorageVerifyFile(t *testing.T) {
|
||||
algo = HighwayHash256S
|
||||
shardSize := int64(1024 * 1024)
|
||||
shard := make([]byte, shardSize)
|
||||
w := newStreamingBitrotWriter(storage, volName, fileName, size, algo, shardSize)
|
||||
w := newStreamingBitrotWriter(storage, "", volName, fileName, size, algo, shardSize)
|
||||
reader := bytes.NewReader(data)
|
||||
for {
|
||||
// Using io.Copy instead of this loop will not work for us as io.Copy
|
||||
|
||||
@@ -6,7 +6,7 @@ Metrics may include one or more labels, such as the server that calculated that
|
||||
These metrics can be obtained from any MinIO server once per collection by using the following URL:
|
||||
|
||||
```shell
|
||||
https://HOSTNAME:PORT/minio/metrics/v2/cluster
|
||||
https://HOSTNAME:PORT/minio/v2/metrics/cluster
|
||||
```
|
||||
|
||||
Replace ``HOSTNAME:PORT`` with the hostname of your MinIO deployment.
|
||||
@@ -328,7 +328,7 @@ Metrics may include one or more additional labels, such as the drive path, inter
|
||||
These metrics can be obtained from any MinIO server once per collection by using the following URL:
|
||||
|
||||
```shell
|
||||
https://HOSTNAME:PORT/minio/metrics/v2/resource
|
||||
https://HOSTNAME:PORT/minio/v2/metrics/resource
|
||||
```
|
||||
|
||||
Replace `HOSTNAME:PORT` with the hostname of your MinIO deployment.
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
# Settings and configurations that are common for all containers
|
||||
x-minio-common: &minio-common
|
||||
image: quay.io/minio/minio:RELEASE.2024-01-18T22-51-28Z
|
||||
image: quay.io/minio/minio:RELEASE.2024-01-31T20-20-33Z
|
||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||
expose:
|
||||
- "9000"
|
||||
|
||||
@@ -51,10 +51,10 @@ require (
|
||||
github.com/minio/dperf v0.5.3
|
||||
github.com/minio/highwayhash v1.0.2
|
||||
github.com/minio/kes-go v0.2.0
|
||||
github.com/minio/madmin-go/v3 v3.0.40
|
||||
github.com/minio/madmin-go/v3 v3.0.43
|
||||
github.com/minio/minio-go/v7 v7.0.66
|
||||
github.com/minio/mux v1.9.0
|
||||
github.com/minio/pkg/v2 v2.0.8
|
||||
github.com/minio/pkg/v2 v2.0.9-0.20240130164631-ac3f1be23e94
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/minio/sha256-simd v1.0.1
|
||||
github.com/minio/simdjson-go v0.4.5
|
||||
@@ -80,7 +80,7 @@ require (
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
|
||||
github.com/rs/cors v1.10.1
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
github.com/shirou/gopsutil/v3 v3.23.11
|
||||
github.com/shirou/gopsutil/v3 v3.24.1
|
||||
github.com/tidwall/gjson v1.17.0
|
||||
github.com/tinylib/msgp v1.1.9
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
@@ -196,7 +196,7 @@ require (
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/minio/colorjson v1.0.6 // indirect
|
||||
github.com/minio/filepath v1.0.0 // indirect
|
||||
github.com/minio/mc v0.0.0-20240111054932-d4305a5bf95e // indirect
|
||||
github.com/minio/mc v0.0.0-20240129194012-12f446e1de57 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/websocket v1.6.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
|
||||
@@ -443,10 +443,10 @@ github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA
|
||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/minio/kes-go v0.2.0 h1:HA33arq9s3MErbsj3PAXFVfFo4U4yw7lTKQ5kWFrpCA=
|
||||
github.com/minio/kes-go v0.2.0/go.mod h1:VorHLaIYis9/MxAHAtXN4d8PUMNKhIxTIlvFt0hBOEo=
|
||||
github.com/minio/madmin-go/v3 v3.0.40 h1:e4bfbRYgzv+Sl13ek6CM1la0PMLfxfgmKSLjBBDil0I=
|
||||
github.com/minio/madmin-go/v3 v3.0.40/go.mod h1:4QN2NftLSV7MdlT50dkrenOMmNVHluxTvlqJou3hte8=
|
||||
github.com/minio/mc v0.0.0-20240111054932-d4305a5bf95e h1:vKnv5aBTcAAnDGYeJW/SPieXCerp/7MIYxuEUYt7iOE=
|
||||
github.com/minio/mc v0.0.0-20240111054932-d4305a5bf95e/go.mod h1:wFVJTmLJniMFDkcvPP0h/KvCxK+MiA2rc6q7KUefN28=
|
||||
github.com/minio/madmin-go/v3 v3.0.43 h1:AkniczVEkBErQ94MikyPINGoOjFWhuP8xH5KmFpAaO0=
|
||||
github.com/minio/madmin-go/v3 v3.0.43/go.mod h1:ZDF7kf5fhmxLhbGTqyq5efs4ao0v4eWf7nOuef/ljJs=
|
||||
github.com/minio/mc v0.0.0-20240129194012-12f446e1de57 h1:FO4a9XVuLcIS5s11efycWkBNrfIz4HtDQgUhR+xmLsQ=
|
||||
github.com/minio/mc v0.0.0-20240129194012-12f446e1de57/go.mod h1:MmDLdb7NWd/OYhcKcXKvwErq2GNa/Zq6xtTWuhdC4II=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v6 v6.0.46/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg=
|
||||
@@ -454,8 +454,8 @@ github.com/minio/minio-go/v7 v7.0.66 h1:bnTOXOHjOqv/gcMuiVbN9o2ngRItvqE774dG9nq0
|
||||
github.com/minio/minio-go/v7 v7.0.66/go.mod h1:DHAgmyQEGdW3Cif0UooKOyrT3Vxs82zNdV6tkKhRtbs=
|
||||
github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
|
||||
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
|
||||
github.com/minio/pkg/v2 v2.0.8 h1:JuNWjELRaDSMZegeWxNcx5Lf7ZxqKAI5hjSgx1bLbKc=
|
||||
github.com/minio/pkg/v2 v2.0.8/go.mod h1:yayUTo82b0RK+e97hGb1naC787mOtUEyDs3SIcwSyHI=
|
||||
github.com/minio/pkg/v2 v2.0.9-0.20240130164631-ac3f1be23e94 h1:nBZkkmq9HPfs2ZUDcRqPahe2js1mvMUR/7cBfW17Jik=
|
||||
github.com/minio/pkg/v2 v2.0.9-0.20240130164631-ac3f1be23e94/go.mod h1:yayUTo82b0RK+e97hGb1naC787mOtUEyDs3SIcwSyHI=
|
||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
@@ -606,8 +606,8 @@ github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6o
|
||||
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
|
||||
github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc=
|
||||
github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs=
|
||||
github.com/shirou/gopsutil/v3 v3.23.11 h1:i3jP9NjCPUz7FiZKxlMnODZkdSIp2gnzfrvsu9CuWEQ=
|
||||
github.com/shirou/gopsutil/v3 v3.23.11/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
||||
github.com/shirou/gopsutil/v3 v3.24.1 h1:R3t6ondCEvmARp3wxODhXMTLC/klMa87h2PHUw5m7QI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.1/go.mod h1:UU7a2MSBQa+kW1uuDq8DeEBS8kmrnQwsv2b5O513rwU=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
|
||||
@@ -28,7 +28,7 @@ type BytePoolCap struct {
|
||||
|
||||
// NewBytePoolCap creates a new BytePool bounded to the given maxSize, with new
|
||||
// byte arrays sized based on width.
|
||||
func NewBytePoolCap(maxSize int, width int, capwidth int) (bp *BytePoolCap) {
|
||||
func NewBytePoolCap(maxSize uint64, width int, capwidth int) (bp *BytePoolCap) {
|
||||
if capwidth > 0 && capwidth < 64 {
|
||||
panic("buffer capped with smaller than 64 bytes is not supported")
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import "testing"
|
||||
|
||||
// Tests - bytePool functionality.
|
||||
func TestBytePool(t *testing.T) {
|
||||
size := 4
|
||||
size := uint64(4)
|
||||
width := 1024
|
||||
capWidth := 2048
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestBytePool(t *testing.T) {
|
||||
bufPool.Put(b)
|
||||
|
||||
// Fill the pool beyond the capped pool size.
|
||||
for i := 0; i < size*2; i++ {
|
||||
for i := uint64(0); i < size*2; i++ {
|
||||
bufPool.Put(make([]byte, bufPool.w))
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestBytePool(t *testing.T) {
|
||||
close(bufPool.c)
|
||||
|
||||
// Check the size of the pool.
|
||||
if len(bufPool.c) != size {
|
||||
if uint64(len(bufPool.c)) != size {
|
||||
t.Fatalf("bytepool size invalid: got %v want %v", len(bufPool.c), size)
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ func LookupConfig(kvs config.KVS) (cfg *Config, err error) {
|
||||
}
|
||||
dur, _ := time.ParseDuration(d)
|
||||
if dur < time.Second {
|
||||
cfg.MaxTimeout = time.Minute * 2
|
||||
cfg.MaxTimeout = 30 * time.Second
|
||||
} else {
|
||||
cfg.MaxTimeout = getMaxTimeout(dur)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func getMaxTimeout(t time.Duration) time.Duration {
|
||||
}
|
||||
dur, _ := time.ParseDuration(d)
|
||||
if dur < time.Second {
|
||||
return time.Minute * 2
|
||||
return 30 * time.Second
|
||||
}
|
||||
return dur
|
||||
}
|
||||
|
||||
@@ -49,8 +49,8 @@ var generateKeyTests = []struct {
|
||||
}
|
||||
|
||||
func TestGenerateKey(t *testing.T) {
|
||||
defer func(l logger.LogLevel) { logger.MinimumLogLevel = l }(logger.MinimumLogLevel)
|
||||
logger.MinimumLogLevel = logger.ErrorLvl
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
|
||||
for i, test := range generateKeyTests {
|
||||
i, test := i, test
|
||||
@@ -75,8 +75,8 @@ var generateIVTests = []struct {
|
||||
}
|
||||
|
||||
func TestGenerateIV(t *testing.T) {
|
||||
defer func(l logger.LogLevel) { logger.MinimumLogLevel = l }(logger.MinimumLogLevel)
|
||||
logger.MinimumLogLevel = logger.ErrorLvl
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
|
||||
for i, test := range generateIVTests {
|
||||
i, test := i, test
|
||||
|
||||
@@ -313,8 +313,8 @@ var s3CreateMetadataTests = []struct {
|
||||
}
|
||||
|
||||
func TestS3CreateMetadata(t *testing.T) {
|
||||
defer func(l logger.LogLevel) { logger.MinimumLogLevel = l }(logger.MinimumLogLevel)
|
||||
logger.MinimumLogLevel = logger.ErrorLvl
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
for i, test := range s3CreateMetadataTests {
|
||||
metadata := S3.CreateMetadata(nil, test.KeyID, test.SealedDataKey, test.SealedKey)
|
||||
keyID, kmsKey, sealedKey, err := S3.ParseMetadata(metadata)
|
||||
@@ -358,8 +358,8 @@ var ssecCreateMetadataTests = []struct {
|
||||
}
|
||||
|
||||
func TestSSECCreateMetadata(t *testing.T) {
|
||||
defer func(l logger.LogLevel) { logger.MinimumLogLevel = l }(logger.MinimumLogLevel)
|
||||
logger.MinimumLogLevel = logger.ErrorLvl
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
for i, test := range ssecCreateMetadataTests {
|
||||
metadata := SSEC.CreateMetadata(nil, test.SealedKey)
|
||||
sealedKey, err := SSEC.ParseMetadata(metadata)
|
||||
|
||||
@@ -287,6 +287,7 @@ func (c *Connection) newMuxClient(ctx context.Context) (*muxClient, error) {
|
||||
if dl, ok := ctx.Deadline(); ok {
|
||||
client.deadline = getDeadline(time.Until(dl))
|
||||
if client.deadline == 0 {
|
||||
client.cancelFn(context.DeadlineExceeded)
|
||||
return nil, context.DeadlineExceeded
|
||||
}
|
||||
}
|
||||
@@ -333,6 +334,7 @@ func (c *Connection) Request(ctx context.Context, h HandlerID, req []byte) ([]by
|
||||
_, ok := c.outgoing.Load(client.MuxID)
|
||||
fmt.Println(client.MuxID, c.String(), "Connection.Request: DELETING MUX. Exists:", ok)
|
||||
}
|
||||
client.cancelFn(context.Canceled)
|
||||
c.outgoing.Delete(client.MuxID)
|
||||
}()
|
||||
return client.traceRoundtrip(ctx, c.trace, h, req)
|
||||
@@ -1372,7 +1374,7 @@ func (c *Connection) handleDisconnectClientMux(m message) {
|
||||
if m.Flags&FlagPayloadIsErr != 0 {
|
||||
v.error(RemoteErr(m.Payload))
|
||||
} else {
|
||||
v.error("remote disconnected")
|
||||
v.error(ErrDisconnected)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
// ErrDisconnected is returned when the connection to the remote has been lost during the call.
|
||||
var ErrDisconnected = errors.New("remote disconnected")
|
||||
var ErrDisconnected = RemoteErr("remote disconnected")
|
||||
|
||||
const (
|
||||
// minBufferSize is the minimum buffer size.
|
||||
|
||||
@@ -231,6 +231,7 @@ func TestSingleRoundtripGenerics(t *testing.T) {
|
||||
t.Errorf("want %q, got %q", testPayload, resp.OrgString)
|
||||
}
|
||||
t.Log("Roundtrip:", time.Since(start))
|
||||
h1.PutResponse(resp)
|
||||
|
||||
start = time.Now()
|
||||
resp, err = h2.Call(context.Background(), remoteConn, &testRequest{Num: 1, String: testPayload})
|
||||
@@ -241,9 +242,74 @@ func TestSingleRoundtripGenerics(t *testing.T) {
|
||||
if resp != nil {
|
||||
t.Errorf("want nil, got %q", resp)
|
||||
}
|
||||
h2.PutResponse(resp)
|
||||
t.Log("Roundtrip:", time.Since(start))
|
||||
}
|
||||
|
||||
func TestSingleRoundtripGenericsRecycle(t *testing.T) {
|
||||
defer testlogger.T.SetLogTB(t)()
|
||||
errFatal := func(err error) {
|
||||
t.Helper()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
grid, err := SetupTestGrid(2)
|
||||
errFatal(err)
|
||||
remoteHost := grid.Hosts[1]
|
||||
local := grid.Managers[0]
|
||||
remote := grid.Managers[1]
|
||||
|
||||
// 1: Echo
|
||||
h1 := NewSingleHandler[*MSS, *MSS](handlerTest, NewMSS, NewMSS)
|
||||
// Handles incoming requests, returns a response
|
||||
handler1 := func(req *MSS) (resp *MSS, err *RemoteErr) {
|
||||
resp = h1.NewResponse()
|
||||
for k, v := range *req {
|
||||
(*resp)[k] = v
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
// Return error
|
||||
h2 := NewSingleHandler[*MSS, *MSS](handlerTest2, NewMSS, NewMSS)
|
||||
handler2 := func(req *MSS) (resp *MSS, err *RemoteErr) {
|
||||
defer req.Recycle()
|
||||
r := RemoteErr(req.Get("err"))
|
||||
return nil, &r
|
||||
}
|
||||
errFatal(h1.Register(local, handler1))
|
||||
errFatal(h2.Register(local, handler2))
|
||||
|
||||
errFatal(h1.Register(remote, handler1))
|
||||
errFatal(h2.Register(remote, handler2))
|
||||
|
||||
// local to remote connection
|
||||
remoteConn := local.Connection(remoteHost)
|
||||
const testPayload = "Hello Grid World!"
|
||||
|
||||
start := time.Now()
|
||||
req := NewMSSWith(map[string]string{"test": testPayload})
|
||||
resp, err := h1.Call(context.Background(), remoteConn, req)
|
||||
errFatal(err)
|
||||
if resp.Get("test") != testPayload {
|
||||
t.Errorf("want %q, got %q", testPayload, resp.Get("test"))
|
||||
}
|
||||
t.Log("Roundtrip:", time.Since(start))
|
||||
h1.PutResponse(resp)
|
||||
|
||||
start = time.Now()
|
||||
resp, err = h2.Call(context.Background(), remoteConn, NewMSSWith(map[string]string{"err": testPayload}))
|
||||
t.Log("Roundtrip:", time.Since(start))
|
||||
if err != RemoteErr(testPayload) {
|
||||
t.Errorf("want error %v(%T), got %v(%T)", RemoteErr(testPayload), RemoteErr(testPayload), err, err)
|
||||
}
|
||||
if resp != nil {
|
||||
t.Errorf("want nil, got %q", resp)
|
||||
}
|
||||
t.Log("Roundtrip:", time.Since(start))
|
||||
h2.PutResponse(resp)
|
||||
}
|
||||
|
||||
func TestStreamSuite(t *testing.T) {
|
||||
defer testlogger.T.SetErrorTB(t)()
|
||||
errFatal := func(err error) {
|
||||
|
||||
+159
-76
@@ -59,8 +59,32 @@ const (
|
||||
HandlerRenameData
|
||||
HandlerRenameFile
|
||||
HandlerReadAll
|
||||
|
||||
HandlerServerVerify
|
||||
HandlerTrace
|
||||
HandlerListen
|
||||
HandlerGetLocalDiskIDs
|
||||
HandlerDeleteBucketMetadata
|
||||
HandlerLoadBucketMetadata
|
||||
HandlerReloadSiteReplicationConfig
|
||||
HandlerReloadPoolMeta
|
||||
HandlerStopRebalance
|
||||
HandlerLoadRebalanceMeta
|
||||
HandlerLoadTransitionTierConfig
|
||||
|
||||
HandlerDeletePolicy
|
||||
HandlerLoadPolicy
|
||||
HandlerLoadPolicyMapping
|
||||
HandlerDeleteServiceAccount
|
||||
HandlerLoadServiceAccount
|
||||
HandlerDeleteUser
|
||||
HandlerLoadUser
|
||||
HandlerLoadGroup
|
||||
|
||||
HandlerHealBucket
|
||||
HandlerMakeBucket
|
||||
HandlerHeadBucket
|
||||
HandlerDeleteBucket
|
||||
|
||||
// Add more above here ^^^
|
||||
// If all handlers are used, the type of Handler can be changed.
|
||||
// Handlers have no versioning, so non-compatible handler changes must result in new IDs.
|
||||
@@ -72,33 +96,58 @@ const (
|
||||
// handlerPrefixes are prefixes for handler IDs used for tracing.
|
||||
// If a handler is not listed here, it will be traced with "grid" prefix.
|
||||
var handlerPrefixes = [handlerLast]string{
|
||||
HandlerLockLock: lockPrefix,
|
||||
HandlerLockRLock: lockPrefix,
|
||||
HandlerLockUnlock: lockPrefix,
|
||||
HandlerLockRUnlock: lockPrefix,
|
||||
HandlerLockRefresh: lockPrefix,
|
||||
HandlerLockForceUnlock: lockPrefix,
|
||||
HandlerWalkDir: storagePrefix,
|
||||
HandlerStatVol: storagePrefix,
|
||||
HandlerDiskInfo: storagePrefix,
|
||||
HandlerNSScanner: storagePrefix,
|
||||
HandlerReadXL: storagePrefix,
|
||||
HandlerReadVersion: storagePrefix,
|
||||
HandlerDeleteFile: storagePrefix,
|
||||
HandlerDeleteVersion: storagePrefix,
|
||||
HandlerUpdateMetadata: storagePrefix,
|
||||
HandlerWriteMetadata: storagePrefix,
|
||||
HandlerCheckParts: storagePrefix,
|
||||
HandlerRenameData: storagePrefix,
|
||||
HandlerRenameFile: storagePrefix,
|
||||
HandlerReadAll: storagePrefix,
|
||||
HandlerServerVerify: bootstrapPrefix,
|
||||
HandlerLockLock: lockPrefix,
|
||||
HandlerLockRLock: lockPrefix,
|
||||
HandlerLockUnlock: lockPrefix,
|
||||
HandlerLockRUnlock: lockPrefix,
|
||||
HandlerLockRefresh: lockPrefix,
|
||||
HandlerLockForceUnlock: lockPrefix,
|
||||
HandlerWalkDir: storagePrefix,
|
||||
HandlerStatVol: storagePrefix,
|
||||
HandlerDiskInfo: storagePrefix,
|
||||
HandlerNSScanner: storagePrefix,
|
||||
HandlerReadXL: storagePrefix,
|
||||
HandlerReadVersion: storagePrefix,
|
||||
HandlerDeleteFile: storagePrefix,
|
||||
HandlerDeleteVersion: storagePrefix,
|
||||
HandlerUpdateMetadata: storagePrefix,
|
||||
HandlerWriteMetadata: storagePrefix,
|
||||
HandlerCheckParts: storagePrefix,
|
||||
HandlerRenameData: storagePrefix,
|
||||
HandlerRenameFile: storagePrefix,
|
||||
HandlerReadAll: storagePrefix,
|
||||
HandlerServerVerify: bootstrapPrefix,
|
||||
HandlerTrace: peerPrefix,
|
||||
HandlerListen: peerPrefix,
|
||||
HandlerGetLocalDiskIDs: peerPrefix,
|
||||
HandlerDeleteBucketMetadata: peerPrefix,
|
||||
HandlerLoadBucketMetadata: peerPrefix,
|
||||
HandlerReloadSiteReplicationConfig: peerPrefix,
|
||||
HandlerReloadPoolMeta: peerPrefix,
|
||||
HandlerStopRebalance: peerPrefix,
|
||||
HandlerLoadRebalanceMeta: peerPrefix,
|
||||
HandlerLoadTransitionTierConfig: peerPrefix,
|
||||
HandlerDeletePolicy: peerPrefix,
|
||||
HandlerLoadPolicy: peerPrefix,
|
||||
HandlerLoadPolicyMapping: peerPrefix,
|
||||
HandlerDeleteServiceAccount: peerPrefix,
|
||||
HandlerLoadServiceAccount: peerPrefix,
|
||||
HandlerDeleteUser: peerPrefix,
|
||||
HandlerLoadUser: peerPrefix,
|
||||
HandlerLoadGroup: peerPrefix,
|
||||
HandlerMakeBucket: peerPrefixS3,
|
||||
HandlerHeadBucket: peerPrefixS3,
|
||||
HandlerDeleteBucket: peerPrefixS3,
|
||||
HandlerHealBucket: healPrefix,
|
||||
}
|
||||
|
||||
const (
|
||||
lockPrefix = "lockR"
|
||||
storagePrefix = "storageR"
|
||||
bootstrapPrefix = "bootstrap"
|
||||
peerPrefix = "peer"
|
||||
peerPrefixS3 = "peerS3"
|
||||
healPrefix = "heal"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -297,14 +346,40 @@ type RoundTripper interface {
|
||||
|
||||
// SingleHandler is a type safe handler for single roundtrip requests.
|
||||
type SingleHandler[Req, Resp RoundTripper] struct {
|
||||
id HandlerID
|
||||
sharedResponse bool
|
||||
id HandlerID
|
||||
sharedResp bool
|
||||
callReuseReq bool
|
||||
|
||||
reqPool sync.Pool
|
||||
respPool sync.Pool
|
||||
newReq func() Req
|
||||
newResp func() Resp
|
||||
|
||||
nilReq Req
|
||||
nilResp Resp
|
||||
recycleReq func(Req)
|
||||
recycleResp func(Resp)
|
||||
}
|
||||
|
||||
func recycleFunc[RT RoundTripper](newRT func() RT) (newFn func() RT, recycle func(r RT)) {
|
||||
rAny := any(newRT())
|
||||
var rZero RT
|
||||
if _, ok := rAny.(Recycler); ok {
|
||||
return newRT, func(r RT) {
|
||||
if r != rZero {
|
||||
if rc, ok := any(r).(Recycler); ok {
|
||||
rc.Recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pool := sync.Pool{
|
||||
New: func() interface{} {
|
||||
return newRT()
|
||||
},
|
||||
}
|
||||
return func() RT { return pool.Get().(RT) },
|
||||
func(r RT) {
|
||||
if r != rZero {
|
||||
pool.Put(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewSingleHandler creates a typed handler that can provide Marshal/Unmarshal.
|
||||
@@ -312,27 +387,34 @@ type SingleHandler[Req, Resp RoundTripper] struct {
|
||||
// Use Call to initiate a clientside call.
|
||||
func NewSingleHandler[Req, Resp RoundTripper](h HandlerID, newReq func() Req, newResp func() Resp) *SingleHandler[Req, Resp] {
|
||||
s := SingleHandler[Req, Resp]{id: h}
|
||||
s.reqPool.New = func() interface{} {
|
||||
return newReq()
|
||||
}
|
||||
s.respPool.New = func() interface{} {
|
||||
return newResp()
|
||||
s.newReq, s.recycleReq = recycleFunc[Req](newReq)
|
||||
s.newResp, s.recycleResp = recycleFunc[Resp](newResp)
|
||||
if _, ok := any(newReq()).(Recycler); ok {
|
||||
s.callReuseReq = true
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
// PutResponse will accept a response for reuse.
|
||||
// These should be returned by the caller.
|
||||
// This can be used by a caller to recycle a response after receiving it from a Call.
|
||||
func (h *SingleHandler[Req, Resp]) PutResponse(r Resp) {
|
||||
if r != h.nilResp {
|
||||
h.respPool.Put(r)
|
||||
}
|
||||
h.recycleResp(r)
|
||||
}
|
||||
|
||||
// WithSharedResponse indicates it is unsafe to reuse the response.
|
||||
// AllowCallRequestPool indicates it is safe to reuse the request
|
||||
// on the client side, meaning the request is recycled/pooled when a request is sent.
|
||||
// CAREFUL: This should only be used when there are no pointers, slices that aren't freshly constructed.
|
||||
func (h *SingleHandler[Req, Resp]) AllowCallRequestPool(b bool) *SingleHandler[Req, Resp] {
|
||||
h.callReuseReq = b
|
||||
return h
|
||||
}
|
||||
|
||||
// WithSharedResponse indicates it is unsafe to reuse the response
|
||||
// when it has been returned on a handler.
|
||||
// This will disable automatic response recycling/pooling.
|
||||
// Typically this is used when the response sharing part of its data structure.
|
||||
func (h *SingleHandler[Req, Resp]) WithSharedResponse() *SingleHandler[Req, Resp] {
|
||||
h.sharedResponse = true
|
||||
h.sharedResp = true
|
||||
return h
|
||||
}
|
||||
|
||||
@@ -340,26 +422,25 @@ func (h *SingleHandler[Req, Resp]) WithSharedResponse() *SingleHandler[Req, Resp
|
||||
// Handlers can use this to create a reusable response.
|
||||
// The response may be reused, so caller should clear any fields.
|
||||
func (h *SingleHandler[Req, Resp]) NewResponse() Resp {
|
||||
return h.respPool.Get().(Resp)
|
||||
}
|
||||
|
||||
// putRequest will accept a request for reuse.
|
||||
// This is not exported, since it shouldn't be needed.
|
||||
func (h *SingleHandler[Req, Resp]) putRequest(r Req) {
|
||||
if r != h.nilReq {
|
||||
h.reqPool.Put(r)
|
||||
}
|
||||
return h.newResp()
|
||||
}
|
||||
|
||||
// NewRequest creates a new request.
|
||||
// Handlers can use this to create a reusable request.
|
||||
// The request may be reused, so caller should clear any fields.
|
||||
func (h *SingleHandler[Req, Resp]) NewRequest() Req {
|
||||
return h.reqPool.Get().(Req)
|
||||
return h.newReq()
|
||||
}
|
||||
|
||||
// Register a handler for a Req -> Resp roundtrip.
|
||||
// Requests are automatically recycled.
|
||||
func (h *SingleHandler[Req, Resp]) Register(m *Manager, handle func(req Req) (resp Resp, err *RemoteErr), subroute ...string) error {
|
||||
if h.newReq == nil {
|
||||
return errors.New("newReq nil in NewSingleHandler")
|
||||
}
|
||||
if h.newResp == nil {
|
||||
return errors.New("newResp nil in NewSingleHandler")
|
||||
}
|
||||
return m.RegisterSingleHandler(h.id, func(payload []byte) ([]byte, *RemoteErr) {
|
||||
req := h.NewRequest()
|
||||
_, err := req.UnmarshalMsg(payload)
|
||||
@@ -369,13 +450,14 @@ func (h *SingleHandler[Req, Resp]) Register(m *Manager, handle func(req Req) (re
|
||||
return nil, &r
|
||||
}
|
||||
resp, rerr := handle(req)
|
||||
h.putRequest(req)
|
||||
h.recycleReq(req)
|
||||
|
||||
if rerr != nil {
|
||||
PutByteBuffer(payload)
|
||||
return nil, rerr
|
||||
}
|
||||
payload, err = resp.MarshalMsg(payload[:0])
|
||||
if !h.sharedResponse {
|
||||
if !h.sharedResp {
|
||||
h.PutResponse(resp)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -400,19 +482,29 @@ func (h *SingleHandler[Req, Resp]) Call(ctx context.Context, c Requester, req Re
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
ctx = context.WithValue(ctx, TraceParamsKey{}, req)
|
||||
switch any(req).(type) {
|
||||
case *MSS, *URLValues:
|
||||
ctx = context.WithValue(ctx, TraceParamsKey{}, req)
|
||||
case *NoPayload, *Bytes:
|
||||
// do not need to trace nopayload and bytes payload
|
||||
default:
|
||||
ctx = context.WithValue(ctx, TraceParamsKey{}, fmt.Sprintf("type=%T", req))
|
||||
}
|
||||
if h.callReuseReq {
|
||||
defer h.recycleReq(req)
|
||||
}
|
||||
res, err := c.Request(ctx, h.id, payload)
|
||||
PutByteBuffer(payload)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
defer PutByteBuffer(res)
|
||||
r := h.NewResponse()
|
||||
_, err = r.UnmarshalMsg(res)
|
||||
if err != nil {
|
||||
h.PutResponse(r)
|
||||
return resp, err
|
||||
}
|
||||
PutByteBuffer(res)
|
||||
return r, err
|
||||
}
|
||||
|
||||
@@ -542,6 +634,20 @@ func (h *StreamTypeHandler[Payload, Req, Resp]) Register(m *Manager, handle func
|
||||
return h.register(m, handle, subroute...)
|
||||
}
|
||||
|
||||
// WithOutCapacity adjusts the output capacity from the handler perspective.
|
||||
// This must be done prior to registering the handler.
|
||||
func (h *StreamTypeHandler[Payload, Req, Resp]) WithOutCapacity(out int) *StreamTypeHandler[Payload, Req, Resp] {
|
||||
h.OutCapacity = out
|
||||
return h
|
||||
}
|
||||
|
||||
// WithInCapacity adjusts the input capacity from the handler perspective.
|
||||
// This must be done prior to registering the handler.
|
||||
func (h *StreamTypeHandler[Payload, Req, Resp]) WithInCapacity(in int) *StreamTypeHandler[Payload, Req, Resp] {
|
||||
h.InCapacity = in
|
||||
return h
|
||||
}
|
||||
|
||||
// RegisterNoInput a handler for one-way streaming with payload and output stream.
|
||||
// An optional subroute can be given. Multiple entries are joined with '/'.
|
||||
func (h *StreamTypeHandler[Payload, Req, Resp]) RegisterNoInput(m *Manager, handle func(ctx context.Context, p Payload, out chan<- Resp) *RemoteErr, subroute ...string) error {
|
||||
@@ -712,26 +818,3 @@ func (h *StreamTypeHandler[Payload, Req, Resp]) Call(ctx context.Context, c Stre
|
||||
|
||||
return &TypedStream[Req, Resp]{responses: stream, newResp: h.NewResponse, Requests: reqT}, nil
|
||||
}
|
||||
|
||||
// NoPayload is a type that can be used for handlers that do not use a payload.
|
||||
type NoPayload struct{}
|
||||
|
||||
// Msgsize returns 0.
|
||||
func (p NoPayload) Msgsize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// UnmarshalMsg satisfies the interface, but is a no-op.
|
||||
func (NoPayload) UnmarshalMsg(bytes []byte) ([]byte, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// MarshalMsg satisfies the interface, but is a no-op.
|
||||
func (NoPayload) MarshalMsg(bytes []byte) ([]byte, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// NewNoPayload returns an empty NoPayload struct.
|
||||
func NewNoPayload() NoPayload {
|
||||
return NoPayload{}
|
||||
}
|
||||
|
||||
@@ -30,14 +30,36 @@ func _() {
|
||||
_ = x[HandlerRenameFile-19]
|
||||
_ = x[HandlerReadAll-20]
|
||||
_ = x[HandlerServerVerify-21]
|
||||
_ = x[handlerTest-22]
|
||||
_ = x[handlerTest2-23]
|
||||
_ = x[handlerLast-24]
|
||||
_ = x[HandlerTrace-22]
|
||||
_ = x[HandlerListen-23]
|
||||
_ = x[HandlerGetLocalDiskIDs-24]
|
||||
_ = x[HandlerDeleteBucketMetadata-25]
|
||||
_ = x[HandlerLoadBucketMetadata-26]
|
||||
_ = x[HandlerReloadSiteReplicationConfig-27]
|
||||
_ = x[HandlerReloadPoolMeta-28]
|
||||
_ = x[HandlerStopRebalance-29]
|
||||
_ = x[HandlerLoadRebalanceMeta-30]
|
||||
_ = x[HandlerLoadTransitionTierConfig-31]
|
||||
_ = x[HandlerDeletePolicy-32]
|
||||
_ = x[HandlerLoadPolicy-33]
|
||||
_ = x[HandlerLoadPolicyMapping-34]
|
||||
_ = x[HandlerDeleteServiceAccount-35]
|
||||
_ = x[HandlerLoadServiceAccount-36]
|
||||
_ = x[HandlerDeleteUser-37]
|
||||
_ = x[HandlerLoadUser-38]
|
||||
_ = x[HandlerLoadGroup-39]
|
||||
_ = x[HandlerHealBucket-40]
|
||||
_ = x[HandlerMakeBucket-41]
|
||||
_ = x[HandlerHeadBucket-42]
|
||||
_ = x[HandlerDeleteBucket-43]
|
||||
_ = x[handlerTest-44]
|
||||
_ = x[handlerTest2-45]
|
||||
_ = x[handlerLast-46]
|
||||
}
|
||||
|
||||
const _HandlerID_name = "handlerInvalidLockLockLockRLockLockUnlockLockRUnlockLockRefreshLockForceUnlockWalkDirStatVolDiskInfoNSScannerReadXLReadVersionDeleteFileDeleteVersionUpdateMetadataWriteMetadataCheckPartsRenameDataRenameFileReadAllServerVerifyhandlerTesthandlerTest2handlerLast"
|
||||
const _HandlerID_name = "handlerInvalidLockLockLockRLockLockUnlockLockRUnlockLockRefreshLockForceUnlockWalkDirStatVolDiskInfoNSScannerReadXLReadVersionDeleteFileDeleteVersionUpdateMetadataWriteMetadataCheckPartsRenameDataRenameFileReadAllServerVerifyTraceListenGetLocalDiskIDsDeleteBucketMetadataLoadBucketMetadataReloadSiteReplicationConfigReloadPoolMetaStopRebalanceLoadRebalanceMetaLoadTransitionTierConfigDeletePolicyLoadPolicyLoadPolicyMappingDeleteServiceAccountLoadServiceAccountDeleteUserLoadUserLoadGroupHealBucketMakeBucketHeadBucketDeleteBuckethandlerTesthandlerTest2handlerLast"
|
||||
|
||||
var _HandlerID_index = [...]uint16{0, 14, 22, 31, 41, 52, 63, 78, 85, 92, 100, 109, 115, 126, 136, 149, 163, 176, 186, 196, 206, 213, 225, 236, 248, 259}
|
||||
var _HandlerID_index = [...]uint16{0, 14, 22, 31, 41, 52, 63, 78, 85, 92, 100, 109, 115, 126, 136, 149, 163, 176, 186, 196, 206, 213, 225, 230, 236, 251, 271, 289, 316, 330, 343, 360, 384, 396, 406, 423, 443, 461, 471, 479, 488, 498, 508, 518, 530, 541, 553, 564}
|
||||
|
||||
func (i HandlerID) String() string {
|
||||
if i >= HandlerID(len(_HandlerID_index)-1) {
|
||||
|
||||
@@ -325,9 +325,15 @@ func (m *muxServer) close() {
|
||||
m.cancel()
|
||||
m.recvMu.Lock()
|
||||
defer m.recvMu.Unlock()
|
||||
xioutil.SafeClose(m.inbound)
|
||||
m.inbound = nil
|
||||
|
||||
xioutil.SafeClose(m.outBlock)
|
||||
m.outBlock = nil
|
||||
if m.inbound != nil {
|
||||
xioutil.SafeClose(m.inbound)
|
||||
m.inbound = nil
|
||||
}
|
||||
|
||||
if m.outBlock != nil {
|
||||
xioutil.SafeClose(m.outBlock)
|
||||
m.outBlock = nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,10 +63,11 @@ func (s *Stream) Send(b []byte) error {
|
||||
func (s *Stream) Results(next func(b []byte) error) (err error) {
|
||||
done := false
|
||||
defer func() {
|
||||
if s.cancel != nil {
|
||||
s.cancel(err)
|
||||
}
|
||||
|
||||
if !done {
|
||||
if s.cancel != nil {
|
||||
s.cancel(err)
|
||||
}
|
||||
// Drain channel.
|
||||
for range s.responses {
|
||||
}
|
||||
|
||||
+15
-15
@@ -21,7 +21,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -131,22 +130,23 @@ func (c *muxClient) traceRoundtrip(ctx context.Context, t *tracer, h HandlerID,
|
||||
}
|
||||
// If the context contains a TraceParamsKey, add it to the trace path.
|
||||
v := ctx.Value(TraceParamsKey{})
|
||||
if p, ok := v.(*MSS); ok && p != nil {
|
||||
trace.Path += p.ToQuery()
|
||||
trace.HTTP.ReqInfo.Path = trace.Path
|
||||
} else if p, ok := v.(map[string]string); ok {
|
||||
m := MSS(p)
|
||||
// Should match SingleHandler.Call checks.
|
||||
switch typed := v.(type) {
|
||||
case *MSS:
|
||||
trace.Path += typed.ToQuery()
|
||||
case map[string]string:
|
||||
m := MSS(typed)
|
||||
trace.Path += m.ToQuery()
|
||||
trace.HTTP.ReqInfo.Path = trace.Path
|
||||
} else if v != nil {
|
||||
// Print exported fields as single request to path.
|
||||
obj := fmt.Sprintf("%+v", v)
|
||||
if len(obj) > 1024 {
|
||||
obj = obj[:1024] + "..."
|
||||
}
|
||||
trace.Path = fmt.Sprintf("%s?req=%s", trace.Path, url.QueryEscape(obj))
|
||||
trace.HTTP.ReqInfo.Path = trace.Path
|
||||
case *URLValues:
|
||||
trace.Path += typed.Values().Encode()
|
||||
case *NoPayload, *Bytes:
|
||||
trace.Path = fmt.Sprintf("%s?payload=%T", trace.Path, typed)
|
||||
case string:
|
||||
trace.Path = fmt.Sprintf("%s?%s", trace.Path, typed)
|
||||
default:
|
||||
}
|
||||
trace.HTTP.ReqInfo.Path = trace.Path
|
||||
|
||||
t.Publisher.Publish(trace)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
+223
-2
@@ -22,10 +22,19 @@ import (
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
)
|
||||
|
||||
// Recycler will override the internal reuse in typed handlers.
|
||||
// When this is supported, the handler will not do internal pooling of objects,
|
||||
// call Recycle() when the object is no longer needed.
|
||||
// The recycler should handle nil pointers.
|
||||
type Recycler interface {
|
||||
Recycle()
|
||||
}
|
||||
|
||||
// MSS is a map[string]string that can be serialized.
|
||||
// It is not very efficient, but it is only used for easy parameter passing.
|
||||
type MSS map[string]string
|
||||
@@ -38,6 +47,14 @@ func (m *MSS) Get(key string) string {
|
||||
return (*m)[key]
|
||||
}
|
||||
|
||||
// Set a key, value pair.
|
||||
func (m *MSS) Set(key, value string) {
|
||||
if m == nil {
|
||||
*m = mssPool.Get().(map[string]string)
|
||||
}
|
||||
(*m)[key] = value
|
||||
}
|
||||
|
||||
// UnmarshalMsg deserializes m from the provided byte slice and returns the
|
||||
// remainder of bytes.
|
||||
func (m *MSS) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
@@ -110,7 +127,10 @@ func (m *MSS) Msgsize() int {
|
||||
|
||||
// NewMSS returns a new MSS.
|
||||
func NewMSS() *MSS {
|
||||
m := MSS(make(map[string]string))
|
||||
m := MSS(mssPool.Get().(map[string]string))
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
@@ -120,6 +140,20 @@ func NewMSSWith(m map[string]string) *MSS {
|
||||
return &m2
|
||||
}
|
||||
|
||||
var mssPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make(map[string]string, 5)
|
||||
},
|
||||
}
|
||||
|
||||
// Recycle the underlying map.
|
||||
func (m *MSS) Recycle() {
|
||||
if m != nil && *m != nil {
|
||||
mssPool.Put(map[string]string(*m))
|
||||
*m = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ToQuery constructs a URL query string from the MSS, including "?" if there are any keys.
|
||||
func (m MSS) ToQuery() string {
|
||||
if len(m) == 0 {
|
||||
@@ -146,17 +180,36 @@ func (m MSS) ToQuery() string {
|
||||
}
|
||||
|
||||
// NewBytes returns a new Bytes.
|
||||
// A slice is preallocated.
|
||||
func NewBytes() *Bytes {
|
||||
b := Bytes(GetByteBuffer()[:0])
|
||||
return &b
|
||||
}
|
||||
|
||||
// NewBytesWith returns a new Bytes with the provided content.
|
||||
// When sent as a parameter, the caller gives up ownership of the byte slice.
|
||||
// When returned as response, the handler also gives up ownership of the byte slice.
|
||||
func NewBytesWith(b []byte) *Bytes {
|
||||
bb := Bytes(b)
|
||||
return &bb
|
||||
}
|
||||
|
||||
// NewBytesWithCopyOf returns a new byte slice with a copy of the provided content.
|
||||
func NewBytesWithCopyOf(b []byte) *Bytes {
|
||||
if b == nil {
|
||||
bb := Bytes(nil)
|
||||
return &bb
|
||||
}
|
||||
if len(b) < maxBufferSize {
|
||||
bb := NewBytes()
|
||||
*bb = append(*bb, b...)
|
||||
return bb
|
||||
}
|
||||
bb := Bytes(make([]byte, len(b)))
|
||||
copy(bb, b)
|
||||
return &bb
|
||||
}
|
||||
|
||||
// Bytes provides a byte slice that can be serialized.
|
||||
type Bytes []byte
|
||||
|
||||
@@ -167,6 +220,9 @@ func (b *Bytes) UnmarshalMsg(bytes []byte) ([]byte, error) {
|
||||
return bytes, errors.New("Bytes: UnmarshalMsg on nil pointer")
|
||||
}
|
||||
if bytes, err := msgp.ReadNilBytes(bytes); err == nil {
|
||||
if *b != nil {
|
||||
PutByteBuffer(*b)
|
||||
}
|
||||
*b = nil
|
||||
return bytes, nil
|
||||
}
|
||||
@@ -178,7 +234,15 @@ func (b *Bytes) UnmarshalMsg(bytes []byte) ([]byte, error) {
|
||||
*b = (*b)[:len(val)]
|
||||
copy(*b, val)
|
||||
} else {
|
||||
*b = append(make([]byte, 0, len(val)), val...)
|
||||
if cap(*b) == 0 && len(val) <= maxBufferSize {
|
||||
*b = GetByteBuffer()[:0]
|
||||
} else {
|
||||
PutByteBuffer(*b)
|
||||
*b = make([]byte, 0, len(val))
|
||||
}
|
||||
in := *b
|
||||
in = append(in[:0], val...)
|
||||
*b = in
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
@@ -198,3 +262,160 @@ func (b *Bytes) Msgsize() int {
|
||||
}
|
||||
return msgp.ArrayHeaderSize + len(*b)
|
||||
}
|
||||
|
||||
// Recycle puts the Bytes back into the pool.
|
||||
func (b *Bytes) Recycle() {
|
||||
if b != nil && *b != nil {
|
||||
*b = (*b)[:0]
|
||||
PutByteBuffer(*b)
|
||||
*b = nil
|
||||
}
|
||||
}
|
||||
|
||||
// URLValues can be used for url.Values.
|
||||
type URLValues map[string][]string
|
||||
|
||||
var urlValuesPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make(map[string][]string, 10)
|
||||
},
|
||||
}
|
||||
|
||||
// NewURLValues returns a new URLValues.
|
||||
func NewURLValues() *URLValues {
|
||||
u := URLValues(urlValuesPool.Get().(map[string][]string))
|
||||
return &u
|
||||
}
|
||||
|
||||
// NewURLValuesWith returns a new URLValues with the provided content.
|
||||
func NewURLValuesWith(values map[string][]string) *URLValues {
|
||||
u := URLValues(values)
|
||||
return &u
|
||||
}
|
||||
|
||||
// Values returns the url.Values.
|
||||
// If u is nil, an empty url.Values is returned.
|
||||
// The values are a shallow copy of the underlying map.
|
||||
func (u *URLValues) Values() url.Values {
|
||||
if u == nil {
|
||||
return url.Values{}
|
||||
}
|
||||
return url.Values(*u)
|
||||
}
|
||||
|
||||
// Recycle the underlying map.
|
||||
func (u *URLValues) Recycle() {
|
||||
if *u != nil {
|
||||
for key := range *u {
|
||||
delete(*u, key)
|
||||
}
|
||||
val := map[string][]string(*u)
|
||||
urlValuesPool.Put(val)
|
||||
*u = nil
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (u URLValues) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, u.Msgsize())
|
||||
o = msgp.AppendMapHeader(o, uint32(len(u)))
|
||||
for zb0006, zb0007 := range u {
|
||||
o = msgp.AppendString(o, zb0006)
|
||||
o = msgp.AppendArrayHeader(o, uint32(len(zb0007)))
|
||||
for zb0008 := range zb0007 {
|
||||
o = msgp.AppendString(o, zb0007[zb0008])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalMsg implements msgp.Unmarshaler
|
||||
func (u *URLValues) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
var zb0004 uint32
|
||||
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
if *u == nil {
|
||||
*u = urlValuesPool.Get().(map[string][]string)
|
||||
}
|
||||
if len(*u) > 0 {
|
||||
for key := range *u {
|
||||
delete(*u, key)
|
||||
}
|
||||
}
|
||||
|
||||
for zb0004 > 0 {
|
||||
var zb0001 string
|
||||
var zb0002 []string
|
||||
zb0004--
|
||||
zb0001, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err)
|
||||
return
|
||||
}
|
||||
var zb0005 uint32
|
||||
zb0005, bts, err = msgp.ReadArrayHeaderBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0001)
|
||||
return
|
||||
}
|
||||
if cap(zb0002) >= int(zb0005) {
|
||||
zb0002 = zb0002[:zb0005]
|
||||
} else {
|
||||
zb0002 = make([]string, zb0005)
|
||||
}
|
||||
for zb0003 := range zb0002 {
|
||||
zb0002[zb0003], bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, zb0001, zb0003)
|
||||
return
|
||||
}
|
||||
}
|
||||
(*u)[zb0001] = zb0002
|
||||
}
|
||||
o = bts
|
||||
return
|
||||
}
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (u URLValues) Msgsize() (s int) {
|
||||
s = msgp.MapHeaderSize
|
||||
if u != nil {
|
||||
for zb0006, zb0007 := range u {
|
||||
_ = zb0007
|
||||
s += msgp.StringPrefixSize + len(zb0006) + msgp.ArrayHeaderSize
|
||||
for zb0008 := range zb0007 {
|
||||
s += msgp.StringPrefixSize + len(zb0007[zb0008])
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// NoPayload is a type that can be used for handlers that do not use a payload.
|
||||
type NoPayload struct{}
|
||||
|
||||
// Msgsize returns 0.
|
||||
func (p NoPayload) Msgsize() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// UnmarshalMsg satisfies the interface, but is a no-op.
|
||||
func (NoPayload) UnmarshalMsg(bytes []byte) ([]byte, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// MarshalMsg satisfies the interface, but is a no-op.
|
||||
func (NoPayload) MarshalMsg(bytes []byte) ([]byte, error) {
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// NewNoPayload returns an empty NoPayload struct.
|
||||
func NewNoPayload() NoPayload {
|
||||
return NoPayload{}
|
||||
}
|
||||
|
||||
// Recycle is a no-op.
|
||||
func (NoPayload) Recycle() {}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user