Compare commits

..

20 Commits

Author SHA1 Message Date
Harshavardhana 2dc917e87f maxConcurrent must be set only once per node (#18303) 2023-10-23 21:42:36 -07:00
Aditya Manthramurthy 0a284a1a10 fix: SR: Add more info when IAM config differs (#18302)
Provide details on what IAM info mismatched when the validation fails
2023-10-23 21:16:40 -07:00
Harshavardhana 5c8339e1e8 fix: veeam SOS API to higher layers (#18287)
- support populating usage info from scanner info
- support populating quota for the bucket via quota
  settings for the bucket
2023-10-23 13:55:45 -07:00
Harshavardhana fd37418da2 fix: allow server not initialized error to be retried (#18300)
Since relaxing quorum the error across pools
for ListBuckets(), GetBucketInfo() we hit a
situation where loading IAM could potentially
return an error for second pool that server
is not initialized.

We need to handle this, let the pool come online
and retry transparently - this PR fixes that.
2023-10-23 12:30:20 -07:00
Harshavardhana bbfea29c2b use object modTime for the event sequencer ID (#18285)
always set modTime after lock is acquired in
completemultipart stage to make sure that the
modTime is not racy.
2023-10-20 19:28:05 -07:00
Harshavardhana aa703dc903 relax write quorum requirement for ListBuckets()/HeadBucket() (#18288)
Also fix error handling for HeadBucket() to be pool specific
2023-10-20 17:50:21 -07:00
Krishnan Parthasarathi 8cd80fec8c Add unit test for lifecycle.FilterRules (#18284) 2023-10-19 21:33:28 -07:00
Harshavardhana 780882efcf do not check for query params to be signed headers (#18283)
x-amz-signed-headers is meant for HTTP headers only
not for query params, using that to verify things
further can lead to failure.

The generated presigned URL with custom metadata
is already kosher (tamper proof).

fixes #18281
2023-10-19 21:32:49 -07:00
dependabot[bot] c5636143c6 build(deps): bump github.com/nats-io/nats-server/v2 from 2.9.20 to 2.9.23 (#18282)
build(deps): bump github.com/nats-io/nats-server/v2

Bumps [github.com/nats-io/nats-server/v2](https://github.com/nats-io/nats-server) from 2.9.20 to 2.9.23.
- [Release notes](https://github.com/nats-io/nats-server/releases)
- [Changelog](https://github.com/nats-io/nats-server/blob/main/.goreleaser.yml)
- [Commits](https://github.com/nats-io/nats-server/compare/v2.9.20...v2.9.23)

---
updated-dependencies:
- dependency-name: github.com/nats-io/nats-server/v2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-19 16:40:59 -07:00
Klaus Post ba6218b354 fix: resource metrics "concurrent map iteration and map write" (#18273)
`resourceMetricsMap` has no protection against concurrent reads and writes.

Add a mutex and don't use maps from the last iteration.

Bug introduced in #18057

Fixes #18271
2023-10-18 13:28:50 -07:00
Harshavardhana 8e32de3ba9 cache DiskInfo() metrics call separately (#18270) 2023-10-18 11:17:32 -07:00
Klaus Post e37508fb8f fix: linter errors in Windows specific code (#18276) 2023-10-18 11:08:15 -07:00
Klaus Post b46a717425 Remove unused config migration (#18277)
None of the migration is called. Remove dead code.
2023-10-18 11:05:24 -07:00
Klaus Post 7926df0b80 Fix globalDeploymentID race (#18275)
globalDeploymentID was being read while it was being set.

Fixes race:

```
WARNING: DATA RACE
Write at 0x0000079605a0 by main goroutine:
  github.com/minio/minio/cmd.connectLoadInitFormats()
      github.com/minio/minio/cmd/prepare-storage.go:269 +0x14f0
  github.com/minio/minio/cmd.waitForFormatErasure()
      github.com/minio/minio/cmd/prepare-storage.go:294 +0x21d
...

Previous read at 0x0000079605a0 by goroutine 105:
  github.com/minio/minio/cmd.newContext()
      github.com/minio/minio/cmd/utils.go:817 +0x31e
  github.com/minio/minio/cmd.adminMiddleware.func1()
      github.com/minio/minio/cmd/admin-router.go:110 +0x96
  net/http.HandlerFunc.ServeHTTP()
      net/http/server.go:2136 +0x47
  github.com/minio/minio/cmd.setBucketForwardingMiddleware.func1()
      github.com/minio/minio/cmd/generic-handlers.go:460 +0xb1a
  net/http.HandlerFunc.ServeHTTP()
      net/http/server.go:2136 +0x47
...
```
2023-10-18 08:06:57 -07:00
Krishnan Parthasarathi 557df666fd Don't skip rules with ExpiredObjectDeleteMarker (#18256) 2023-10-16 22:46:46 -07:00
Harshavardhana f91b257f50 choose different max_concurrent requests per drive based on HDD/NVMe (#18254)
currently the default for all drives is 512, which is a lot
for HDDs the recent testing has revealed moving this to 32
for HDDs seems like a fair value.
2023-10-16 17:18:13 -07:00
Aditya Manthramurthy 28a2d1eb3d Allow OpenID ARN resource ID to start with a - (#18255) 2023-10-16 13:50:51 -07:00
Minio Trusted a0ae1489e5 Update yaml files to latest version RELEASE.2023-10-16T04-13-43Z 2023-10-16 05:58:49 +00:00
Harshavardhana edfb310a59 fix: always load ENVs from files first as soon as server starts (#18247)
This is a regression from #18231, however reading from ENV files
must happen well before any parsing logic is invoked.
2023-10-15 21:13:43 -07:00
Minio Trusted a2312028b9 Update yaml files to latest version RELEASE.2023-10-14T05-17-22Z 2023-10-14 06:27:13 +00:00
44 changed files with 614 additions and 3765 deletions
+4 -4
View File
@@ -1001,7 +1001,7 @@ func (a adminAPIHandlers) HealHandler(w http.ResponseWriter, r *http.Request) {
Message: hr.errBody,
Resource: r.URL.Path,
RequestID: w.Header().Get(xhttp.AmzRequestID),
HostID: globalDeploymentID,
HostID: globalDeploymentID(),
})
}
if !started {
@@ -1982,7 +1982,7 @@ func getServerInfo(ctx context.Context, poolsInfoEnabled bool, r *http.Request)
Domain: domain,
Region: globalSite.Region,
SQSARN: globalEventNotifier.GetARNList(false),
DeploymentID: globalDeploymentID,
DeploymentID: globalDeploymentID(),
Buckets: buckets,
Objects: objects,
Versions: versions,
@@ -2392,7 +2392,7 @@ func (a adminAPIHandlers) HealthInfoHandler(w http.ResponseWriter, r *http.Reque
Version: madmin.HealthInfoVersion,
Minio: madmin.MinioHealthInfo{
Info: madmin.MinioInfo{
DeploymentID: globalDeploymentID,
DeploymentID: globalDeploymentID(),
},
},
}
@@ -2690,7 +2690,7 @@ func getClusterMetaInfo(ctx context.Context) []byte {
ci.Info.NoOfBuckets = dataUsageInfo.BucketsCount
ci.Info.NoOfObjects = dataUsageInfo.ObjectsTotalCount
ci.DeploymentID = globalDeploymentID
ci.DeploymentID = globalDeploymentID()
ci.ClusterName = fmt.Sprintf("%d-servers-%d-disks-%s", ci.Info.NoOfServers, ci.Info.NoOfDrives, ci.Info.MinioVersion)
select {
+6
View File
@@ -310,6 +310,7 @@ const (
ErrSiteReplicationBucketMetaError
ErrSiteReplicationIAMError
ErrSiteReplicationConfigMissing
ErrSiteReplicationIAMConfigMismatch
// Pool rebalance errors
ErrAdminRebalanceAlreadyStarted
@@ -1512,6 +1513,11 @@ var errorCodes = errorCodeMap{
Description: "Site not found in site replication configuration",
HTTPStatusCode: http.StatusBadRequest,
},
ErrSiteReplicationIAMConfigMismatch: {
Code: "XMinioSiteReplicationIAMConfigMismatch",
Description: "IAM configuration mismatch between sites",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAdminRebalanceAlreadyStarted: {
Code: "XMinioAdminRebalanceAlreadyStarted",
Description: "Pool rebalance is already started",
+1 -1
View File
@@ -978,7 +978,7 @@ func writeCustomErrorResponseJSON(ctx context.Context, w http.ResponseWriter, er
BucketName: reqInfo.BucketName,
Key: reqInfo.ObjectName,
RequestID: w.Header().Get(xhttp.AmzRequestID),
HostID: globalDeploymentID,
HostID: globalDeploymentID(),
}
encodedErrorResponse := encodeResponseJSON(errorResponse)
writeResponse(w, err.HTTPStatusCode, encodedErrorResponse, mimeJSON)
+111 -110
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -444,7 +444,7 @@ func genTransitionObjName(bucket string) (string, error) {
return "", err
}
us := u.String()
obj := fmt.Sprintf("%s/%s/%s/%s/%s", globalDeploymentID, bucket, us[0:2], us[2:4], us)
obj := fmt.Sprintf("%s/%s/%s/%s/%s", globalDeploymentID(), bucket, us[0:2], us[2:4], us)
return obj, nil
}
+1 -1
View File
@@ -131,7 +131,7 @@ func performCallhome(ctx context.Context) {
Version: madmin.HealthInfoVersion,
Minio: madmin.MinioHealthInfo{
Info: madmin.MinioInfo{
DeploymentID: globalDeploymentID,
DeploymentID: globalDeploymentID(),
},
},
}
+3 -5
View File
@@ -122,8 +122,8 @@ func init() {
const consolePrefix = "CONSOLE_"
func minioConfigToConsoleFeatures() {
os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID)
os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID)
os.Setenv("CONSOLE_PBKDF_SALT", globalDeploymentID())
os.Setenv("CONSOLE_PBKDF_PASSPHRASE", globalDeploymentID())
if globalMinioEndpoint != "" {
os.Setenv("CONSOLE_MINIO_SERVER", globalMinioEndpoint)
} else {
@@ -191,7 +191,7 @@ func buildOpenIDConsoleConfig() consoleoauth2.OpenIDPCfg {
DisplayName: cfg.DisplayName,
ClientID: cfg.ClientID,
ClientSecret: cfg.ClientSecret,
HMACSalt: globalDeploymentID,
HMACSalt: globalDeploymentID(),
HMACPassphrase: cfg.ClientID,
Scopes: strings.Join(cfg.DiscoveryDoc.ScopesSupported, ","),
Userinfo: cfg.ClaimUserinfo,
@@ -619,8 +619,6 @@ func loadEnvVarsFromFiles() {
}
func handleCommonEnvVars() {
loadEnvVarsFromFiles()
var err error
globalBrowserEnabled, err = config.ParseBool(env.Get(config.EnvBrowser, config.EnableOn))
if err != nil {
File diff suppressed because it is too large Load Diff
-336
View File
@@ -1,336 +0,0 @@
// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"context"
"fmt"
"os"
"testing"
)
// Test if config v1 is purged
func TestServerConfigMigrateV1(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
objLayer, fsDir, err := prepareFS(ctx)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(fsDir)
err = newTestConfig(globalMinioDefaultRegion, objLayer)
if err != nil {
t.Fatalf("Init Test config failed")
}
rootPath := t.TempDir()
globalConfigDir = &ConfigDir{path: rootPath}
globalObjLayerMutex.Lock()
globalObjectAPI = objLayer
globalObjLayerMutex.Unlock()
// Create a V1 config json file and store it
configJSON := "{ \"version\":\"1\", \"accessKeyId\":\"abcde\", \"secretAccessKey\":\"abcdefgh\"}"
configPath := rootPath + "/fsUsers.json"
if err := os.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Fire a migrateConfig()
if err := migrateConfig(); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Check if config v1 is removed from filesystem
if _, err := os.Stat(configPath); err == nil || !osIsNotExist(err) {
t.Fatal("Config V1 file is not purged")
}
// Initialize server config and check again if everything is fine
if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err)
}
}
// Test if all migrate code returns nil when config file does not
// exist
func TestServerConfigMigrateInexistentConfig(t *testing.T) {
globalConfigDir = &ConfigDir{path: t.TempDir()}
if err := migrateV2ToV3(); err != nil {
t.Fatal("migrate v2 to v3 should succeed when no config file is found")
}
if err := migrateV3ToV4(); err != nil {
t.Fatal("migrate v3 to v4 should succeed when no config file is found")
}
if err := migrateV4ToV5(); err != nil {
t.Fatal("migrate v4 to v5 should succeed when no config file is found")
}
if err := migrateV5ToV6(); err != nil {
t.Fatal("migrate v5 to v6 should succeed when no config file is found")
}
if err := migrateV6ToV7(); err != nil {
t.Fatal("migrate v6 to v7 should succeed when no config file is found")
}
if err := migrateV7ToV8(); err != nil {
t.Fatal("migrate v7 to v8 should succeed when no config file is found")
}
if err := migrateV8ToV9(); err != nil {
t.Fatal("migrate v8 to v9 should succeed when no config file is found")
}
if err := migrateV9ToV10(); err != nil {
t.Fatal("migrate v9 to v10 should succeed when no config file is found")
}
if err := migrateV10ToV11(); err != nil {
t.Fatal("migrate v10 to v11 should succeed when no config file is found")
}
if err := migrateV11ToV12(); err != nil {
t.Fatal("migrate v11 to v12 should succeed when no config file is found")
}
if err := migrateV12ToV13(); err != nil {
t.Fatal("migrate v12 to v13 should succeed when no config file is found")
}
if err := migrateV13ToV14(); err != nil {
t.Fatal("migrate v13 to v14 should succeed when no config file is found")
}
if err := migrateV14ToV15(); err != nil {
t.Fatal("migrate v14 to v15 should succeed when no config file is found")
}
if err := migrateV15ToV16(); err != nil {
t.Fatal("migrate v15 to v16 should succeed when no config file is found")
}
if err := migrateV16ToV17(); err != nil {
t.Fatal("migrate v16 to v17 should succeed when no config file is found")
}
if err := migrateV17ToV18(); err != nil {
t.Fatal("migrate v17 to v18 should succeed when no config file is found")
}
if err := migrateV18ToV19(); err != nil {
t.Fatal("migrate v18 to v19 should succeed when no config file is found")
}
if err := migrateV19ToV20(); err != nil {
t.Fatal("migrate v19 to v20 should succeed when no config file is found")
}
if err := migrateV20ToV21(); err != nil {
t.Fatal("migrate v20 to v21 should succeed when no config file is found")
}
if err := migrateV21ToV22(); err != nil {
t.Fatal("migrate v21 to v22 should succeed when no config file is found")
}
if err := migrateV22ToV23(); err != nil {
t.Fatal("migrate v22 to v23 should succeed when no config file is found")
}
if err := migrateV23ToV24(); err != nil {
t.Fatal("migrate v23 to v24 should succeed when no config file is found")
}
if err := migrateV24ToV25(); err != nil {
t.Fatal("migrate v24 to v25 should succeed when no config file is found")
}
if err := migrateV25ToV26(); err != nil {
t.Fatal("migrate v25 to v26 should succeed when no config file is found")
}
if err := migrateV26ToV27(); err != nil {
t.Fatal("migrate v26 to v27 should succeed when no config file is found")
}
if err := migrateV27ToV28(); err != nil {
t.Fatal("migrate v27 to v28 should succeed when no config file is found")
}
}
// Test if a config migration from v2 to v33 is successfully done
func TestServerConfigMigrateV2toV33(t *testing.T) {
rootPath := t.TempDir()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
globalConfigDir = &ConfigDir{path: rootPath}
objLayer, fsDir, err := prepareFS(ctx)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(fsDir)
configPath := rootPath + SlashSeparator + minioConfigFile
// Create a corrupted config file
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\","), 0o644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Fire a migrateConfig()
if err := migrateConfig(); err == nil {
t.Fatal("migration should fail with corrupted config file")
}
accessKey := "accessfoo"
secretKey := "secretfoo"
// Create a V2 config json file and store it
configJSON := "{ \"version\":\"2\", \"credentials\": {\"accessKeyId\":\"" + accessKey + "\", \"secretAccessKey\":\"" + secretKey + "\", \"region\":\"us-east-1\"}, \"mongoLogger\":{\"addr\":\"127.0.0.1:3543\", \"db\":\"foodb\", \"collection\":\"foo\"}, \"syslogLogger\":{\"network\":\"127.0.0.1:543\", \"addr\":\"addr\"}, \"fileLogger\":{\"filename\":\"log.out\"}}"
if err := os.WriteFile(configPath, []byte(configJSON), 0o644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Fire a migrateConfig()
if err := migrateConfig(); err != nil {
t.Fatal("Unexpected error: ", err)
}
if err := migrateConfigToMinioSys(objLayer); err != nil {
t.Fatal("Unexpected error: ", err)
}
srvCfg, err := readConfigWithoutMigrate(context.Background(), objLayer)
if err != nil {
t.Fatal("Unexpected error: ", err)
}
if err = saveServerConfig(GlobalContext, objLayer, srvCfg); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Initialize server config and check again if everything is fine
if err := loadConfig(objLayer, nil); err != nil {
t.Fatalf("Unable to initialize from updated config file %s", err)
}
}
// Test if all migrate code returns error with corrupted config files
func TestServerConfigMigrateFaultyConfig(t *testing.T) {
rootPath := t.TempDir()
globalConfigDir = &ConfigDir{path: rootPath}
configPath := rootPath + SlashSeparator + minioConfigFile
// Create a corrupted config file
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\", \"test\":"), 0o644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Test different migrate versions and be sure they are returning an error
if err := migrateV2ToV3(); err == nil {
t.Fatal("migrateConfigV2ToV3() should fail with a corrupted json")
}
if err := migrateV3ToV4(); err == nil {
t.Fatal("migrateConfigV3ToV4() should fail with a corrupted json")
}
if err := migrateV4ToV5(); err == nil {
t.Fatal("migrateConfigV4ToV5() should fail with a corrupted json")
}
if err := migrateV5ToV6(); err == nil {
t.Fatal("migrateConfigV5ToV6() should fail with a corrupted json")
}
if err := migrateV6ToV7(); err == nil {
t.Fatal("migrateConfigV6ToV7() should fail with a corrupted json")
}
if err := migrateV7ToV8(); err == nil {
t.Fatal("migrateConfigV7ToV8() should fail with a corrupted json")
}
if err := migrateV8ToV9(); err == nil {
t.Fatal("migrateConfigV8ToV9() should fail with a corrupted json")
}
if err := migrateV9ToV10(); err == nil {
t.Fatal("migrateConfigV9ToV10() should fail with a corrupted json")
}
if err := migrateV10ToV11(); err == nil {
t.Fatal("migrateConfigV10ToV11() should fail with a corrupted json")
}
if err := migrateV11ToV12(); err == nil {
t.Fatal("migrateConfigV11ToV12() should fail with a corrupted json")
}
if err := migrateV12ToV13(); err == nil {
t.Fatal("migrateConfigV12ToV13() should fail with a corrupted json")
}
if err := migrateV13ToV14(); err == nil {
t.Fatal("migrateConfigV13ToV14() should fail with a corrupted json")
}
if err := migrateV14ToV15(); err == nil {
t.Fatal("migrateConfigV14ToV15() should fail with a corrupted json")
}
if err := migrateV15ToV16(); err == nil {
t.Fatal("migrateConfigV15ToV16() should fail with a corrupted json")
}
if err := migrateV16ToV17(); err == nil {
t.Fatal("migrateConfigV16ToV17() should fail with a corrupted json")
}
if err := migrateV17ToV18(); err == nil {
t.Fatal("migrateConfigV17ToV18() should fail with a corrupted json")
}
if err := migrateV18ToV19(); err == nil {
t.Fatal("migrateConfigV18ToV19() should fail with a corrupted json")
}
if err := migrateV19ToV20(); err == nil {
t.Fatal("migrateConfigV19ToV20() should fail with a corrupted json")
}
if err := migrateV20ToV21(); err == nil {
t.Fatal("migrateConfigV20ToV21() should fail with a corrupted json")
}
if err := migrateV21ToV22(); err == nil {
t.Fatal("migrateConfigV21ToV22() should fail with a corrupted json")
}
if err := migrateV22ToV23(); err == nil {
t.Fatal("migrateConfigV22ToV23() should fail with a corrupted json")
}
if err := migrateV23ToV24(); err == nil {
t.Fatal("migrateConfigV23ToV24() should fail with a corrupted json")
}
if err := migrateV24ToV25(); err == nil {
t.Fatal("migrateConfigV24ToV25() should fail with a corrupted json")
}
if err := migrateV25ToV26(); err == nil {
t.Fatal("migrateConfigV25ToV26() should fail with a corrupted json")
}
if err := migrateV26ToV27(); err == nil {
t.Fatal("migrateConfigV26ToV27() should fail with a corrupted json")
}
if err := migrateV27ToV28(); err == nil {
t.Fatal("migrateConfigV27ToV28() should fail with a corrupted json")
}
}
// Test if all migrate code returns error with corrupted config files
func TestServerConfigMigrateCorruptedConfig(t *testing.T) {
rootPath := t.TempDir()
globalConfigDir = &ConfigDir{path: rootPath}
configPath := rootPath + SlashSeparator + minioConfigFile
for i := 3; i <= 17; i++ {
// Create a corrupted config file
if err := os.WriteFile(configPath, []byte(fmt.Sprintf("{ \"version\":\"%d\", \"credential\": { \"accessKey\": 1 } }", i)),
0o644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Test different migrate versions and be sure they are returning an error
if err := migrateConfig(); err == nil {
t.Fatal("migrateConfig() should fail with a corrupted json")
}
}
// Create a corrupted config file for version '2'.
if err := os.WriteFile(configPath, []byte("{ \"version\":\"2\", \"credentials\": { \"accessKeyId\": 1 } }"), 0o644); err != nil {
t.Fatal("Unexpected error: ", err)
}
// Test different migrate versions and be sure they are returning an error
if err := migrateConfig(); err == nil {
t.Fatal("migrateConfig() should fail with a corrupted json")
}
}
-657
View File
@@ -18,8 +18,6 @@
package cmd
import (
"sync"
"github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/config/compress"
@@ -28,407 +26,10 @@ import (
"github.com/minio/minio/internal/config/notify"
"github.com/minio/minio/internal/config/policy/opa"
"github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/event/target"
"github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/quick"
)
type configV1 struct {
Version string `json:"version"`
AccessKey string `json:"accessKeyId"`
SecretKey string `json:"secretAccessKey"`
}
type configV2 struct {
Version string `json:"version"`
Credentials struct {
AccessKey string `json:"accessKeyId"`
SecretKey string `json:"secretAccessKey"`
Region string `json:"region"`
} `json:"credentials"`
MongoLogger struct {
Addr string `json:"addr"`
DB string `json:"db"`
Collection string `json:"collection"`
} `json:"mongoLogger"`
SyslogLogger struct {
Network string `json:"network"`
Addr string `json:"addr"`
} `json:"syslogLogger"`
FileLogger struct {
Filename string `json:"filename"`
} `json:"fileLogger"`
}
// backendV3 type.
type backendV3 struct {
Type string `json:"type"`
Disk string `json:"disk,omitempty"`
Disks []string `json:"disks,omitempty"`
}
// syslogLogger v3
type syslogLoggerV3 struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
}
// loggerV3 type.
type loggerV3 struct {
Console struct {
Enable bool `json:"enable"`
Level string `json:"level"`
}
File struct {
Enable bool `json:"enable"`
Filename string `json:"fileName"`
Level string `json:"level"`
}
Syslog struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
} `json:"syslog"`
// Add new loggers here.
}
// configV3 server configuration version '3'.
type configV3 struct {
Version string `json:"version"`
// Backend configuration.
Backend backendV3 `json:"backend"`
// http Server configuration.
Addr string `json:"address"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV3 `json:"logger"`
}
// logger type representing version '4' logger config.
type loggerV4 struct {
Console struct {
Enable bool `json:"enable"`
Level string `json:"level"`
} `json:"console"`
File struct {
Enable bool `json:"enable"`
Filename string `json:"fileName"`
Level string `json:"level"`
} `json:"file"`
Syslog struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
} `json:"syslog"`
}
// configV4 server configuration version '4'.
type configV4 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV4 `json:"logger"`
}
// logger type representing version '5' logger config.
type loggerV5 struct {
Console struct {
Enable bool `json:"enable"`
Level string `json:"level"`
} `json:"console"`
File struct {
Enable bool `json:"enable"`
Filename string `json:"fileName"`
Level string `json:"level"`
} `json:"file"`
Syslog struct {
Enable bool `json:"enable"`
Addr string `json:"address"`
Level string `json:"level"`
} `json:"syslog"`
AMQP struct {
Enable bool `json:"enable"`
Level string `json:"level"`
URL string `json:"url"`
Exchange string `json:"exchange"`
RoutingKey string `json:"routingKey"`
ExchangeType string `json:"exchangeType"`
Mandatory bool `json:"mandatory"`
Immediate bool `json:"immediate"`
Durable bool `json:"durable"`
Internal bool `json:"internal"`
NoWait bool `json:"noWait"`
AutoDeleted bool `json:"autoDeleted"`
} `json:"amqp"`
ElasticSearch struct {
Enable bool `json:"enable"`
Level string `json:"level"`
URL string `json:"url"`
Index string `json:"index"`
} `json:"elasticsearch"`
Redis struct {
Enable bool `json:"enable"`
Level string `json:"level"`
Addr string `json:"address"`
Password string `json:"password"`
Key string `json:"key"`
} `json:"redis"`
}
// configV5 server configuration version '5'.
type configV5 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV5 `json:"logger"`
}
// consoleLogger - default logger if not other logging is enabled.
type consoleLoggerV1 struct {
Enable bool `json:"enable"`
Level string `json:"level"`
}
type fileLoggerV1 struct {
Enable bool `json:"enable"`
Filename string `json:"fileName"`
Level string `json:"level"`
}
type loggerV6 struct {
Console consoleLoggerV1 `json:"console"`
File fileLoggerV1 `json:"file"`
Syslog syslogLoggerV3 `json:"syslog"`
}
// configV6 server configuration version '6'.
type configV6 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV6 `json:"logger"`
// Notification queue configuration.
Notify notifierV1 `json:"notify"`
}
// Notifier represents collection of supported notification queues in version
// 1 without NATS streaming.
type notifierV1 struct {
AMQP map[string]target.AMQPArgs `json:"amqp"`
NATS map[string]natsNotifyV1 `json:"nats"`
ElasticSearch map[string]target.ElasticsearchArgs `json:"elasticsearch"`
Redis map[string]target.RedisArgs `json:"redis"`
PostgreSQL map[string]target.PostgreSQLArgs `json:"postgresql"`
Kafka map[string]target.KafkaArgs `json:"kafka"`
}
// Notifier represents collection of supported notification queues in version 2
// with NATS streaming but without webhook.
type notifierV2 struct {
AMQP map[string]target.AMQPArgs `json:"amqp"`
NATS map[string]target.NATSArgs `json:"nats"`
ElasticSearch map[string]target.ElasticsearchArgs `json:"elasticsearch"`
Redis map[string]target.RedisArgs `json:"redis"`
PostgreSQL map[string]target.PostgreSQLArgs `json:"postgresql"`
Kafka map[string]target.KafkaArgs `json:"kafka"`
}
// configV7 server configuration version '7'.
type serverConfigV7 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV6 `json:"logger"`
// Notification queue configuration.
Notify notifierV1 `json:"notify"`
}
// serverConfigV8 server configuration version '8'. Adds NATS notify.Config
// configuration.
type serverConfigV8 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV6 `json:"logger"`
// Notification queue configuration.
Notify notifierV1 `json:"notify"`
}
// serverConfigV9 server configuration version '9'. Adds PostgreSQL
// notify.Config configuration.
type serverConfigV9 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV6 `json:"logger"`
// Notification queue configuration.
Notify notifierV1 `json:"notify"`
}
type loggerV7 struct {
sync.RWMutex
Console consoleLoggerV1 `json:"console"`
File fileLoggerV1 `json:"file"`
}
// serverConfigV10 server configuration version '10' which is like
// version '9' except it drops support of syslog config, and makes the
// RWMutex global (so it does not exist in this struct).
type serverConfigV10 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV7 `json:"logger"`
// Notification queue configuration.
Notify notifierV1 `json:"notify"`
}
// natsNotifyV1 - structure was valid until config V 11
type natsNotifyV1 struct {
Enable bool `json:"enable"`
Address string `json:"address"`
Subject string `json:"subject"`
Username string `json:"username"`
Password string `json:"password"`
Token string `json:"token"`
Secure bool `json:"secure"`
PingInterval int64 `json:"pingInterval"`
}
// serverConfigV11 server configuration version '11' which is like
// version '10' except it adds support for Kafka notifications.
type serverConfigV11 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV7 `json:"logger"`
// Notification queue configuration.
Notify notifierV1 `json:"notify"`
}
// serverConfigV12 server configuration version '12' which is like
// version '11' except it adds support for NATS streaming notifications.
type serverConfigV12 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger loggerV7 `json:"logger"`
// Notification queue configuration.
Notify notifierV2 `json:"notify"`
}
type notifierV3 struct {
AMQP map[string]target.AMQPArgs `json:"amqp"`
Elasticsearch map[string]target.ElasticsearchArgs `json:"elasticsearch"`
Kafka map[string]target.KafkaArgs `json:"kafka"`
MQTT map[string]target.MQTTArgs `json:"mqtt"`
MySQL map[string]target.MySQLArgs `json:"mysql"`
NATS map[string]target.NATSArgs `json:"nats"`
PostgreSQL map[string]target.PostgreSQLArgs `json:"postgresql"`
Redis map[string]target.RedisArgs `json:"redis"`
Webhook map[string]target.WebhookArgs `json:"webhook"`
}
// serverConfigV13 server configuration version '13' which is like
// version '12' except it adds support for webhook notification.
type serverConfigV13 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
// Additional error logging configuration.
Logger *loggerV7 `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV14 server configuration version '14' which is like
// version '13' except it adds support of browser param.
type serverConfigV14 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
// Additional error logging configuration.
Logger *loggerV7 `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV15 server configuration version '15' which is like
// version '14' except it adds mysql support
type serverConfigV15 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
// Additional error logging configuration.
Logger *loggerV7 `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// FileLogger is introduced to workaround the dependency about logrus
type FileLogger struct {
Enable bool `json:"enable"`
@@ -440,264 +41,6 @@ type ConsoleLogger struct {
Enable bool `json:"enable"`
}
// Loggers struct is defined with FileLogger and ConsoleLogger
// although they are removed from logging logic. They are
// kept here just to workaround the dependency migration
// code/logic has on them.
type loggers struct {
sync.RWMutex
Console ConsoleLogger `json:"console"`
File FileLogger `json:"file"`
}
// serverConfigV16 server configuration version '16' which is like
// version '15' except it makes a change to logging configuration.
type serverConfigV16 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
// Additional error logging configuration.
Logger *loggers `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV17 server configuration version '17' which is like
// version '16' except it adds support for "format" parameter in
// database event notification targets: PostgreSQL, MySQL, Redis and
// Elasticsearch.
type serverConfigV17 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
// Additional error logging configuration.
Logger *loggers `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV18 server configuration version '18' which is like
// version '17' except it adds support for "deliveryMode" parameter in
// the AMQP notification target.
type serverConfigV18 struct {
sync.RWMutex
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
// Additional error logging configuration.
Logger *loggers `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV19 server configuration version '19' which is like
// version '18' except it adds support for MQTT notifications.
type serverConfigV19 struct {
sync.RWMutex
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
// Additional error logging configuration.
Logger *loggers `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV20 server configuration version '20' which is like
// version '19' except it adds support for VirtualHostDomain
type serverConfigV20 struct {
sync.RWMutex
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Domain string `json:"domain"`
// Additional error logging configuration.
Logger *loggers `json:"logger"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV21 is just like version '20' without logger field
type serverConfigV21 struct {
sync.RWMutex
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Domain string `json:"domain"`
// Notification queue configuration.
Notify *notifierV3 `json:"notify"`
}
// serverConfigV22 is just like version '21' with added support
// for StorageClass.
type serverConfigV22 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Domain string `json:"domain"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
}
// serverConfigV23 is just like version '22' with addition of cache field.
type serverConfigV23 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Domain string `json:"domain"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
}
// serverConfigV24 is just like version '23', we had to revert
// the changes which were made in 6fb06045028b7a57c37c60a612c8e50735279ab4
type serverConfigV24 struct {
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Domain string `json:"domain"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
}
// serverConfigV25 is just like version '24', stores additionally
// worm variable.
type serverConfigV25 struct {
quick.Config `json:"-"` // ignore interfaces
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Worm config.BoolFlag `json:"worm"`
Domain string `json:"domain"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
}
// serverConfigV26 is just like version '25', stores additionally
// cache max use value in 'cache.Config'.
type serverConfigV26 struct {
quick.Config `json:"-"` // ignore interfaces
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Worm config.BoolFlag `json:"worm"`
Domain string `json:"domain"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
}
// serverConfigV27 is just like version '26', stores additionally
// the logger field
type serverConfigV27 struct {
quick.Config `json:"-"` // ignore interfaces
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Browser config.BoolFlag `json:"browser"`
Worm config.BoolFlag `json:"worm"`
Domain string `json:"domain"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
// Logger configuration
Logger logger.Config `json:"logger"`
}
// serverConfigV28 is just like version '27', additionally
// storing KMS config
type serverConfigV28 struct {
quick.Config `json:"-"` // ignore interfaces
Version string `json:"version"`
// S3 API configuration.
Credential auth.Credentials `json:"credential"`
Region string `json:"region"`
Worm config.BoolFlag `json:"worm"`
// Storage class configuration
StorageClass storageclass.Config `json:"storageclass"`
// Notification queue configuration.
Notify notifierV3 `json:"notify"`
// Logger configuration
Logger logger.Config `json:"logger"`
}
// serverConfigV33 is just like version '32', removes clientID from NATS and MQTT, and adds queueDir, queueLimit in all notification targets.
type serverConfigV33 struct {
quick.Config `json:"-"` // ignore interfaces
+1 -1
View File
@@ -1142,7 +1142,7 @@ func (er erasureObjects) HealObject(ctx context.Context, bucket, object, version
if reqInfo != nil {
newReqInfo = logger.NewReqInfo(reqInfo.RemoteHost, reqInfo.UserAgent, reqInfo.DeploymentID, reqInfo.RequestID, reqInfo.API, bucket, object)
} else {
newReqInfo = logger.NewReqInfo("", "", globalDeploymentID, "", "Heal", bucket, object)
newReqInfo = logger.NewReqInfo("", "", globalDeploymentID(), "", "Heal", bucket, object)
}
healCtx := logger.SetReqInfo(GlobalContext, newReqInfo)
+12 -11
View File
@@ -320,7 +320,7 @@ func (er erasureObjects) ListMultipartUploads(ctx context.Context, bucket, objec
populatedUploadIds.Add(uploadID)
uploads = append(uploads, MultipartInfo{
Object: object,
UploadID: base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%s.%s", globalDeploymentID, uploadID))),
UploadID: base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%s.%s", globalDeploymentID(), uploadID))),
Initiated: fi.ModTime,
})
}
@@ -481,7 +481,7 @@ func (er erasureObjects) newMultipartUpload(ctx context.Context, bucket string,
partsMetadata[index].Metadata = userDefined
}
uploadUUID := mustGetUUID()
uploadID := base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%s.%s", globalDeploymentID, uploadUUID)))
uploadID := base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%s.%s", globalDeploymentID(), uploadUUID)))
uploadIDPath := er.getUploadIDDir(bucket, object, uploadUUID)
// Write updated `xl.meta` to all disks.
@@ -1169,6 +1169,16 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
return oi, err
}
}
// Hold namespace to complete the transaction
lk := er.NewNSLock(bucket, object)
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return oi, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx)
if checksumType.IsSet() {
checksumType |= hash.ChecksumMultipart | hash.ChecksumIncludesMultipart
cs := hash.NewChecksumFromData(checksumType, checksumCombined)
@@ -1212,15 +1222,6 @@ func (er erasureObjects) CompleteMultipartUpload(ctx context.Context, bucket str
}
}
// Hold namespace to complete the transaction
lk := er.NewNSLock(bucket, object)
lkctx, err := lk.GetLock(ctx, globalOperationTimeout)
if err != nil {
return oi, err
}
ctx = lkctx.Context()
defer lk.Unlock(lkctx)
// Remove parts that weren't present in CompleteMultipartUpload request.
for _, curpart := range currentFI.Parts {
// Remove part.meta which is not needed anymore.
-16
View File
@@ -188,14 +188,6 @@ func (er erasureObjects) CopyObject(ctx context.Context, srcBucket, srcObject, d
func (er erasureObjects) GetObjectNInfo(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions) (gr *GetObjectReader, err error) {
auditObjectErasureSet(ctx, object, &er)
// This is a special call attempted first to check for SOS-API calls.
gr, err = veeamSOSAPIGetObject(ctx, bucket, object, rs, opts)
if err == nil {
return gr, nil
}
// reset any error to 'nil'
err = nil
var unlockOnDefer bool
nsUnlocker := func() {}
defer func() {
@@ -451,14 +443,6 @@ func (er erasureObjects) getObjectWithFileInfo(ctx context.Context, bucket, obje
func (er erasureObjects) GetObjectInfo(ctx context.Context, bucket, object string, opts ObjectOptions) (info ObjectInfo, err error) {
auditObjectErasureSet(ctx, object, &er)
// This is a special call attempted first to check for SOS-API calls.
info, err = veeamSOSAPIHeadObject(ctx, bucket, object, opts)
if err == nil {
return info, nil
}
// reset any error to 'nil'
err = nil
if !opts.NoLock {
// Lock the object before reading.
lk := er.NewNSLock(bucket, object)
+20
View File
@@ -795,6 +795,16 @@ func (z *erasureServerPools) GetObjectNInfo(ctx context.Context, bucket, object
return nil, err
}
// This is a special call attempted first to check for SOS-API calls.
gr, err = veeamSOSAPIGetObject(ctx, bucket, object, rs, opts)
if err == nil {
return gr, nil
}
// reset any error to 'nil' and any reader to be 'nil'
gr = nil
err = nil
object = encodeDirObject(object)
if z.SinglePool() {
@@ -924,6 +934,16 @@ func (z *erasureServerPools) GetObjectInfo(ctx context.Context, bucket, object s
return objInfo, err
}
// This is a special call attempted first to check for SOS-API calls.
objInfo, err = veeamSOSAPIHeadObject(ctx, bucket, object, opts)
if err == nil {
return objInfo, nil
}
// reset any error to 'nil', and object info to be empty.
err = nil
objInfo = ObjectInfo{}
object = encodeDirObject(object)
if z.SinglePool() {
+4 -1
View File
@@ -210,6 +210,9 @@ type eventArgs struct {
func (args eventArgs) ToEvent(escape bool) event.Event {
eventTime := UTCNow()
uniqueID := fmt.Sprintf("%X", eventTime.UnixNano())
if !args.Object.ModTime.IsZero() {
uniqueID = fmt.Sprintf("%X", args.Object.ModTime.UnixNano())
}
respElements := map[string]string{
"x-amz-request-id": args.RespElements["requestId"],
@@ -223,7 +226,7 @@ func (args eventArgs) ToEvent(escape bool) event.Event {
}
// Add deployment as part of response elements.
respElements["x-minio-deployment-id"] = globalDeploymentID
respElements["x-minio-deployment-id"] = globalDeploymentID()
if args.RespElements["content-length"] != "" {
respElements["content-length"] = args.RespElements["content-length"]
}
+9 -2
View File
@@ -23,7 +23,6 @@ import (
"net/http"
"os"
"sync"
"sync/atomic"
"time"
"github.com/minio/console/restapi"
@@ -35,6 +34,7 @@ import (
"github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/handlers"
"github.com/minio/minio/internal/kms"
"go.uber.org/atomic"
"github.com/dustin/go-humanize"
"github.com/minio/minio/internal/auth"
@@ -313,7 +313,14 @@ var (
globalAuthZPlugin *polplugin.AuthZPlugin
// Deployment ID - unique per deployment
globalDeploymentID string
globalDeploymentIDPtr atomic.Pointer[string]
globalDeploymentID = func() string {
ptr := globalDeploymentIDPtr.Load()
if ptr == nil {
return ""
}
return *ptr
}
globalAllHealState *allHealState
+4 -14
View File
@@ -112,14 +112,9 @@ func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error {
bootstrapTraceMsg("Load IAM format file")
var iamFmt iamFormat
path := getIAMFormatFilePath()
if err := store.loadIAMConfig(ctx, &iamFmt, path); err != nil {
switch err {
case errConfigNotFound:
// Need to migrate to V1.
default:
// if IAM format
return err
}
if err := store.loadIAMConfig(ctx, &iamFmt, path); err != nil && !errors.Is(err, errConfigNotFound) {
// if IAM format
return err
}
if iamFmt.Version >= iamFormatVersion1 {
@@ -129,12 +124,7 @@ func saveIAMFormat(ctx context.Context, store IAMStorageAPI) error {
bootstrapTraceMsg("Write IAM format file")
// Save iam format to version 1.
if err := store.saveIAMConfig(ctx, newIAMFormatVersion1(), path); err != nil {
logger.LogIf(ctx, err)
return err
}
return nil
return store.saveIAMConfig(ctx, newIAMFormatVersion1(), path)
}
func getGroupInfoPath(group string) string {
+2 -1
View File
@@ -293,6 +293,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
if err := saveIAMFormat(retryCtx, sys.store); err != nil {
if configRetriableErrors(err) {
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v)", err)
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
continue
}
logger.LogIf(ctx, fmt.Errorf("IAM sub-system is partially initialized, unable to write the IAM format: %w", err))
@@ -307,7 +308,7 @@ func (sys *IAMSys) Init(ctx context.Context, objAPI ObjectLayer, etcdClient *etc
if err := sys.Load(retryCtx, true); err != nil {
if configRetriableErrors(err) {
logger.Info("Waiting for all MinIO IAM sub-system to be initialized.. possible cause (%v)", err)
time.Sleep(time.Duration(r.Float64() * float64(5*time.Second)))
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
continue
}
if err != nil {
+12 -6
View File
@@ -74,7 +74,8 @@ const (
var (
resourceCollector *minioResourceCollector
// resourceMetricsMap is a map of subsystem to its metrics
resourceMetricsMap map[MetricSubsystem]ResourceMetrics
resourceMetricsMap map[MetricSubsystem]ResourceMetrics
resourceMetricsMapMu sync.RWMutex
// resourceMetricsHelpMap maps metric name to its help string
resourceMetricsHelpMap map[MetricName]string
resourceMetricsGroups []*MetricsGroup
@@ -159,6 +160,8 @@ func init() {
}
func updateResourceMetrics(subSys MetricSubsystem, name MetricName, val float64, labels map[string]string, isCumulative bool) {
resourceMetricsMapMu.Lock()
defer resourceMetricsMapMu.Unlock()
subsysMetrics, found := resourceMetricsMap[subSys]
if !found {
subsysMetrics = ResourceMetrics{}
@@ -320,7 +323,9 @@ func collectLocalResourceMetrics() {
// startResourceMetricsCollection - starts the job for collecting resource metrics
func startResourceMetricsCollection() {
resourceMetricsMapMu.Lock()
resourceMetricsMap = map[MetricSubsystem]ResourceMetrics{}
resourceMetricsMapMu.Unlock()
metricsTimer := time.NewTimer(resourceMetricsCollectionInterval)
defer metricsTimer.Stop()
@@ -382,12 +387,11 @@ func newMinioResourceCollector(metricsGroups []*MetricsGroup) *minioResourceColl
func prepareResourceMetrics(rm ResourceMetric, subSys MetricSubsystem, requireAvgMax bool) []Metric {
help := resourceMetricsHelpMap[rm.Name]
name := rm.Name
metrics := []Metric{}
metrics := make([]Metric, 0, 3)
metrics = append(metrics, Metric{
Description: getResourceMetricDescription(subSys, name, help),
Value: rm.Current,
VariableLabels: rm.Labels,
VariableLabels: cloneMSS(rm.Labels),
})
if requireAvgMax {
@@ -396,7 +400,7 @@ func prepareResourceMetrics(rm ResourceMetric, subSys MetricSubsystem, requireAv
metrics = append(metrics, Metric{
Description: getResourceMetricDescription(subSys, avgName, avgHelp),
Value: math.Round(rm.Avg*100) / 100,
VariableLabels: rm.Labels,
VariableLabels: cloneMSS(rm.Labels),
})
maxName := MetricName(fmt.Sprintf("%s_max", name))
@@ -404,7 +408,7 @@ func prepareResourceMetrics(rm ResourceMetric, subSys MetricSubsystem, requireAv
metrics = append(metrics, Metric{
Description: getResourceMetricDescription(subSys, maxName, maxHelp),
Value: rm.Max,
VariableLabels: rm.Labels,
VariableLabels: cloneMSS(rm.Labels),
})
}
@@ -429,6 +433,8 @@ func getResourceMetrics() *MetricsGroup {
metrics := []Metric{}
subSystems := []MetricSubsystem{interfaceSubsystem, memSubsystem, driveSubsystem, cpuSubsystem}
resourceMetricsMapMu.RLock()
defer resourceMetricsMapMu.RUnlock()
for _, subSys := range subSystems {
stats, found := resourceMetricsMap[subSys]
if found {
+5 -7
View File
@@ -194,8 +194,6 @@ func delOpts(ctx context.Context, r *http.Request, bucket, object string) (opts
Err: fmt.Errorf("Unable to parse %s, failed with %w", xhttp.MinIOSourceMTime, err),
}
}
} else {
opts.MTime = UTCNow()
}
return opts, nil
}
@@ -225,7 +223,7 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada
}
mtimeStr := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceMTime))
mtime := UTCNow()
var mtime time.Time
if mtimeStr != "" {
mtime, err = time.Parse(time.RFC3339Nano, mtimeStr)
if err != nil {
@@ -237,7 +235,7 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada
}
}
retaintimeStr := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceObjectRetentionTimestamp))
retaintimestmp := mtime
var retaintimestmp time.Time
if retaintimeStr != "" {
retaintimestmp, err = time.Parse(time.RFC3339, retaintimeStr)
if err != nil {
@@ -250,7 +248,7 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada
}
lholdtimeStr := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceObjectLegalHoldTimestamp))
lholdtimestmp := mtime
var lholdtimestmp time.Time
if lholdtimeStr != "" {
lholdtimestmp, err = time.Parse(time.RFC3339, lholdtimeStr)
if err != nil {
@@ -262,7 +260,7 @@ func putOpts(ctx context.Context, r *http.Request, bucket, object string, metada
}
}
tagtimeStr := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceTaggingTimestamp))
taggingtimestmp := mtime
var taggingtimestmp time.Time
if tagtimeStr != "" {
taggingtimestmp, err = time.Parse(time.RFC3339, tagtimeStr)
if err != nil {
@@ -353,7 +351,7 @@ func copySrcOpts(ctx context.Context, r *http.Request, bucket, object string) (O
// get ObjectOptions for CompleteMultipart calls
func completeMultipartOpts(ctx context.Context, r *http.Request, bucket, object string) (opts ObjectOptions, err error) {
mtimeStr := strings.TrimSpace(r.Header.Get(xhttp.MinIOSourceMTime))
mtime := UTCNow()
var mtime time.Time
if mtimeStr != "" {
mtime, err = time.Parse(time.RFC3339Nano, mtimeStr)
if err != nil {
+4 -4
View File
@@ -245,7 +245,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
Key: object,
Resource: r.URL.Path,
RequestID: w.Header().Get(xhttp.AmzRequestID),
HostID: globalDeploymentID,
HostID: globalDeploymentID(),
})
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
} else {
@@ -264,7 +264,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r
Key: object,
Resource: r.URL.Path,
RequestID: w.Header().Get(xhttp.AmzRequestID),
HostID: globalDeploymentID,
HostID: globalDeploymentID(),
})
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
} else {
@@ -1375,7 +1375,7 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
srcTimestamp := dstOpts.ReplicationSourceLegalholdTimestamp
if !srcTimestamp.IsZero() {
ondiskTimestamp, err := time.Parse(lastLegalHoldTimestamp, time.RFC3339Nano)
// update legalhold metadata only if replica timestamp is newer than what's on disk
// update legalhold metadata only if replica timestamp is newer than what's on disk
if err != nil || (err == nil && ondiskTimestamp.Before(srcTimestamp)) {
srcInfo.UserDefined[strings.ToLower(xhttp.AmzObjectLockLegalHold)] = string(legalHold.Status)
srcInfo.UserDefined[ReservedMetadataPrefixLower+ObjectLockRetentionTimestamp] = srcTimestamp.Format(time.RFC3339Nano)
@@ -3141,7 +3141,7 @@ func (api objectAPIHandlers) PostRestoreObjectHandler(w http.ResponseWriter, r *
Key: object,
Resource: r.URL.Path,
RequestID: w.Header().Get(xhttp.AmzRequestID),
HostID: globalDeploymentID,
HostID: globalDeploymentID(),
})
writeResponse(w, serr.HTTPStatusCode(), encodedErrorResponse, mimeXML)
} else {
+4 -4
View File
@@ -80,11 +80,11 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
continue
}
var typ os.FileMode = 0 // regular file
var typ os.FileMode // regular file
switch {
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
// Reparse point is a symlink
fi, err := os.Stat(pathJoin(dirPath, string(name)))
fi, err := os.Stat(pathJoin(dirPath, name))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
@@ -153,7 +153,7 @@ func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err er
switch {
case data.FileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0:
// Reparse point is a symlink
fi, err := os.Stat(pathJoin(dirPath, string(name)))
fi, err := os.Stat(pathJoin(dirPath, name))
if err != nil {
// It got deleted in the meantime, not found
// or returns too many symlinks ignore this
@@ -170,7 +170,7 @@ func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err er
continue
}
case data.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0:
name = name + SlashSeparator
name += SlashSeparator
}
count--
+14 -7
View File
@@ -131,7 +131,6 @@ func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) ([]Bu
g := errgroup.WithNErrs(len(sys.peerClients))
nodeBuckets := make([][]BucketInfo, len(sys.peerClients))
errs := []error{nil}
for idx, client := range sys.peerClients {
idx := idx
@@ -149,7 +148,7 @@ func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) ([]Bu
}, idx)
}
errs = append(errs, g.Wait()...)
errs := g.Wait()
// The list of buckets in a map to avoid duplication
resultMap := make(map[string]BucketInfo)
@@ -161,7 +160,7 @@ func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) ([]Bu
perPoolErrs = append(perPoolErrs, errs[i])
}
}
quorum := len(perPoolErrs)/2 + 1
quorum := len(perPoolErrs) / 2
if poolErr := reduceWriteQuorumErrs(ctx, perPoolErrs, bucketOpIgnoredErrs, quorum); poolErr != nil {
return nil, poolErr
}
@@ -181,7 +180,7 @@ func (sys *S3PeerSys) ListBuckets(ctx context.Context, opts BucketOptions) ([]Bu
continue
}
bucketsMap[bi.Name]++
if bucketsMap[bi.Name] == quorum {
if bucketsMap[bi.Name] >= quorum {
resultMap[bi.Name] = bi
}
}
@@ -223,9 +222,17 @@ func (sys *S3PeerSys) GetBucketInfo(ctx context.Context, bucket string, opts Buc
errs := g.Wait()
quorum := len(sys.peerClients)/2 + 1
if err = reduceReadQuorumErrs(ctx, errs, bucketOpIgnoredErrs, quorum); err != nil {
return BucketInfo{}, toObjectErr(err, bucket)
for poolIdx := 0; poolIdx < sys.poolsCount; poolIdx++ {
perPoolErrs := make([]error, 0, len(sys.peerClients))
for i, client := range sys.peerClients {
if slices.Contains(client.GetPools(), poolIdx) {
perPoolErrs = append(perPoolErrs, errs[i])
}
}
quorum := len(perPoolErrs) / 2
if poolErr := reduceWriteQuorumErrs(ctx, perPoolErrs, bucketOpIgnoredErrs, quorum); poolErr != nil {
return BucketInfo{}, poolErr
}
}
for i, err := range errs {
+1 -1
View File
@@ -351,7 +351,7 @@ func siteNetperf(ctx context.Context, duration time.Duration) madmin.SiteNetPerf
for _, info := range clusterInfos.Sites {
// skip self
if globalDeploymentID == info.DeploymentID {
if globalDeploymentID() == info.DeploymentID {
continue
}
info := info
+3 -3
View File
@@ -225,9 +225,9 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, endpoints Endpo
return nil, nil, err
}
// Assign globalDeploymentID on first run for the
// Assign globalDeploymentID() on first run for the
// minio server managing the first disk
globalDeploymentID = format.ID
globalDeploymentIDPtr.Store(&format.ID)
return storageDisks, format, nil
}
@@ -266,7 +266,7 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, endpoints Endpo
}
}
globalDeploymentID = format.ID
globalDeploymentIDPtr.Store(&format.ID)
if err = formatErasureFixLocalDeploymentID(endpoints, storageDisks, format); err != nil {
logger.LogIf(GlobalContext, err)
+28 -7
View File
@@ -372,6 +372,12 @@ func initAllSubsystems(ctx context.Context) {
}
func configRetriableErrors(err error) bool {
if err == nil {
return false
}
notInitialized := err.Error() == "Server not initialized, please try again"
// Initializing sub-systems needs a retry mechanism for
// the following reasons:
// - Read quorum is lost just after the initialization
@@ -392,7 +398,8 @@ func configRetriableErrors(err error) bool {
errors.As(err, &wquorum) ||
isErrObjectNotFound(err) ||
isErrBucketNotFound(err) ||
errors.Is(err, os.ErrDeadlineExceeded)
errors.Is(err, os.ErrDeadlineExceeded) ||
notInitialized
}
func bootstrapTraceMsg(msg string) {
@@ -579,6 +586,9 @@ func serverMain(ctx *cli.Context) {
setDefaultProfilerRates()
// Always load ENV variables from files first.
loadEnvVarsFromFiles()
// Handle all server command args.
bootstrapTrace("serverHandleCmdArgs", func() {
serverHandleCmdArgs(ctx)
@@ -710,7 +720,7 @@ func serverMain(ctx *cli.Context) {
}
})
xhttp.SetDeploymentID(globalDeploymentID)
xhttp.SetDeploymentID(globalDeploymentID())
xhttp.SetMinIOVersion(Version)
for _, n := range globalNodes {
@@ -718,7 +728,7 @@ func serverMain(ctx *cli.Context) {
if n.IsLocal {
nodeName = globalLocalNodeName
}
nodeNameSum := sha256.Sum256([]byte(nodeName + globalDeploymentID))
nodeNameSum := sha256.Sum256([]byte(nodeName + globalDeploymentID()))
globalNodeNamesHex[hex.EncodeToString(nodeNameSum[:])] = struct{}{}
}
@@ -810,10 +820,12 @@ func serverMain(ctx *cli.Context) {
}()
go func() {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
if !globalDisableFreezeOnBoot {
defer bootstrapTrace("unfreezeServices", unfreezeServices)
t := time.AfterFunc(5*time.Minute, func() {
logger.Info(color.Yellow("WARNING: Taking more time to initialize the config subsystem. Please set '_MINIO_DISABLE_API_FREEZE_ON_BOOT=true' to not freeze the APIs"))
logger.Info(color.Yellow("WARNING: Initializing the config subsystem is taking longer than 5 minutes. Please set '_MINIO_DISABLE_API_FREEZE_ON_BOOT=true' to not freeze the APIs"))
})
defer t.Stop()
}
@@ -861,9 +873,18 @@ func serverMain(ctx *cli.Context) {
var buckets []BucketInfo
// List buckets to initialize bucket metadata sub-sys.
bootstrapTrace("listBuckets", func() {
buckets, err = newObject.ListBuckets(GlobalContext, BucketOptions{})
if err != nil {
logger.LogIf(GlobalContext, fmt.Errorf("Unable to list buckets to initialize bucket metadata sub-system: %w", err))
for {
buckets, err = newObject.ListBuckets(GlobalContext, BucketOptions{})
if err != nil {
if configRetriableErrors(err) {
logger.Info("Waiting for list buckets to succeed to initialize buckets.. possible cause (%v)", err)
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
continue
}
logger.LogIf(GlobalContext, fmt.Errorf("Unable to list buckets to initialize bucket metadata sub-system: %w", err))
}
break
}
})
-10
View File
@@ -273,15 +273,5 @@ func checkMetaHeaders(signedHeadersMap http.Header, r *http.Request) APIErrorCod
}
}
// check values from url, if no http header
for k, val := range r.Form {
if stringsHasPrefixFold(k, "x-amz-meta-") {
if signedHeadersMap.Get(http.CanonicalHeaderKey(k)) == val[0] {
continue
}
return ErrUnsignedHeaders
}
}
return ErrNone
}
-13
View File
@@ -394,17 +394,4 @@ func TestCheckMetaHeaders(t *testing.T) {
if errCode != ErrNone {
t.Fatalf("Expected the APIErrorCode to be %d, but got %d", ErrNone, errCode)
}
// Add extra metadata in url values
r, err = http.NewRequest(http.MethodPut, "http://play.min.io:9000?x-amz-meta-test=test&x-amz-meta-extension=png&x-amz-meta-name=imagepng&x-amz-meta-clone=fail", nil)
if err != nil {
t.Fatal("Unable to create http.Request :", err)
}
r.ParseForm()
// calling the function being tested.
errCode = checkMetaHeaders(signedHeadersMap, r)
if errCode != ErrUnsignedHeaders {
t.Fatalf("Expected the APIErrorCode to be %d, but got %d", ErrUnsignedHeaders, errCode)
}
}
+1 -1
View File
@@ -110,7 +110,7 @@ func (sm *siteResyncMetrics) load(ctx context.Context, objAPI ObjectLayer) error
return nil
}
for _, peer := range info.Sites {
if peer.DeploymentID == globalDeploymentID {
if peer.DeploymentID == globalDeploymentID() {
continue
}
rs, err := loadSiteResyncMetadata(ctx, objAPI, peer.DeploymentID)
+54 -49
View File
@@ -154,6 +154,13 @@ func errSRConfigMissingError(err error) SRError {
}
}
func errSRIAMConfigMismatch(peer1, peer2 string, s1, s2 madmin.IDPSettings) SRError {
return SRError{
Cause: fmt.Errorf("IAM/IDP settings mismatch between %s and %s: %#v vs %#v", peer1, peer2, s1, s2),
Code: ErrSiteReplicationIAMConfigMismatch,
}
}
var errSRObjectLayerNotReady = SRError{
Cause: fmt.Errorf("object layer not ready"),
Code: ErrServerNotInitialized,
@@ -365,7 +372,7 @@ func (c *SiteReplicationSys) getSiteStatuses(ctx context.Context, sites ...madmi
PeerSite: v,
DeploymentID: info.DeploymentID,
Empty: len(buckets) == 0,
self: info.DeploymentID == globalDeploymentID,
self: info.DeploymentID == globalDeploymentID(),
})
}
return
@@ -410,7 +417,7 @@ func (c *SiteReplicationSys) AddPeerClusters(ctx context.Context, psites []madmi
}
}
if selfIdx == -1 {
return madmin.ReplicateAddStatus{}, errSRBackendIssue(fmt.Errorf("global deployment ID %s mismatch, expected one of %s", globalDeploymentID, deploymentIDsSet))
return madmin.ReplicateAddStatus{}, errSRBackendIssue(fmt.Errorf("global deployment ID %s mismatch, expected one of %s", globalDeploymentID(), deploymentIDsSet))
}
if !currDeploymentIDsSet.IsEmpty() {
// If current cluster is already SR enabled and no new site being added ,fail.
@@ -424,13 +431,10 @@ func (c *SiteReplicationSys) AddPeerClusters(ctx context.Context, psites []madmi
}
// validate that all clusters are using the same IDP settings.
pass, err := c.validateIDPSettings(ctx, sites)
err = c.validateIDPSettings(ctx, sites)
if err != nil {
return madmin.ReplicateAddStatus{}, err
}
if !pass {
return madmin.ReplicateAddStatus{}, errSRInvalidRequest(errors.New("all cluster sites must have the same IAM/IDP settings"))
}
// For this `add` API, either all clusters must be empty or the local
// cluster must be the only one having some buckets.
@@ -569,7 +573,7 @@ func (c *SiteReplicationSys) AddPeerClusters(ctx context.Context, psites []madmi
func (c *SiteReplicationSys) PeerJoinReq(ctx context.Context, arg madmin.SRPeerJoinReq) error {
var ourName string
for d, p := range arg.Peers {
if d == globalDeploymentID {
if d == globalDeploymentID() {
ourName = p.Name
break
}
@@ -619,7 +623,7 @@ func (c *SiteReplicationSys) GetIDPSettings(ctx context.Context) madmin.IDPSetti
return s
}
func (c *SiteReplicationSys) validateIDPSettings(ctx context.Context, peers []PeerSiteInfo) (bool, error) {
func (c *SiteReplicationSys) validateIDPSettings(ctx context.Context, peers []PeerSiteInfo) error {
s := make([]madmin.IDPSettings, 0, len(peers))
for _, v := range peers {
if v.self {
@@ -629,22 +633,23 @@ func (c *SiteReplicationSys) validateIDPSettings(ctx context.Context, peers []Pe
admClient, err := getAdminClient(v.Endpoint, v.AccessKey, v.SecretKey)
if err != nil {
return false, errSRPeerResp(fmt.Errorf("unable to create admin client for %s: %w", v.Name, err))
return errSRPeerResp(fmt.Errorf("unable to create admin client for %s: %w", v.Name, err))
}
is, err := admClient.SRPeerGetIDPSettings(ctx)
if err != nil {
return false, errSRPeerResp(fmt.Errorf("unable to fetch IDP settings from %s: %v", v.Name, err))
return errSRPeerResp(fmt.Errorf("unable to fetch IDP settings from %s: %v", v.Name, err))
}
s = append(s, is)
}
for i := 1; i < len(s); i++ {
if !reflect.DeepEqual(s[i], s[0]) {
return false, nil
return errSRIAMConfigMismatch(peers[0].Name, peers[i].Name, s[0], s[i])
}
}
return true, nil
return nil
}
// Netperf for site-replication net perf
@@ -658,7 +663,7 @@ func (c *SiteReplicationSys) Netperf(ctx context.Context, duration time.Duration
for _, info := range infos.Sites {
info := info
// will call siteNetperf, means call others's adminAPISiteReplicationDevNull
if globalDeploymentID == info.DeploymentID {
if globalDeploymentID() == info.DeploymentID {
wg.Add(1)
go func() {
defer wg.Done()
@@ -1090,7 +1095,7 @@ func (c *SiteReplicationSys) PeerBucketConfigureReplHandler(ctx context.Context,
defer c.RUnlock()
errMap := make(map[string]error, len(c.state.Peers))
for d, peer := range c.state.Peers {
if d == globalDeploymentID {
if d == globalDeploymentID() {
continue
}
errMap[d] = configurePeerFn(d, peer)
@@ -2112,7 +2117,7 @@ func (c *SiteReplicationSys) concDo(selfActionFn func() error, peerActionFn func
for i := range depIDs {
go func(i int) {
defer wg.Done()
if depIDs[i] == globalDeploymentID {
if depIDs[i] == globalDeploymentID() {
if selfActionFn != nil {
errs[i] = selfActionFn()
}
@@ -2193,11 +2198,11 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
for _, v := range info.Sites {
wg.Add(1)
if v.DeploymentID == globalDeploymentID {
if v.DeploymentID == globalDeploymentID() {
go func() {
defer wg.Done()
err := c.RemoveRemoteTargetsForEndpoint(ctx, objectAPI, rmvEndpoints, false)
errs[globalDeploymentID] = err
errs[globalDeploymentID()] = err
}()
continue
}
@@ -2209,7 +2214,7 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
return
}
// set the requesting site's deploymentID for verification of peer request
rreq.RequestingDepID = globalDeploymentID
rreq.RequestingDepID = globalDeploymentID()
if _, err = admClient.SRPeerRemove(ctx, rreq); err != nil {
if errors.Is(err, errMissingSRConfig) {
// ignore if peer is already removed.
@@ -2223,7 +2228,7 @@ func (c *SiteReplicationSys) RemovePeerCluster(ctx context.Context, objectAPI Ob
wg.Wait()
errdID := ""
selfTgtsDeleted := errs[globalDeploymentID] == nil // true if all remote targets and replication config cleared successfully on local cluster
selfTgtsDeleted := errs[globalDeploymentID()] == nil // true if all remote targets and replication config cleared successfully on local cluster
for dID, err := range errs {
if err != nil {
@@ -2311,7 +2316,7 @@ func (c *SiteReplicationSys) InternalRemoveReq(ctx context.Context, objectAPI Ob
for _, p := range c.state.Peers {
peerMap[p.Name] = p
if p.DeploymentID == globalDeploymentID {
if p.DeploymentID == globalDeploymentID() {
ourName = p.Name
}
updatedPeers[p.DeploymentID] = p
@@ -2327,7 +2332,7 @@ func (c *SiteReplicationSys) InternalRemoveReq(ctx context.Context, objectAPI Ob
if !ok {
return errMissingSRConfig
}
if info.DeploymentID == globalDeploymentID {
if info.DeploymentID == globalDeploymentID() {
unlinkSelf = true
continue
}
@@ -2608,7 +2613,7 @@ func (c *SiteReplicationSys) siteReplicationStatus(ctx context.Context, objAPI O
if err != nil {
return err
}
sris[depIdx[globalDeploymentID]] = srInfo
sris[depIdx[globalDeploymentID()]] = srInfo
return nil
},
func(deploymentID string, p madmin.PeerInfo) error {
@@ -3354,7 +3359,7 @@ func (c *SiteReplicationSys) SiteReplicationMetaInfo(ctx context.Context, objAPI
if !c.enabled {
return info, nil
}
info.DeploymentID = globalDeploymentID
info.DeploymentID = globalDeploymentID()
if opts.Buckets || opts.Entity == madmin.SRBucketEntity {
var (
buckets []BucketInfo
@@ -3611,7 +3616,7 @@ func (c *SiteReplicationSys) EditPeerCluster(ctx context.Context, peer madmin.Pe
admClient *madmin.AdminClient
)
if globalDeploymentID == peer.DeploymentID && !peer.SyncState.Empty() && !peer.DefaultBandwidth.IsSet {
if globalDeploymentID() == peer.DeploymentID && !peer.SyncState.Empty() && !peer.DefaultBandwidth.IsSet {
return madmin.ReplicateEditStatus{}, errSRInvalidRequest(fmt.Errorf("a peer cluster, rather than the local cluster (endpoint=%s, deployment-id=%s) needs to be specified while setting a 'sync' replication mode", peer.Endpoint, peer.DeploymentID))
}
@@ -3665,7 +3670,7 @@ func (c *SiteReplicationSys) EditPeerCluster(ctx context.Context, peer madmin.Pe
var wg sync.WaitGroup
for dID, v := range state.Peers {
if v.DeploymentID == globalDeploymentID {
if v.DeploymentID == globalDeploymentID() {
continue
}
wg.Add(1)
@@ -3792,7 +3797,7 @@ func (c *SiteReplicationSys) PeerEditReq(ctx context.Context, arg madmin.PeerInf
}
c.state.Peers[arg.DeploymentID] = p
}
if p.DeploymentID == globalDeploymentID {
if p.DeploymentID == globalDeploymentID() {
ourName = p.Name
}
}
@@ -3993,7 +3998,7 @@ func (c *SiteReplicationSys) healTagMetadata(ctx context.Context, objAPI ObjectL
if isBucketMetadataEqual(latestTaggingConfig, bStatus.meta.Tags) {
continue
}
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketTaggingConfig, latestTaggingConfigBytes); err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to heal tagging metadata from peer site %s : %w", latestPeerName, err))
}
@@ -4057,7 +4062,7 @@ func (c *SiteReplicationSys) healBucketPolicies(ctx context.Context, objAPI Obje
if strings.EqualFold(string(latestIAMPolicy), string(bStatus.meta.Policy)) {
continue
}
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketPolicyConfig, latestIAMPolicy); err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to heal bucket policy metadata from peer site %s : %w", latestPeerName, err))
}
@@ -4132,7 +4137,7 @@ func (c *SiteReplicationSys) healBucketQuotaConfig(ctx context.Context, objAPI O
if isBucketMetadataEqual(latestQuotaConfig, bStatus.meta.QuotaConfig) {
continue
}
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketQuotaConfigFile, latestQuotaConfigBytes); err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to heal quota metadata from peer site %s : %w", latestPeerName, err))
}
@@ -4207,7 +4212,7 @@ func (c *SiteReplicationSys) healVersioningMetadata(ctx context.Context, objAPI
if isBucketMetadataEqual(latestVersioningConfig, bStatus.meta.Versioning) {
continue
}
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketVersioningConfig, latestVersioningConfigBytes); err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to heal versioning metadata from peer site %s : %w", latestPeerName, err))
}
@@ -4282,7 +4287,7 @@ func (c *SiteReplicationSys) healSSEMetadata(ctx context.Context, objAPI ObjectL
if isBucketMetadataEqual(latestSSEConfig, bStatus.meta.SSEConfig) {
continue
}
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
if _, err := globalBucketMetadataSys.Update(ctx, bucket, bucketSSEConfig, latestSSEConfigBytes); err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to heal sse metadata from peer site %s : %w", latestPeerName, err))
}
@@ -4357,7 +4362,7 @@ func (c *SiteReplicationSys) healOLockConfigMetadata(ctx context.Context, objAPI
if isBucketMetadataEqual(latestObjLockConfig, bStatus.meta.ObjectLockConfig) {
continue
}
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
if _, err := globalBucketMetadataSys.Update(ctx, bucket, objectLockConfig, latestObjLockConfigBytes); err != nil {
logger.LogIf(ctx, fmt.Errorf("Unable to heal objectlock config metadata from peer site %s : %w", latestPeerName, err))
}
@@ -4445,7 +4450,7 @@ func (c *SiteReplicationSys) healBucket(ctx context.Context, objAPI ObjectLayer,
bStatus := info.BucketStats[bucket][latestID].meta
isMakeBucket := len(missingB) > 0
deleteOp := NoOp
if latestID != globalDeploymentID {
if latestID != globalDeploymentID() {
return nil
}
if lastUpdate.Equal(bStatus.DeletedAt) {
@@ -4479,7 +4484,7 @@ func (c *SiteReplicationSys) healBucket(ctx context.Context, objAPI ObjectLayer,
}
for _, dID := range missingB {
peerName := info.Sites[dID].Name
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
err := c.PeerBucketMakeWithVersioningHandler(ctx, bucket, opts)
if err != nil {
return c.annotateErr(makeBucketWithVersion, fmt.Errorf("error healing bucket for site replication %w from %s -> %s",
@@ -4512,7 +4517,7 @@ func (c *SiteReplicationSys) healBucket(ctx context.Context, objAPI ObjectLayer,
if deleteOp == Purge {
for _, dID := range missingB {
peerName := info.Sites[dID].Name
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
c.purgeDeletedBucket(ctx, objAPI, bucket)
} else {
admClient, err := c.getAdminClient(ctx, dID)
@@ -4529,7 +4534,7 @@ func (c *SiteReplicationSys) healBucket(ctx context.Context, objAPI ObjectLayer,
if deleteOp == MarkDelete {
for _, dID := range withB {
peerName := info.Sites[dID].Name
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
err := c.PeerBucketDeleteHandler(ctx, bucket, DeleteBucketOptions{
Force: true,
})
@@ -4685,14 +4690,14 @@ func (c *SiteReplicationSys) healPolicies(ctx context.Context, objAPI ObjectLaye
latestPolicyStat = ss
}
}
if latestID != globalDeploymentID {
if latestID != globalDeploymentID() {
// heal only from the site with latest info.
return nil
}
latestPeerName = info.Sites[latestID].Name
// heal policy of peers if peer does not have it.
for dID, pStatus := range ps {
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
continue
}
if !pStatus.PolicyMismatch && pStatus.HasPolicy {
@@ -4739,14 +4744,14 @@ func (c *SiteReplicationSys) healUserPolicies(ctx context.Context, objAPI Object
latestUserStat = ss
}
}
if latestID != globalDeploymentID {
if latestID != globalDeploymentID() {
// heal only from the site with latest info.
return nil
}
latestPeerName = info.Sites[latestID].Name
// heal policy of peers if peer does not have it.
for dID, pStatus := range us {
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
continue
}
if !pStatus.PolicyMismatch && pStatus.HasPolicyMapping {
@@ -4800,14 +4805,14 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec
latestGroupStat = ss
}
}
if latestID != globalDeploymentID {
if latestID != globalDeploymentID() {
// heal only from the site with latest info.
return nil
}
latestPeerName = info.Sites[latestID].Name
// heal policy of peers if peer does not have it.
for dID, pStatus := range gs {
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
continue
}
if !pStatus.PolicyMismatch && pStatus.HasPolicyMapping {
@@ -4862,13 +4867,13 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
latestUserStat = ss
}
}
if latestID != globalDeploymentID {
if latestID != globalDeploymentID() {
// heal only from the site with latest info.
return nil
}
latestPeerName = info.Sites[latestID].Name
for dID, uStatus := range us {
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
continue
}
if !uStatus.UserInfoMismatch {
@@ -5008,13 +5013,13 @@ func (c *SiteReplicationSys) healGroups(ctx context.Context, objAPI ObjectLayer,
latestGroupStat = ss
}
}
if latestID != globalDeploymentID {
if latestID != globalDeploymentID() {
// heal only from the site with latest info.
return nil
}
latestPeerName = info.Sites[latestID].Name
for dID, gStatus := range gs {
if dID == globalDeploymentID {
if dID == globalDeploymentID() {
continue
}
if !gStatus.GroupDescMismatch {
@@ -5113,7 +5118,7 @@ func (c *SiteReplicationSys) getPeerForUpload(deplID string) (pi srPeerInfo, loc
PeerInfo: site,
EndpointURL: ep,
}
return pi, site.DeploymentID == globalDeploymentID
return pi, site.DeploymentID == globalDeploymentID()
}
}
return pi, true
@@ -5130,7 +5135,7 @@ func (c *SiteReplicationSys) startResync(ctx context.Context, objAPI ObjectLayer
return res, errSRObjectLayerNotReady
}
if peer.DeploymentID == globalDeploymentID {
if peer.DeploymentID == globalDeploymentID() {
return res, errSRResyncToSelf
}
if _, ok := c.state.Peers[peer.DeploymentID]; !ok {
@@ -5244,7 +5249,7 @@ func (c *SiteReplicationSys) cancelResync(ctx context.Context, objAPI ObjectLaye
if objAPI == nil {
return res, errSRObjectLayerNotReady
}
if peer.DeploymentID == globalDeploymentID {
if peer.DeploymentID == globalDeploymentID() {
return res, errSRResyncToSelf
}
if _, ok := c.state.Peers[peer.DeploymentID]; !ok {
+54 -22
View File
@@ -144,7 +144,8 @@ type storageRESTClient struct {
// Indexes, will be -1 until assigned a set.
poolIndex, setIndex, diskIndex int
diskInfoCache timedValue
diskInfoCache timedValue
diskInfoCacheMetrics timedValue
}
// Retrieve location indexes.
@@ -289,30 +290,61 @@ func (client *storageRESTClient) DiskInfo(_ context.Context, metrics bool) (info
}
// Do not cache results from atomic variables
scanning := atomic.LoadInt32(&client.scanning) == 1
client.diskInfoCache.Once.Do(func() {
client.diskInfoCache.TTL = time.Second
client.diskInfoCache.Update = func() (interface{}, error) {
var info DiskInfo
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if metrics {
client.diskInfoCacheMetrics.Once.Do(func() {
client.diskInfoCacheMetrics.TTL = time.Second
client.diskInfoCacheMetrics.Update = func() (interface{}, error) {
var info DiskInfo
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
vals := make(url.Values)
vals.Set(storageRESTMetrics, strconv.FormatBool(metrics))
respBody, err := client.call(ctx, storageRESTMethodDiskInfo, vals, nil, -1)
if err != nil {
return info, err
vals := make(url.Values)
vals.Set(storageRESTMetrics, "true")
respBody, err := client.call(ctx, storageRESTMethodDiskInfo, vals, nil, -1)
if err != nil {
return info, err
}
defer xhttp.DrainBody(respBody)
if err = msgp.Decode(respBody, &info); err != nil {
return info, err
}
if info.Error != "" {
return info, toStorageErr(errors.New(info.Error))
}
return info, nil
}
defer xhttp.DrainBody(respBody)
if err = msgp.Decode(respBody, &info); err != nil {
return info, err
})
} else {
client.diskInfoCache.Once.Do(func() {
client.diskInfoCache.TTL = time.Second
client.diskInfoCache.Update = func() (interface{}, error) {
var info DiskInfo
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
vals := make(url.Values)
respBody, err := client.call(ctx, storageRESTMethodDiskInfo, vals, nil, -1)
if err != nil {
return info, err
}
defer xhttp.DrainBody(respBody)
if err = msgp.Decode(respBody, &info); err != nil {
return info, err
}
if info.Error != "" {
return info, toStorageErr(errors.New(info.Error))
}
return info, nil
}
if info.Error != "" {
return info, toStorageErr(errors.New(info.Error))
}
return info, nil
}
})
val, err := client.diskInfoCache.Get()
})
}
var val interface{}
if metrics {
val, err = client.diskInfoCacheMetrics.Get()
} else {
val, err = client.diskInfoCache.Get()
}
if val != nil {
info = val.(DiskInfo)
}
+2 -2
View File
@@ -814,7 +814,7 @@ func newContext(r *http.Request, w http.ResponseWriter, api string) context.Cont
bucket := vars["bucket"]
object := likelyUnescapeGeneric(vars["object"], url.PathUnescape)
reqInfo := &logger.ReqInfo{
DeploymentID: globalDeploymentID,
DeploymentID: globalDeploymentID(),
RequestID: reqID,
RemoteHost: handlers.GetSourceIP(r),
Host: getHostName(r),
@@ -1048,7 +1048,7 @@ func auditLogInternal(ctx context.Context, opts AuditLogOptions) {
if len(logger.AuditTargets()) == 0 {
return
}
entry := audit.NewEntry(globalDeploymentID)
entry := audit.NewEntry(globalDeploymentID())
entry.Trigger = opts.Event
entry.Event = opts.Event
entry.Error = opts.Error
+24 -7
View File
@@ -22,6 +22,8 @@ import (
"context"
"encoding/xml"
"io"
"github.com/minio/madmin-go/v3"
)
// From Veeam-SOSAPI_1.0_Document_v1.02d.pdf
@@ -143,18 +145,33 @@ func veeamSOSAPIGetObject(ctx context.Context, bucket, object string, rs *HTTPRa
if objAPI == nil {
return nil, errServerNotInitialized
}
info := objAPI.StorageInfo(ctx)
info.Backend = objAPI.BackendInfo()
usableTotal := int64(GetTotalUsableCapacity(info.Disks, info))
usableFree := int64(GetTotalUsableCapacityFree(info.Disks, info))
q, _ := globalBucketQuotaSys.Get(ctx, bucket)
binfo, _ := globalBucketQuotaSys.GetBucketUsageInfo(bucket)
ci := capacityInfo{
Capacity: usableTotal,
Available: usableFree,
Used: usableTotal - usableFree,
Used: int64(binfo.Size),
}
var quotaSize int64
if q != nil && q.Type == madmin.HardQuota {
if q.Size > 0 {
quotaSize = int64(q.Size)
} else if q.Quota > 0 {
quotaSize = int64(q.Quota)
}
}
if quotaSize == 0 {
info := objAPI.StorageInfo(ctx)
info.Backend = objAPI.BackendInfo()
ci.Capacity = int64(GetTotalUsableCapacity(info.Disks, info))
} else {
ci.Capacity = quotaSize
}
ci.Available = ci.Capacity - ci.Used
buf = encodeResponse(&ci)
default:
return nil, errFileNotFound
+48 -29
View File
@@ -86,6 +86,15 @@ type xlStorageDiskIDCheck struct {
diskID string
storage *xlStorage
health *diskHealthTracker
// diskStartChecking is a threshold above which we will start to check
// the state of disks, generally this value is less than diskMaxConcurrent
diskStartChecking int
// diskMaxConcurrent represents maximum number of running concurrent
// operations for local and (incoming) remote disk operations.
diskMaxConcurrent int
metricsCache timedValue
diskCtx context.Context
cancel context.CancelFunc
@@ -168,10 +177,45 @@ func (e *lockedLastMinuteLatency) total() AccElem {
return e.lastMinuteLatency.getTotal()
}
var maxConcurrentOnce sync.Once
func newXLStorageDiskIDCheck(storage *xlStorage, healthCheck bool) *xlStorageDiskIDCheck {
// diskMaxConcurrent 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
diskMaxConcurrent := -1
maxConcurrentOnce.Do(func() {
s := env.Get("_MINIO_DRIVE_MAX_CONCURRENT", "")
if s == "" {
s = env.Get("_MINIO_DISK_MAX_CONCURRENT", "")
}
if s != "" {
diskMaxConcurrent, _ = strconv.Atoi(s)
}
})
if diskMaxConcurrent <= 0 {
diskMaxConcurrent = 512
if storage.rotational {
diskMaxConcurrent = int(storage.nrRequests) / 2
if diskMaxConcurrent == 0 {
diskMaxConcurrent = 32
}
}
}
diskStartChecking := 16 + diskMaxConcurrent/8
if diskStartChecking > diskMaxConcurrent {
diskStartChecking = diskMaxConcurrent
}
xl := xlStorageDiskIDCheck{
storage: storage,
health: newDiskHealthTracker(),
storage: storage,
health: newDiskHealthTracker(diskMaxConcurrent),
diskMaxConcurrent: diskMaxConcurrent,
diskStartChecking: diskStartChecking,
}
xl.diskCtx, xl.cancel = context.WithCancel(context.TODO())
for i := range xl.apiLatencies[:] {
@@ -709,14 +753,6 @@ const (
diskHealthFaulty
)
// diskMaxConcurrent is the maximum number of running concurrent operations
// for local and (incoming) remote disk ops respectively.
var diskMaxConcurrent = 512
// diskStartChecking is a threshold above which we will start to check
// the state of disks.
var diskStartChecking = 32
// diskMaxTimeoutOperation maximum wait time before we consider a drive
// offline under active monitoring.
var diskMaxTimeout = 2 * time.Minute
@@ -725,18 +761,6 @@ var diskMaxTimeout = 2 * time.Minute
var diskActiveMonitoring = true
func init() {
s := env.Get("_MINIO_DRIVE_MAX_CONCURRENT", "")
if s == "" {
s = env.Get("_MINIO_DISK_MAX_CONCURRENT", "")
}
if s != "" {
diskMaxConcurrent, _ = strconv.Atoi(s)
if diskMaxConcurrent <= 0 {
logger.Info("invalid _MINIO_DISK_MAX_CONCURRENT value: %s, defaulting to '512'", s)
diskMaxConcurrent = 512
}
}
d := env.Get("_MINIO_DRIVE_MAX_TIMEOUT", "")
if d == "" {
d = env.Get("_MINIO_DISK_MAX_TIMEOUT", "")
@@ -752,11 +776,6 @@ func init() {
diskActiveMonitoring = (env.Get("_MINIO_DRIVE_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn) ||
(env.Get("_MINIO_DISK_ACTIVE_MONITORING", config.EnableOn) == config.EnableOn)
diskStartChecking = 16 + diskMaxConcurrent/8
if diskStartChecking > diskMaxConcurrent {
diskStartChecking = diskMaxConcurrent
}
}
type diskHealthTracker struct {
@@ -777,7 +796,7 @@ type diskHealthTracker struct {
}
// newDiskHealthTracker creates a new disk health tracker.
func newDiskHealthTracker() *diskHealthTracker {
func newDiskHealthTracker(diskMaxConcurrent int) *diskHealthTracker {
d := diskHealthTracker{
lastSuccess: time.Now().UnixNano(),
lastStarted: time.Now().UnixNano(),
@@ -912,7 +931,7 @@ func (p *xlStorageDiskIDCheck) checkHealth(ctx context.Context) (err error) {
return errFaultyDisk
}
// Check if there are tokens.
if diskMaxConcurrent-len(p.health.tokens) < diskStartChecking {
if p.diskMaxConcurrent-len(p.health.tokens) < p.diskStartChecking {
return nil
}
@@ -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.2023-10-07T15-07-38Z
image: quay.io/minio/minio:RELEASE.2023-10-16T04-13-43Z
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
expose:
- "9000"
+2 -2
View File
@@ -59,7 +59,7 @@ require (
github.com/minio/xxml v0.0.3
github.com/minio/zipindex v0.3.0
github.com/mitchellh/go-homedir v1.1.0
github.com/nats-io/nats-server/v2 v2.9.20
github.com/nats-io/nats-server/v2 v2.9.23
github.com/nats-io/nats.go v1.30.0
github.com/nats-io/stan.go v0.10.4
github.com/ncw/directio v1.0.5
@@ -192,7 +192,7 @@ require (
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nats-io/jwt/v2 v2.4.1 // indirect
github.com/nats-io/jwt/v2 v2.5.0 // indirect
github.com/nats-io/nats-streaming-server v0.24.3 // indirect
github.com/nats-io/nkeys v0.4.5 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
+4 -4
View File
@@ -529,11 +529,11 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt/v2 v2.2.1-0.20220113022732-58e87895b296/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
github.com/nats-io/jwt/v2 v2.4.1 h1:Y35W1dgbbz2SQUYDPCaclXcuqleVmpbRa7646Jf2EX4=
github.com/nats-io/jwt/v2 v2.4.1/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/jwt/v2 v2.5.0 h1:WQQ40AAlqqfx+f6ku+i0pOVm+ASirD4fUh+oQsiE9Ak=
github.com/nats-io/jwt/v2 v2.5.0/go.mod h1:24BeQtRwxRV8ruvC4CojXlx/WQ/VjuwlYiH+vu/+ibI=
github.com/nats-io/nats-server/v2 v2.7.4/go.mod h1:1vZ2Nijh8tcyNe8BDVyTviCd9NYzRbubQYiEHsvOQWc=
github.com/nats-io/nats-server/v2 v2.9.20 h1:bt1dW6xsL1hWWwv7Hovm+EJt5L6iplyqlgEFkoEUk0k=
github.com/nats-io/nats-server/v2 v2.9.20/go.mod h1:aTb/xtLCGKhfTFLxP591CMWfkdgBmcUUSkiSOe5A3gw=
github.com/nats-io/nats-server/v2 v2.9.23 h1:6Wj6H6QpP9FMlpCyWUaNu2yeZ/qGj+mdRkZ1wbikExU=
github.com/nats-io/nats-server/v2 v2.9.23/go.mod h1:wEjrEy9vnqIGE4Pqz4/c75v9Pmaq7My2IgFmnykc4C0=
github.com/nats-io/nats-streaming-server v0.24.3 h1:uZez8jBkXscua++jaDsK7DhpSAkizdetar6yWbPMRco=
github.com/nats-io/nats-streaming-server v0.24.3/go.mod h1:rqWfyCbxlhKj//fAp8POdQzeADwqkVhZcoWlbhkuU5w=
github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
+2 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2015-2021 MinIO, Inc.
// Copyright (c) 2015-2023 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
@@ -60,7 +60,7 @@ type ARN struct {
// Allows english letters, numbers, '.', '-', '_' and '/'. Starts with a
// letter or digit. At least 1 character long.
var validResourceIDRegex = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_/\.-]*$`)
var validResourceIDRegex = regexp.MustCompile(`[A-Za-z0-9_/\.-]+$`)
// NewIAMRoleARN - returns an ARN for a role in MinIO.
func NewIAMRoleARN(resourceID, serverRegion string) (ARN, error) {
+76
View File
@@ -0,0 +1,76 @@
// Copyright (c) 2015-2023 MinIO, Inc.
//
// # This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package arn
import (
"fmt"
"testing"
)
func TestNewIAMRoleARN(t *testing.T) {
testCases := []struct {
resourceID string
serverRegion string
expectedARN string
isErrExpected bool
}{
{
resourceID: "myrole",
serverRegion: "us-east-1",
expectedARN: "arn:minio:iam:us-east-1::role/myrole",
isErrExpected: false,
},
{
resourceID: "myrole",
serverRegion: "",
expectedARN: "arn:minio:iam:::role/myrole",
isErrExpected: false,
},
{
// Resource ID can start with a hyphen
resourceID: "-myrole",
serverRegion: "",
expectedARN: "arn:minio:iam:::role/-myrole",
isErrExpected: false,
},
{
resourceID: "",
serverRegion: "",
expectedARN: "",
isErrExpected: true,
},
}
for i, testCase := range testCases {
arn, err := NewIAMRoleARN(testCase.resourceID, testCase.serverRegion)
fmt.Println(arn, err)
if err != nil {
if !testCase.isErrExpected {
t.Errorf("Test %d: Unexpected error: %v", i+1, err)
}
continue
}
if testCase.isErrExpected {
t.Errorf("Test %d: Expected error but got none", i+1)
}
if arn.String() != testCase.expectedARN {
t.Errorf("Test %d: Expected ARN %s but got %s", i+1, testCase.expectedARN, arn.String())
}
}
}
+5 -4
View File
@@ -156,7 +156,8 @@ func (lc Lifecycle) HasActiveRules(prefix string) bool {
}
if len(prefix) > 0 && len(rule.GetPrefix()) > 0 {
// If recursive, we can skip this rule if it doesn't match the tested prefix.
// we can skip this rule if it doesn't match the tested
// prefix.
if !strings.HasPrefix(prefix, rule.GetPrefix()) && !strings.HasPrefix(rule.GetPrefix(), prefix) {
continue
}
@@ -171,15 +172,15 @@ func (lc Lifecycle) HasActiveRules(prefix string) bool {
if !rule.NoncurrentVersionTransition.IsNull() {
return true
}
if rule.Expiration.IsNull() && rule.Transition.IsNull() {
continue
}
if !rule.Expiration.IsDateNull() && rule.Expiration.Date.Before(time.Now().UTC()) {
return true
}
if !rule.Expiration.IsDaysNull() {
return true
}
if rule.Expiration.DeleteMarker.val {
return true
}
if !rule.Transition.IsDateNull() && rule.Transition.Date.Before(time.Now().UTC()) {
return true
}
+78 -4
View File
@@ -575,7 +575,7 @@ func TestHasActiveRules(t *testing.T) {
want: true,
},
{ // empty prefix
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Expiration><Days>5</Days></Expiration></Rule></LifecycleConfiguration>`,
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Filter></Filter><Expiration><Days>5</Days></Expiration></Rule></LifecycleConfiguration>`,
prefix: "foodir/foobject/foo.txt",
want: true,
},
@@ -600,15 +600,25 @@ func TestHasActiveRules(t *testing.T) {
want: false,
},
{
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Transition><StorageClass>S3TIER-1</StorageClass></Transition></Rule></LifecycleConfiguration>`,
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Filter></Filter><Transition><StorageClass>S3TIER-1</StorageClass></Transition></Rule></LifecycleConfiguration>`,
prefix: "foodir/foobject/foo.txt",
want: true,
},
{
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><NoncurrentVersionTransition><StorageClass>S3TIER-1</StorageClass></NoncurrentVersionTransition></Rule></LifecycleConfiguration>`,
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Filter></Filter><NoncurrentVersionTransition><StorageClass>S3TIER-1</StorageClass></NoncurrentVersionTransition></Rule></LifecycleConfiguration>`,
prefix: "foodir/foobject/foo.txt",
want: true,
},
{
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Filter></Filter><Expiration><ExpiredObjectDeleteMarker>true</ExpiredObjectDeleteMarker></Expiration></Rule></LifecycleConfiguration>`,
prefix: "",
want: true,
},
{
inputConfig: `<LifecycleConfiguration><Rule><Status>Enabled</Status><Filter></Filter><Expiration><Days>42</Days><ExpiredObjectAllVersions>true</ExpiredObjectAllVersions></Expiration></Rule></LifecycleConfiguration>`,
prefix: "",
want: true,
},
}
for i, tc := range testCases {
@@ -618,8 +628,12 @@ func TestHasActiveRules(t *testing.T) {
if err != nil {
t.Fatalf("Got unexpected error: %v", err)
}
// To ensure input lifecycle configurations are valid
if err := lc.Validate(); err != nil {
t.Fatalf("Invalid test case: %d %v", i+1, err)
}
if got := lc.HasActiveRules(tc.prefix); got != tc.want {
t.Fatalf("Expected result with recursive set to false: `%v`, got: `%v`", tc.want, got)
t.Fatalf("Expected result: `%v`, got: `%v`", tc.want, got)
}
})
}
@@ -1002,3 +1016,63 @@ func TestFilterAndSetPredictionHeaders(t *testing.T) {
})
}
}
func TestFilterRules(t *testing.T) {
lc := Lifecycle{
Rules: []Rule{
{
ID: "rule-1",
Status: "Enabled",
Filter: Filter{
Tag: Tag{
Key: "key1",
Value: "val1",
},
},
Expiration: Expiration{
Days: 1,
},
},
},
}
tests := []struct {
opts ObjectOpts
wantRule string
}{
{ // Delete marker should match filter without tags
opts: ObjectOpts{
DeleteMarker: true,
IsLatest: true,
Name: "obj-1",
},
wantRule: "rule-1",
},
{ // PUT version with no matching tags
opts: ObjectOpts{
IsLatest: true,
Name: "obj-1",
},
wantRule: "",
},
{ // PUT version with matching tags
opts: ObjectOpts{
IsLatest: true,
UserTags: "key1=val1",
Name: "obj-1",
},
wantRule: "rule-1",
},
}
for i, tc := range tests {
t.Run(fmt.Sprintf("test-%d", i+1), func(t *testing.T) {
rules := lc.FilterRules(tc.opts)
if tc.wantRule != "" && len(rules) == 0 {
t.Fatalf("%d: Expected rule match %s but none matched", i+1, tc.wantRule)
}
if tc.wantRule == "" && len(rules) > 0 {
t.Fatalf("%d: Expected no rules to match but got matches %v", i+1, rules)
}
})
}
}
+2 -2
View File
@@ -63,6 +63,6 @@ func DisableDirectIO(f *os.File) error {
// AlignedBlock simply returns an unaligned buffer
// for systems that do not support DirectIO.
func AlignedBlock(BlockSize int) []byte {
return make([]byte, BlockSize)
func AlignedBlock(blockSize int) []byte {
return make([]byte, blockSize)
}
+8 -12
View File
@@ -81,11 +81,10 @@ func lockedOpenFile(path string, flag int, perm os.FileMode, lockType uint32) (*
// acquire a write lock.
func TryLockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
var lockType uint32 = lockFileFailImmediately | lockFileExclusiveLock
switch flag {
case syscall.O_RDONLY:
if flag == syscall.O_RDONLY {
// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-lockfileex
//lint:ignore SA4016 Reasons
lockType = lockFileFailImmediately | 0 // Set this to enable shared lock and fail immediately.
lockType = lockFileFailImmediately // Set this to enable shared lock and fail immediately.
}
return lockedOpenFile(path, flag, perm, lockType)
}
@@ -94,8 +93,7 @@ func TryLockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, er
// the file from concurrent access.
func LockedOpenFile(path string, flag int, perm os.FileMode) (*LockedFile, error) {
var lockType uint32 = lockFileExclusiveLock
switch flag {
case syscall.O_RDONLY:
if flag == syscall.O_RDONLY {
// https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-lockfileex
lockType = 0 // Set this to enable shared lock.
}
@@ -239,15 +237,13 @@ func lockFile(fd syscall.Handle, flags uint32) error {
}
err := lockFileEx(fd, flags, 1, 0, &syscall.Overlapped{})
if err == nil {
if err == nil || err == errLockViolation {
return nil
} else if err.Error() == "The process cannot access the file because another process has locked a portion of the file." {
return ErrAlreadyLocked
} else if err != errLockViolation {
return err
}
return nil
if err.Error() == "The process cannot access the file because another process has locked a portion of the file." {
return ErrAlreadyLocked
}
return err
}
func lockFileEx(h syscall.Handle, flags, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
+3 -3
View File
@@ -51,10 +51,10 @@ func TestFixLongPath(t *testing.T) {
{`\\?\c:\long\foo.txt`, `\\?\c:\long\foo.txt`},
{`\\?\c:\long/foo.txt`, `\\?\c:\long/foo.txt`},
} {
in := strings.Replace(test.in, "long", veryLong, -1)
want := strings.Replace(test.want, "long", veryLong, -1)
in := strings.ReplaceAll(test.in, "long", veryLong)
want := strings.ReplaceAll(test.want, "long", veryLong)
if got := fixLongPath(in); got != want {
got = strings.Replace(got, veryLong, "long", -1)
got = strings.ReplaceAll(got, veryLong, "long")
t.Errorf("fixLongPath(%q) = %q; want %q", test.in, got, test.want)
}
}