mirror of
https://github.com/pgsty/minio.git
synced 2026-11-06 15:52:01 +03:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 459985f0fa | |||
| d0080046c2 | |||
| 7fcb428622 | |||
| 4ea6f94ed8 | |||
| 83adc2eebf | |||
| 989c318a28 | |||
| ef802f2b2c | |||
| f5d2fbc84c | |||
| e139673969 | |||
| a8c6465f22 | |||
| 27538e2d22 | |||
| 6c6f0987dc | |||
| 5f64658faa | |||
| ce183cb2b4 | |||
| b3bac73c0f | |||
| e726d8ff0f | |||
| f4230777b3 | |||
| 380233d646 | |||
| d592bc0c1c | |||
| 0d0b0aa599 | |||
| b433bf14ba | |||
| cf371da346 |
@@ -3,6 +3,8 @@ extend-exclude = [
|
||||
".git/",
|
||||
"docs/",
|
||||
"CREDITS",
|
||||
"go.mod",
|
||||
"go.sum",
|
||||
]
|
||||
ignore-hidden = false
|
||||
|
||||
@@ -17,6 +19,7 @@ extend-ignore-re = [
|
||||
"MIIDBTCCAe2gAwIBAgIQWHw7h.*",
|
||||
'http\.Header\{"X-Amz-Server-Side-Encryptio":',
|
||||
"ZoEoZdLlzVbOlT9rbhD7ZN7TLyiYXSAlB79uGEge",
|
||||
"ERRO:",
|
||||
]
|
||||
|
||||
[default.extend-words]
|
||||
|
||||
@@ -41,6 +41,7 @@ check-gen: ## check for updated autogenerated files
|
||||
lint: getdeps ## runs golangci-lint suite of linters
|
||||
@echo "Running $@ check"
|
||||
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml
|
||||
@command typos && typos ./ || echo "typos binary is not found.. skipping.."
|
||||
|
||||
lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
|
||||
@echo "Running $@ check"
|
||||
|
||||
@@ -101,7 +101,7 @@ function fail() {
|
||||
}
|
||||
|
||||
function check_online() {
|
||||
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
||||
if ! grep -q 'API:' ${WORK_DIR}/dist-minio-*.log; then
|
||||
echo "1"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ function start_minio_3_node() {
|
||||
}
|
||||
|
||||
function check_heal() {
|
||||
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
|
||||
if ! grep -q 'API:' ${WORK_DIR}/dist-minio-*.log; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -552,7 +552,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
||||
dnList = append(dnList, selfDN)
|
||||
}
|
||||
|
||||
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp)
|
||||
var ldapUserList []string
|
||||
if isAll {
|
||||
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
|
||||
if err != nil {
|
||||
@@ -560,7 +560,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
for user := range ldapUsers {
|
||||
accessKeyMap[user] = madmin.ListAccessKeysLDAPResp{}
|
||||
ldapUserList = append(ldapUserList, user)
|
||||
}
|
||||
} else {
|
||||
for _, userDN := range dnList {
|
||||
@@ -573,7 +573,7 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
||||
if foundResult == nil {
|
||||
continue
|
||||
}
|
||||
accessKeyMap[foundResult.NormDN] = madmin.ListAccessKeysLDAPResp{}
|
||||
ldapUserList = append(ldapUserList, foundResult.NormDN)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,9 +598,12 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
||||
return
|
||||
}
|
||||
|
||||
for dn, accessKeys := range accessKeyMap {
|
||||
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp)
|
||||
for _, internalDN := range ldapUserList {
|
||||
externalDN := globalIAMSys.LDAPConfig.DecodeDN(internalDN)
|
||||
accessKeys := madmin.ListAccessKeysLDAPResp{}
|
||||
if listSTSKeys {
|
||||
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, dn)
|
||||
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, internalDN)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -614,13 +617,12 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
||||
}
|
||||
// if only STS keys, skip if user has no STS keys
|
||||
if !listServiceAccounts && len(stsKeys) == 0 {
|
||||
delete(accessKeyMap, dn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if listServiceAccounts {
|
||||
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, dn)
|
||||
serviceAccounts, err := globalIAMSys.ListServiceAccounts(ctx, internalDN)
|
||||
if err != nil {
|
||||
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
|
||||
return
|
||||
@@ -634,11 +636,10 @@ func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.
|
||||
}
|
||||
// if only service accounts, skip if user has no service accounts
|
||||
if !listSTSKeys && len(serviceAccounts) == 0 {
|
||||
delete(accessKeyMap, dn)
|
||||
continue
|
||||
}
|
||||
}
|
||||
accessKeyMap[dn] = accessKeys
|
||||
accessKeyMap[externalDN] = accessKeys
|
||||
}
|
||||
|
||||
data, err := json.Marshal(accessKeyMap)
|
||||
|
||||
@@ -977,8 +977,10 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
|
||||
ri.mu.Lock()
|
||||
defer ri.mu.Unlock()
|
||||
|
||||
ri.Bucket = bucket
|
||||
ri.Object = info.Name
|
||||
if success {
|
||||
ri.Bucket = bucket
|
||||
ri.Object = info.Name
|
||||
}
|
||||
ri.countItem(info.Size, info.DeleteMarker, success, attempt)
|
||||
}
|
||||
|
||||
|
||||
@@ -389,6 +389,17 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
|
||||
stopFn(result, err)
|
||||
batchLogIf(ctx, err)
|
||||
success = false
|
||||
if attempts >= retryAttempts {
|
||||
auditOptions := AuditLogOptions{
|
||||
Event: "KeyRotate",
|
||||
APIName: "StartBatchJob",
|
||||
Bucket: result.Bucket,
|
||||
Object: result.Name,
|
||||
VersionID: result.VersionID,
|
||||
Error: err.Error(),
|
||||
}
|
||||
auditLogInternal(ctx, auditOptions)
|
||||
}
|
||||
} else {
|
||||
stopFn(result, nil)
|
||||
}
|
||||
|
||||
+76
-28
@@ -1803,15 +1803,18 @@ var (
|
||||
type ReplicationPool struct {
|
||||
// atomic ops:
|
||||
activeWorkers int32
|
||||
activeLrgWorkers int32
|
||||
activeMRFWorkers int32
|
||||
|
||||
objLayer ObjectLayer
|
||||
ctx context.Context
|
||||
priority string
|
||||
maxWorkers int
|
||||
mu sync.RWMutex
|
||||
mrfMU sync.Mutex
|
||||
resyncer *replicationResyncer
|
||||
objLayer ObjectLayer
|
||||
ctx context.Context
|
||||
priority string
|
||||
maxWorkers int
|
||||
maxLWorkers int
|
||||
|
||||
mu sync.RWMutex
|
||||
mrfMU sync.Mutex
|
||||
resyncer *replicationResyncer
|
||||
|
||||
// workers:
|
||||
workers []chan ReplicationWorkerOperation
|
||||
@@ -1882,9 +1885,13 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
||||
if maxWorkers > 0 && failedWorkers > maxWorkers {
|
||||
failedWorkers = maxWorkers
|
||||
}
|
||||
maxLWorkers := LargeWorkerCount
|
||||
if opts.MaxLWorkers > 0 {
|
||||
maxLWorkers = opts.MaxLWorkers
|
||||
}
|
||||
pool := &ReplicationPool{
|
||||
workers: make([]chan ReplicationWorkerOperation, 0, workers),
|
||||
lrgworkers: make([]chan ReplicationWorkerOperation, 0, LargeWorkerCount),
|
||||
lrgworkers: make([]chan ReplicationWorkerOperation, 0, maxLWorkers),
|
||||
mrfReplicaCh: make(chan ReplicationWorkerOperation, 100000),
|
||||
mrfWorkerKillCh: make(chan struct{}, failedWorkers),
|
||||
resyncer: newresyncer(),
|
||||
@@ -1894,9 +1901,10 @@ func NewReplicationPool(ctx context.Context, o ObjectLayer, opts replicationPool
|
||||
objLayer: o,
|
||||
priority: priority,
|
||||
maxWorkers: maxWorkers,
|
||||
maxLWorkers: maxLWorkers,
|
||||
}
|
||||
|
||||
pool.AddLargeWorkers()
|
||||
pool.ResizeLrgWorkers(maxLWorkers, 0)
|
||||
pool.ResizeWorkers(workers, 0)
|
||||
pool.ResizeFailedWorkers(failedWorkers)
|
||||
go pool.resyncer.PersistToDisk(ctx, o)
|
||||
@@ -1975,23 +1983,8 @@ func (p *ReplicationPool) AddWorker(input <-chan ReplicationWorkerOperation, opT
|
||||
}
|
||||
}
|
||||
|
||||
// AddLargeWorkers adds a static number of workers to handle large uploads
|
||||
func (p *ReplicationPool) AddLargeWorkers() {
|
||||
for i := 0; i < LargeWorkerCount; i++ {
|
||||
p.lrgworkers = append(p.lrgworkers, make(chan ReplicationWorkerOperation, 100000))
|
||||
i := i
|
||||
go p.AddLargeWorker(p.lrgworkers[i])
|
||||
}
|
||||
go func() {
|
||||
<-p.ctx.Done()
|
||||
for i := 0; i < LargeWorkerCount; i++ {
|
||||
xioutil.SafeClose(p.lrgworkers[i])
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// AddLargeWorker adds a replication worker to the static pool for large uploads.
|
||||
func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation) {
|
||||
func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation, opTracker *int32) {
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
@@ -2002,11 +1995,23 @@ func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation
|
||||
}
|
||||
switch v := oi.(type) {
|
||||
case ReplicateObjectInfo:
|
||||
if opTracker != nil {
|
||||
atomic.AddInt32(opTracker, 1)
|
||||
}
|
||||
globalReplicationStats.incQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
||||
replicateObject(p.ctx, v, p.objLayer)
|
||||
globalReplicationStats.decQ(v.Bucket, v.Size, v.DeleteMarker, v.OpType)
|
||||
if opTracker != nil {
|
||||
atomic.AddInt32(opTracker, -1)
|
||||
}
|
||||
case DeletedObjectReplicationInfo:
|
||||
if opTracker != nil {
|
||||
atomic.AddInt32(opTracker, 1)
|
||||
}
|
||||
replicateDelete(p.ctx, v, p.objLayer)
|
||||
if opTracker != nil {
|
||||
atomic.AddInt32(opTracker, -1)
|
||||
}
|
||||
default:
|
||||
bugLogIf(p.ctx, fmt.Errorf("unknown replication type: %T", oi), "unknown-replicate-type")
|
||||
}
|
||||
@@ -2014,6 +2019,30 @@ func (p *ReplicationPool) AddLargeWorker(input <-chan ReplicationWorkerOperation
|
||||
}
|
||||
}
|
||||
|
||||
// ResizeLrgWorkers sets replication workers pool for large transfers(>=128MiB) to new size.
|
||||
// checkOld can be set to an expected value.
|
||||
// If the worker count changed
|
||||
func (p *ReplicationPool) ResizeLrgWorkers(n, checkOld int) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
if (checkOld > 0 && len(p.lrgworkers) != checkOld) || n == len(p.lrgworkers) || n < 1 {
|
||||
// Either already satisfied or worker count changed while we waited for the lock.
|
||||
return
|
||||
}
|
||||
for len(p.lrgworkers) < n {
|
||||
input := make(chan ReplicationWorkerOperation, 100000)
|
||||
p.lrgworkers = append(p.lrgworkers, input)
|
||||
|
||||
go p.AddLargeWorker(input, &p.activeLrgWorkers)
|
||||
}
|
||||
for len(p.lrgworkers) > n {
|
||||
worker := p.lrgworkers[len(p.lrgworkers)-1]
|
||||
p.lrgworkers = p.lrgworkers[:len(p.lrgworkers)-1]
|
||||
xioutil.SafeClose(worker)
|
||||
}
|
||||
}
|
||||
|
||||
// ActiveWorkers returns the number of active workers handling replication traffic.
|
||||
func (p *ReplicationPool) ActiveWorkers() int {
|
||||
return int(atomic.LoadInt32(&p.activeWorkers))
|
||||
@@ -2024,6 +2053,11 @@ func (p *ReplicationPool) ActiveMRFWorkers() int {
|
||||
return int(atomic.LoadInt32(&p.activeMRFWorkers))
|
||||
}
|
||||
|
||||
// ActiveLrgWorkers returns the number of active workers handling traffic > 128MiB object size.
|
||||
func (p *ReplicationPool) ActiveLrgWorkers() int {
|
||||
return int(atomic.LoadInt32(&p.activeLrgWorkers))
|
||||
}
|
||||
|
||||
// ResizeWorkers sets replication workers pool to new size.
|
||||
// checkOld can be set to an expected value.
|
||||
// If the worker count changed
|
||||
@@ -2049,7 +2083,7 @@ func (p *ReplicationPool) ResizeWorkers(n, checkOld int) {
|
||||
}
|
||||
|
||||
// ResizeWorkerPriority sets replication failed workers pool size
|
||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers int) {
|
||||
func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers, maxLWorkers int) {
|
||||
var workers, mrfWorkers int
|
||||
p.mu.Lock()
|
||||
switch pri {
|
||||
@@ -2076,11 +2110,15 @@ func (p *ReplicationPool) ResizeWorkerPriority(pri string, maxWorkers int) {
|
||||
if maxWorkers > 0 && mrfWorkers > maxWorkers {
|
||||
mrfWorkers = maxWorkers
|
||||
}
|
||||
if maxLWorkers <= 0 {
|
||||
maxLWorkers = LargeWorkerCount
|
||||
}
|
||||
p.priority = pri
|
||||
p.maxWorkers = maxWorkers
|
||||
p.mu.Unlock()
|
||||
p.ResizeWorkers(workers, 0)
|
||||
p.ResizeFailedWorkers(mrfWorkers)
|
||||
p.ResizeLrgWorkers(maxLWorkers, 0)
|
||||
}
|
||||
|
||||
// ResizeFailedWorkers sets replication failed workers pool size
|
||||
@@ -2127,6 +2165,15 @@ func (p *ReplicationPool) queueReplicaTask(ri ReplicateObjectInfo) {
|
||||
case p.lrgworkers[h%LargeWorkerCount] <- ri:
|
||||
default:
|
||||
globalReplicationPool.queueMRFSave(ri.ToMRFEntry())
|
||||
p.mu.RLock()
|
||||
maxLWorkers := p.maxLWorkers
|
||||
existing := len(p.lrgworkers)
|
||||
p.mu.RUnlock()
|
||||
maxLWorkers = min(maxLWorkers, LargeWorkerCount)
|
||||
if p.ActiveLrgWorkers() < maxLWorkers {
|
||||
workers := min(existing+1, maxLWorkers)
|
||||
p.ResizeLrgWorkers(workers, existing)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -2229,8 +2276,9 @@ func (p *ReplicationPool) queueReplicaDeleteTask(doi DeletedObjectReplicationInf
|
||||
}
|
||||
|
||||
type replicationPoolOpts struct {
|
||||
Priority string
|
||||
MaxWorkers int
|
||||
Priority string
|
||||
MaxWorkers int
|
||||
MaxLWorkers int
|
||||
}
|
||||
|
||||
func initBackgroundReplication(ctx context.Context, objectAPI ObjectLayer) {
|
||||
|
||||
+9
-2
@@ -843,6 +843,7 @@ func loadRootCredentials() {
|
||||
// Check both cases and authenticate them if correctly defined
|
||||
var user, password string
|
||||
var hasCredentials bool
|
||||
var legacyCredentials bool
|
||||
//nolint:gocritic
|
||||
if env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
|
||||
user = env.Get(config.EnvRootUser, "")
|
||||
@@ -851,6 +852,7 @@ func loadRootCredentials() {
|
||||
} else if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
|
||||
user = env.Get(config.EnvAccessKey, "")
|
||||
password = env.Get(config.EnvSecretKey, "")
|
||||
legacyCredentials = true
|
||||
hasCredentials = true
|
||||
} else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" {
|
||||
user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd
|
||||
@@ -859,8 +861,13 @@ func loadRootCredentials() {
|
||||
if hasCredentials {
|
||||
cred, err := auth.CreateCredentials(user, password)
|
||||
if err != nil {
|
||||
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
if legacyCredentials {
|
||||
logger.Fatal(config.ErrInvalidCredentials(err),
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
} else {
|
||||
logger.Fatal(config.ErrInvalidRootUserCredentials(err),
|
||||
"Unable to validate credentials inherited from the shell environment")
|
||||
}
|
||||
}
|
||||
if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
|
||||
msg := fmt.Sprintf("WARNING: %s and %s are deprecated.\n"+
|
||||
|
||||
@@ -101,7 +101,7 @@ func initHelp() {
|
||||
config.HelpKV{
|
||||
Key: config.SubnetSubSys,
|
||||
Type: "string",
|
||||
Description: "register the cluster to MinIO SUBNET",
|
||||
Description: "register Enterprise license for the cluster",
|
||||
Optional: true,
|
||||
},
|
||||
config.HelpKV{
|
||||
|
||||
@@ -629,7 +629,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
|
||||
}
|
||||
|
||||
for i, v := range result.Before.Drives {
|
||||
if v.Endpoint == disk.String() {
|
||||
if v.Endpoint == disk.Endpoint().String() {
|
||||
result.After.Drives[i].State = madmin.DriveStateOk
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2447,6 +2447,7 @@ const (
|
||||
type HealthOptions struct {
|
||||
Maintenance bool
|
||||
DeploymentType string
|
||||
NoLogging bool
|
||||
}
|
||||
|
||||
// HealthResult returns the current state of the system, also
|
||||
@@ -2483,7 +2484,7 @@ func (hr HealthResult) String() string {
|
||||
if i == 0 {
|
||||
str.WriteString(")")
|
||||
} else {
|
||||
str.WriteString("), ")
|
||||
str.WriteString(") | ")
|
||||
}
|
||||
}
|
||||
return str.String()
|
||||
@@ -2606,7 +2607,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
})
|
||||
|
||||
healthy := erasureSetUpCount[poolIdx][setIdx].online >= poolWriteQuorums[poolIdx]
|
||||
if !healthy {
|
||||
if !healthy && !opts.NoLogging {
|
||||
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||
fmt.Errorf("Write quorum could not be established on pool: %d, set: %d, expected write quorum: %d, drives-online: %d",
|
||||
poolIdx, setIdx, poolWriteQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online), logger.FatalKind)
|
||||
@@ -2614,7 +2615,7 @@ func (z *erasureServerPools) Health(ctx context.Context, opts HealthOptions) Hea
|
||||
result.Healthy = result.Healthy && healthy
|
||||
|
||||
healthyRead := erasureSetUpCount[poolIdx][setIdx].online >= poolReadQuorums[poolIdx]
|
||||
if !healthyRead {
|
||||
if !healthyRead && !opts.NoLogging {
|
||||
storageLogIf(logger.SetReqInfo(ctx, reqInfo),
|
||||
fmt.Errorf("Read quorum could not be established on pool: %d, set: %d, expected read quorum: %d, drives-online: %d",
|
||||
poolIdx, setIdx, poolReadQuorums[poolIdx], erasureSetUpCount[poolIdx][setIdx].online))
|
||||
|
||||
+6
-4
@@ -189,7 +189,7 @@ func (s *erasureSets) Legacy() (ok bool) {
|
||||
|
||||
// connectDisks - attempt to connect all the endpoints, loads format
|
||||
// and re-arranges the disks in proper position.
|
||||
func (s *erasureSets) connectDisks() {
|
||||
func (s *erasureSets) connectDisks(log bool) {
|
||||
defer func() {
|
||||
s.lastConnectDisksOpTime = time.Now()
|
||||
}()
|
||||
@@ -224,7 +224,9 @@ func (s *erasureSets) connectDisks() {
|
||||
if endpoint.IsLocal && errors.Is(err, errUnformattedDisk) {
|
||||
globalBackgroundHealState.pushHealLocalDisks(endpoint)
|
||||
} else if !errors.Is(err, errDriveIsRoot) {
|
||||
printEndpointError(endpoint, err, true)
|
||||
if log {
|
||||
printEndpointError(endpoint, err, true)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -285,7 +287,7 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
|
||||
time.Sleep(time.Duration(r.Float64() * float64(time.Second)))
|
||||
|
||||
// Pre-emptively connect the disks if possible.
|
||||
s.connectDisks()
|
||||
s.connectDisks(false)
|
||||
|
||||
monitor := time.NewTimer(monitorInterval)
|
||||
defer monitor.Stop()
|
||||
@@ -299,7 +301,7 @@ func (s *erasureSets) monitorAndConnectEndpoints(ctx context.Context, monitorInt
|
||||
console.Debugln("running drive monitoring")
|
||||
}
|
||||
|
||||
s.connectDisks()
|
||||
s.connectDisks(true)
|
||||
|
||||
// Reset the timer for next interval
|
||||
monitor.Reset(monitorInterval)
|
||||
|
||||
+21
-5
@@ -441,6 +441,8 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
continue
|
||||
}
|
||||
|
||||
var versionHealed bool
|
||||
|
||||
res, err := er.HealObject(ctx, bucket, encodedEntryName,
|
||||
version.VersionID, madmin.HealOpts{
|
||||
ScanMode: scanMode,
|
||||
@@ -453,15 +455,22 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
versionNotFound++
|
||||
continue
|
||||
}
|
||||
// If not deleted, assume they failed.
|
||||
} else {
|
||||
// Look for the healing results
|
||||
if res.After.Drives[tracker.DiskIndex].State == madmin.DriveStateOk {
|
||||
versionHealed = true
|
||||
}
|
||||
}
|
||||
|
||||
if versionHealed {
|
||||
result = healEntrySuccess(uint64(version.Size))
|
||||
} else {
|
||||
result = healEntryFailure(uint64(version.Size))
|
||||
if version.VersionID != "" {
|
||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err))
|
||||
} else {
|
||||
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, version.Name, err))
|
||||
}
|
||||
} else {
|
||||
result = healEntrySuccess(uint64(res.ObjectSize))
|
||||
}
|
||||
|
||||
if !send(result) {
|
||||
@@ -509,7 +518,11 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
jt.Take()
|
||||
go healEntry(bucket, *entry)
|
||||
},
|
||||
finished: nil,
|
||||
finished: func(errs []error) {
|
||||
if countErrs(errs, nil) != len(errs) {
|
||||
retErr = fmt.Errorf("one or more errors reported during listing: %v", errors.Join(errs...))
|
||||
}
|
||||
},
|
||||
})
|
||||
jt.Wait() // synchronize all the concurrent heal jobs
|
||||
if err != nil {
|
||||
@@ -517,7 +530,10 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
|
||||
// we let the caller retry this disk again for the
|
||||
// buckets it failed to list.
|
||||
retErr = err
|
||||
healingLogIf(ctx, fmt.Errorf("listing failed with: %v on bucket: %v", err, bucket))
|
||||
}
|
||||
|
||||
if retErr != nil {
|
||||
healingLogIf(ctx, fmt.Errorf("listing failed with: %v on bucket: %v", retErr, bucket))
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
+10
-8
@@ -41,17 +41,19 @@ func initGlobalGrid(ctx context.Context, eps EndpointServerPools) error {
|
||||
// Pass Dialer for websocket grid, make sure we do not
|
||||
// provide any DriveOPTimeout() function, as that is not
|
||||
// useful over persistent connections.
|
||||
Dialer: grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||
Dialer: grid.ConnectWS(
|
||||
grid.ContextDialer(xhttp.DialContextWithLookupHost(lookupHost, xhttp.NewInternodeDialContext(rest.DefaultTimeout, globalTCPOptions.ForWebsocket()))),
|
||||
newCachedAuthToken(),
|
||||
&tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: fips.TLSCiphers(),
|
||||
CurvePreferences: fips.TLSCurveIDs(),
|
||||
}),
|
||||
Local: local,
|
||||
Hosts: hosts,
|
||||
AddAuth: newCachedAuthToken(),
|
||||
AuthRequest: storageServerRequestValidate,
|
||||
AuthToken: validateStorageRequestToken,
|
||||
AuthFn: newCachedAuthToken(),
|
||||
BlockConnect: globalGridStart,
|
||||
TLSConfig: &tls.Config{
|
||||
RootCAs: globalRootCAs,
|
||||
CipherSuites: fips.TLSCiphers(),
|
||||
CurvePreferences: fips.TLSCurveIDs(),
|
||||
},
|
||||
// Record incoming and outgoing bytes.
|
||||
Incoming: globalConnStats.incInternodeInputBytes,
|
||||
Outgoing: globalConnStats.incInternodeOutputBytes,
|
||||
|
||||
+18
-14
@@ -39,14 +39,15 @@ import (
|
||||
type apiConfig struct {
|
||||
mu sync.RWMutex
|
||||
|
||||
requestsDeadline time.Duration
|
||||
requestsPool chan struct{}
|
||||
clusterDeadline time.Duration
|
||||
listQuorum string
|
||||
corsAllowOrigins []string
|
||||
replicationPriority string
|
||||
replicationMaxWorkers int
|
||||
transitionWorkers int
|
||||
requestsDeadline time.Duration
|
||||
requestsPool chan struct{}
|
||||
clusterDeadline time.Duration
|
||||
listQuorum string
|
||||
corsAllowOrigins []string
|
||||
replicationPriority string
|
||||
replicationMaxWorkers int
|
||||
replicationMaxLWorkers int
|
||||
transitionWorkers int
|
||||
|
||||
staleUploadsExpiry time.Duration
|
||||
staleUploadsCleanupInterval time.Duration
|
||||
@@ -170,11 +171,12 @@ func (t *apiConfig) init(cfg api.Config, setDriveCounts []int, legacy bool) {
|
||||
}
|
||||
t.listQuorum = listQuorum
|
||||
if globalReplicationPool != nil &&
|
||||
(cfg.ReplicationPriority != t.replicationPriority || cfg.ReplicationMaxWorkers != t.replicationMaxWorkers) {
|
||||
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority, cfg.ReplicationMaxWorkers)
|
||||
(cfg.ReplicationPriority != t.replicationPriority || cfg.ReplicationMaxWorkers != t.replicationMaxWorkers || cfg.ReplicationMaxLWorkers != t.replicationMaxLWorkers) {
|
||||
globalReplicationPool.ResizeWorkerPriority(cfg.ReplicationPriority, cfg.ReplicationMaxWorkers, cfg.ReplicationMaxLWorkers)
|
||||
}
|
||||
t.replicationPriority = cfg.ReplicationPriority
|
||||
t.replicationMaxWorkers = cfg.ReplicationMaxWorkers
|
||||
t.replicationMaxLWorkers = cfg.ReplicationMaxLWorkers
|
||||
|
||||
// N B api.transition_workers will be deprecated
|
||||
if globalTransitionState != nil {
|
||||
@@ -381,14 +383,16 @@ func (t *apiConfig) getReplicationOpts() replicationPoolOpts {
|
||||
|
||||
if t.replicationPriority == "" {
|
||||
return replicationPoolOpts{
|
||||
Priority: "auto",
|
||||
MaxWorkers: WorkerMaxLimit,
|
||||
Priority: "auto",
|
||||
MaxWorkers: WorkerMaxLimit,
|
||||
MaxLWorkers: LargeWorkerCount,
|
||||
}
|
||||
}
|
||||
|
||||
return replicationPoolOpts{
|
||||
Priority: t.replicationPriority,
|
||||
MaxWorkers: t.replicationMaxWorkers,
|
||||
Priority: t.replicationPriority,
|
||||
MaxWorkers: t.replicationMaxWorkers,
|
||||
MaxLWorkers: t.replicationMaxLWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+88
-43
@@ -2088,50 +2088,72 @@ func (store *IAMStoreSys) GetAllSTSUserMappings(userPredicate func(string) bool)
|
||||
return stsMap, nil
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If users is empty, returns all user mappings.
|
||||
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string,
|
||||
userPredicate func(string) bool,
|
||||
// Assumes store is locked by caller. If userMap is empty, returns all user mappings.
|
||||
func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, userMap map[string]set.StringSet,
|
||||
userPredicate func(string) bool, decodeFunc func(string) string,
|
||||
) []madmin.UserPolicyEntities {
|
||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
resMap := make(map[string]madmin.UserPolicyEntities, len(userMap))
|
||||
|
||||
for user, groupSet := range userMap {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||
decodeUser := user
|
||||
if decodeFunc != nil {
|
||||
decodeUser = decodeFunc(user)
|
||||
}
|
||||
blankEntities := madmin.UserPolicyEntities{User: decodeUser}
|
||||
if !groupSet.IsEmpty() {
|
||||
blankEntities.MemberOfMappings = store.listGroupPolicyMappings(cache, groupSet, nil, decodeFunc)
|
||||
}
|
||||
resMap[user] = blankEntities
|
||||
}
|
||||
|
||||
var r []madmin.UserPolicyEntities
|
||||
usersSet := set.CreateStringSet(users...)
|
||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
return true
|
||||
}
|
||||
|
||||
if !usersSet.IsEmpty() && !usersSet.Contains(user) {
|
||||
return true
|
||||
entitiesWithMemberOf, ok := resMap[user]
|
||||
if !ok {
|
||||
if len(userMap) > 0 {
|
||||
return true
|
||||
}
|
||||
decodeUser := user
|
||||
if decodeFunc != nil {
|
||||
decodeUser = decodeFunc(user)
|
||||
}
|
||||
entitiesWithMemberOf = madmin.UserPolicyEntities{User: decodeUser}
|
||||
}
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
sort.Strings(ps)
|
||||
r = append(r, madmin.UserPolicyEntities{
|
||||
User: user,
|
||||
Policies: ps,
|
||||
})
|
||||
entitiesWithMemberOf.Policies = ps
|
||||
resMap[user] = entitiesWithMemberOf
|
||||
return true
|
||||
})
|
||||
|
||||
stsMap := xsync.NewMapOf[string, MappedPolicy]()
|
||||
for _, user := range users {
|
||||
// Attempt to load parent user mapping for STS accounts
|
||||
store.loadMappedPolicy(context.TODO(), user, stsUser, false, stsMap)
|
||||
}
|
||||
|
||||
stsMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
return true
|
||||
}
|
||||
|
||||
entitiesWithMemberOf := resMap[user]
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
sort.Strings(ps)
|
||||
r = append(r, madmin.UserPolicyEntities{
|
||||
User: user,
|
||||
Policies: ps,
|
||||
})
|
||||
entitiesWithMemberOf.Policies = ps
|
||||
resMap[user] = entitiesWithMemberOf
|
||||
return true
|
||||
})
|
||||
|
||||
for _, v := range resMap {
|
||||
if v.Policies != nil || v.MemberOfMappings != nil {
|
||||
r = append(r, v)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(r, func(i, j int) bool {
|
||||
return r[i].User < r[j].User
|
||||
})
|
||||
@@ -2140,11 +2162,11 @@ func (store *IAMStoreSys) listUserPolicyMappings(cache *iamCache, users []string
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If groups is empty, returns all group mappings.
|
||||
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []string,
|
||||
groupPredicate func(string) bool,
|
||||
func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groupsSet set.StringSet,
|
||||
groupPredicate func(string) bool, decodeFunc func(string) string,
|
||||
) []madmin.GroupPolicyEntities {
|
||||
var r []madmin.GroupPolicyEntities
|
||||
groupsSet := set.CreateStringSet(groups...)
|
||||
|
||||
cache.iamGroupPolicyMap.Range(func(group string, mappedPolicy MappedPolicy) bool {
|
||||
if groupPredicate != nil && !groupPredicate(group) {
|
||||
return true
|
||||
@@ -2154,10 +2176,15 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
|
||||
return true
|
||||
}
|
||||
|
||||
decodeGroup := group
|
||||
if decodeFunc != nil {
|
||||
decodeGroup = decodeFunc(group)
|
||||
}
|
||||
|
||||
ps := mappedPolicy.toSlice()
|
||||
sort.Strings(ps)
|
||||
r = append(r, madmin.GroupPolicyEntities{
|
||||
Group: group,
|
||||
Group: decodeGroup,
|
||||
Policies: ps,
|
||||
})
|
||||
return true
|
||||
@@ -2171,17 +2198,20 @@ func (store *IAMStoreSys) listGroupPolicyMappings(cache *iamCache, groups []stri
|
||||
}
|
||||
|
||||
// Assumes store is locked by caller. If policies is empty, returns all policy mappings.
|
||||
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
userPredicate, groupPredicate func(string) bool,
|
||||
func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, queryPolSet set.StringSet,
|
||||
userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string,
|
||||
) []madmin.PolicyEntities {
|
||||
queryPolSet := set.CreateStringSet(policies...)
|
||||
|
||||
policyToUsersMap := make(map[string]set.StringSet)
|
||||
cache.iamUserPolicyMap.Range(func(user string, mappedPolicy MappedPolicy) bool {
|
||||
if userPredicate != nil && !userPredicate(user) {
|
||||
return true
|
||||
}
|
||||
|
||||
decodeUser := user
|
||||
if decodeFunc != nil {
|
||||
decodeUser = decodeFunc(user)
|
||||
}
|
||||
|
||||
commonPolicySet := mappedPolicy.policySet()
|
||||
if !queryPolSet.IsEmpty() {
|
||||
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||
@@ -2189,9 +2219,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
for _, policy := range commonPolicySet.ToSlice() {
|
||||
s, ok := policyToUsersMap[policy]
|
||||
if !ok {
|
||||
policyToUsersMap[policy] = set.CreateStringSet(user)
|
||||
policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
|
||||
} else {
|
||||
s.Add(user)
|
||||
s.Add(decodeUser)
|
||||
policyToUsersMap[policy] = s
|
||||
}
|
||||
}
|
||||
@@ -2205,6 +2235,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
continue
|
||||
}
|
||||
|
||||
decodeUser := user
|
||||
if decodeFunc != nil {
|
||||
decodeUser = decodeFunc(user)
|
||||
}
|
||||
|
||||
var mappedPolicy MappedPolicy
|
||||
store.loadIAMConfig(context.Background(), &mappedPolicy, getMappedPolicyPath(user, stsUser, false))
|
||||
|
||||
@@ -2215,9 +2250,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
for _, policy := range commonPolicySet.ToSlice() {
|
||||
s, ok := policyToUsersMap[policy]
|
||||
if !ok {
|
||||
policyToUsersMap[policy] = set.CreateStringSet(user)
|
||||
policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
|
||||
} else {
|
||||
s.Add(user)
|
||||
s.Add(decodeUser)
|
||||
policyToUsersMap[policy] = s
|
||||
}
|
||||
}
|
||||
@@ -2232,6 +2267,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
return true
|
||||
}
|
||||
|
||||
decodeUser := user
|
||||
if decodeFunc != nil {
|
||||
decodeUser = decodeFunc(user)
|
||||
}
|
||||
|
||||
commonPolicySet := mappedPolicy.policySet()
|
||||
if !queryPolSet.IsEmpty() {
|
||||
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||
@@ -2239,9 +2279,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
for _, policy := range commonPolicySet.ToSlice() {
|
||||
s, ok := policyToUsersMap[policy]
|
||||
if !ok {
|
||||
policyToUsersMap[policy] = set.CreateStringSet(user)
|
||||
policyToUsersMap[policy] = set.CreateStringSet(decodeUser)
|
||||
} else {
|
||||
s.Add(user)
|
||||
s.Add(decodeUser)
|
||||
policyToUsersMap[policy] = s
|
||||
}
|
||||
}
|
||||
@@ -2256,6 +2296,11 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
return true
|
||||
}
|
||||
|
||||
decodeGroup := group
|
||||
if decodeFunc != nil {
|
||||
decodeGroup = decodeFunc(group)
|
||||
}
|
||||
|
||||
commonPolicySet := mappedPolicy.policySet()
|
||||
if !queryPolSet.IsEmpty() {
|
||||
commonPolicySet = commonPolicySet.Intersection(queryPolSet)
|
||||
@@ -2263,9 +2308,9 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
for _, policy := range commonPolicySet.ToSlice() {
|
||||
s, ok := policyToGroupsMap[policy]
|
||||
if !ok {
|
||||
policyToGroupsMap[policy] = set.CreateStringSet(group)
|
||||
policyToGroupsMap[policy] = set.CreateStringSet(decodeGroup)
|
||||
} else {
|
||||
s.Add(group)
|
||||
s.Add(decodeGroup)
|
||||
policyToGroupsMap[policy] = s
|
||||
}
|
||||
}
|
||||
@@ -2305,24 +2350,24 @@ func (store *IAMStoreSys) listPolicyMappings(cache *iamCache, policies []string,
|
||||
}
|
||||
|
||||
// ListPolicyMappings - return users/groups mapped to policies.
|
||||
func (store *IAMStoreSys) ListPolicyMappings(q madmin.PolicyEntitiesQuery,
|
||||
userPredicate, groupPredicate func(string) bool,
|
||||
func (store *IAMStoreSys) ListPolicyMappings(q cleanEntitiesQuery,
|
||||
userPredicate, groupPredicate func(string) bool, decodeFunc func(string) string,
|
||||
) madmin.PolicyEntitiesResult {
|
||||
cache := store.rlock()
|
||||
defer store.runlock()
|
||||
|
||||
var result madmin.PolicyEntitiesResult
|
||||
|
||||
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policy) == 0
|
||||
isAllPoliciesQuery := len(q.Users) == 0 && len(q.Groups) == 0 && len(q.Policies) == 0
|
||||
|
||||
if len(q.Users) > 0 {
|
||||
result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate)
|
||||
result.UserMappings = store.listUserPolicyMappings(cache, q.Users, userPredicate, decodeFunc)
|
||||
}
|
||||
if len(q.Groups) > 0 {
|
||||
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate)
|
||||
result.GroupMappings = store.listGroupPolicyMappings(cache, q.Groups, groupPredicate, decodeFunc)
|
||||
}
|
||||
if len(q.Policy) > 0 || isAllPoliciesQuery {
|
||||
result.PolicyMappings = store.listPolicyMappings(cache, q.Policy, userPredicate, groupPredicate)
|
||||
if len(q.Policies) > 0 || isAllPoliciesQuery {
|
||||
result.PolicyMappings = store.listPolicyMappings(cache, q.Policies, userPredicate, groupPredicate, decodeFunc)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+62
-6
@@ -217,6 +217,9 @@ func (sys *IAMSys) Load(ctx context.Context, firstTime bool) error {
|
||||
|
||||
if firstTime {
|
||||
bootstrapTraceMsg(fmt.Sprintf("globalIAMSys.Load(): (duration: %s)", loadDuration))
|
||||
if globalIsDistErasure {
|
||||
logger.Info("IAM load(startup) finished. (duration: %s)", loadDuration)
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -400,12 +403,12 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
|
||||
// Load all IAM items (except STS creds) periodically.
|
||||
refreshStart := time.Now()
|
||||
if err := sys.Load(ctx, false); err != nil {
|
||||
iamLogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (took %.2fs): %v", time.Since(refreshStart).Seconds(), err), logger.WarningKind)
|
||||
iamLogIf(ctx, fmt.Errorf("Failure in periodic refresh for IAM (duration: %s): %v", time.Since(refreshStart), err), logger.WarningKind)
|
||||
} else {
|
||||
took := time.Since(refreshStart).Seconds()
|
||||
if took > maxDurationSecondsForLog {
|
||||
// Log if we took a lot of time to load.
|
||||
logger.Info("IAM refresh took %.2fs", took)
|
||||
logger.Info("IAM refresh took (duration: %s)", took)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -807,6 +810,57 @@ func (sys *IAMSys) ListLDAPUsers(ctx context.Context) (map[string]madmin.UserInf
|
||||
}
|
||||
}
|
||||
|
||||
type cleanEntitiesQuery struct {
|
||||
Users map[string]set.StringSet
|
||||
Groups set.StringSet
|
||||
Policies set.StringSet
|
||||
}
|
||||
|
||||
// createCleanEntitiesQuery - maps users to their groups and normalizes user or group DNs if ldap.
|
||||
func (sys *IAMSys) createCleanEntitiesQuery(q madmin.PolicyEntitiesQuery, ldap bool) cleanEntitiesQuery {
|
||||
cleanQ := cleanEntitiesQuery{
|
||||
Users: make(map[string]set.StringSet),
|
||||
Groups: set.CreateStringSet(q.Groups...),
|
||||
Policies: set.CreateStringSet(q.Policy...),
|
||||
}
|
||||
|
||||
if ldap {
|
||||
// Validate and normalize users, then fetch and normalize their groups
|
||||
// Also include unvalidated users for backward compatibility.
|
||||
for _, user := range q.Users {
|
||||
lookupRes, actualGroups, _ := sys.LDAPConfig.GetValidatedDNWithGroups(user)
|
||||
if lookupRes != nil {
|
||||
groupSet := set.CreateStringSet(actualGroups...)
|
||||
|
||||
// duplicates can be overwritten, fetched groups should be identical.
|
||||
cleanQ.Users[lookupRes.NormDN] = groupSet
|
||||
}
|
||||
// Search for non-normalized DN as well for backward compatibility.
|
||||
if _, ok := cleanQ.Users[user]; !ok {
|
||||
cleanQ.Users[user] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Validate and normalize groups.
|
||||
for _, group := range q.Groups {
|
||||
lookupRes, underDN, _ := sys.LDAPConfig.GetValidatedGroupDN(nil, group)
|
||||
if lookupRes != nil && underDN {
|
||||
cleanQ.Groups.Add(lookupRes.NormDN)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, user := range q.Users {
|
||||
info, err := sys.store.GetUserInfo(user)
|
||||
var groupSet set.StringSet
|
||||
if err == nil {
|
||||
groupSet = set.CreateStringSet(info.MemberOf...)
|
||||
}
|
||||
cleanQ.Users[user] = groupSet
|
||||
}
|
||||
}
|
||||
return cleanQ
|
||||
}
|
||||
|
||||
// QueryLDAPPolicyEntities - queries policy associations for LDAP users/groups/policies.
|
||||
func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyEntitiesQuery) (*madmin.PolicyEntitiesResult, error) {
|
||||
if !sys.Initialized() {
|
||||
@@ -819,7 +873,8 @@ func (sys *IAMSys) QueryLDAPPolicyEntities(ctx context.Context, q madmin.PolicyE
|
||||
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
pe := sys.store.ListPolicyMappings(q, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN)
|
||||
cleanQuery := sys.createCleanEntitiesQuery(q, true)
|
||||
pe := sys.store.ListPolicyMappings(cleanQuery, sys.LDAPConfig.IsLDAPUserDN, sys.LDAPConfig.IsLDAPGroupDN, sys.LDAPConfig.DecodeDN)
|
||||
pe.Timestamp = UTCNow()
|
||||
return &pe, nil
|
||||
case <-ctx.Done():
|
||||
@@ -893,6 +948,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit
|
||||
|
||||
select {
|
||||
case <-sys.configLoaded:
|
||||
cleanQuery := sys.createCleanEntitiesQuery(q, false)
|
||||
var userPredicate, groupPredicate func(string) bool
|
||||
if sys.LDAPConfig.Enabled() {
|
||||
userPredicate = func(s string) bool {
|
||||
@@ -902,7 +958,7 @@ func (sys *IAMSys) QueryPolicyEntities(ctx context.Context, q madmin.PolicyEntit
|
||||
return !sys.LDAPConfig.IsLDAPGroupDN(s)
|
||||
}
|
||||
}
|
||||
pe := sys.store.ListPolicyMappings(q, userPredicate, groupPredicate)
|
||||
pe := sys.store.ListPolicyMappings(cleanQuery, userPredicate, groupPredicate, nil)
|
||||
pe.Timestamp = UTCNow()
|
||||
return &pe, nil
|
||||
case <-ctx.Done():
|
||||
@@ -1974,7 +2030,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
||||
if dnResult == nil {
|
||||
// dn not found - still attempt to detach if provided user is a DN.
|
||||
if !isAttach && sys.LDAPConfig.IsLDAPUserDN(r.User) {
|
||||
dn = r.User
|
||||
dn = sys.LDAPConfig.QuickNormalizeDN(r.User)
|
||||
} else {
|
||||
err = errNoSuchUser
|
||||
return
|
||||
@@ -1991,7 +2047,7 @@ func (sys *IAMSys) PolicyDBUpdateLDAP(ctx context.Context, isAttach bool,
|
||||
}
|
||||
if dnResult == nil || !underBaseDN {
|
||||
if !isAttach {
|
||||
dn = r.Group
|
||||
dn = sys.LDAPConfig.QuickNormalizeDN(r.Group)
|
||||
} else {
|
||||
err = errNoSuchGroup
|
||||
return
|
||||
|
||||
@@ -532,6 +532,9 @@ func (m *metaCacheEntriesSorted) fileInfoVersions(bucket, prefix, delimiter, aft
|
||||
}
|
||||
|
||||
for _, version := range fiVersions {
|
||||
if !version.VersionPurgeStatus().Empty() {
|
||||
continue
|
||||
}
|
||||
versioned := vcfg != nil && vcfg.Versioned(entry.name)
|
||||
versions = append(versions, version.ToObjectInfo(bucket, entry.name, versioned))
|
||||
}
|
||||
@@ -593,7 +596,7 @@ func (m *metaCacheEntriesSorted) fileInfos(bucket, prefix, delimiter string) (ob
|
||||
}
|
||||
|
||||
fi, err := entry.fileInfo(bucket)
|
||||
if err == nil {
|
||||
if err == nil && fi.VersionPurgeStatus().Empty() {
|
||||
versioned := vcfg != nil && vcfg.Versioned(entry.name)
|
||||
objects = append(objects, fi.ToObjectInfo(bucket, entry.name, versioned))
|
||||
}
|
||||
|
||||
@@ -153,19 +153,7 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (
|
||||
} else {
|
||||
// Continue listing
|
||||
o.ID = c.id
|
||||
go func(meta metacache) {
|
||||
// Continuously update while we wait.
|
||||
t := time.NewTicker(metacacheMaxClientWait / 10)
|
||||
defer t.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Request is done, stop updating.
|
||||
return
|
||||
case <-t.C:
|
||||
meta.lastHandout = time.Now()
|
||||
meta, _ = rpc.UpdateMetacacheListing(ctx, meta)
|
||||
}
|
||||
}(*c)
|
||||
go c.keepAlive(ctx, rpc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +207,9 @@ func (z *erasureServerPools) listPath(ctx context.Context, o *listPathOptions) (
|
||||
o.ID = ""
|
||||
}
|
||||
|
||||
if contextCanceled(ctx) {
|
||||
return entries, ctx.Err()
|
||||
}
|
||||
// Do listing in-place.
|
||||
// Create output for our results.
|
||||
// Create filter for results.
|
||||
@@ -449,5 +440,10 @@ func (z *erasureServerPools) listAndSave(ctx context.Context, o *listPathOptions
|
||||
xioutil.SafeClose(saveCh)
|
||||
}()
|
||||
|
||||
return filteredResults()
|
||||
entries, err = filteredResults()
|
||||
if err == nil {
|
||||
// Check if listing recorded an error.
|
||||
err = meta.getErr()
|
||||
}
|
||||
return entries, err
|
||||
}
|
||||
|
||||
@@ -805,6 +805,17 @@ func (m *metaCacheRPC) setErr(err string) {
|
||||
*m.meta = meta
|
||||
}
|
||||
|
||||
// getErr will return an error if the listing failed.
|
||||
// The error is not type safe.
|
||||
func (m *metaCacheRPC) getErr() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.meta.status == scanStateError {
|
||||
return errors.New(m.meta.error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (er *erasureObjects) saveMetaCacheStream(ctx context.Context, mc *metaCacheRPC, entries <-chan metaCacheEntry) (err error) {
|
||||
o := mc.o
|
||||
o.debugf(color.Green("saveMetaCacheStream:")+" with options: %#v", o)
|
||||
|
||||
+42
-5
@@ -24,6 +24,8 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/pkg/v3/console"
|
||||
)
|
||||
|
||||
type scanStatus uint8
|
||||
@@ -97,6 +99,37 @@ func (m *metacache) worthKeeping() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// keepAlive will continuously update lastHandout until ctx is canceled.
|
||||
func (m metacache) keepAlive(ctx context.Context, rpc *peerRESTClient) {
|
||||
// we intentionally operate on a copy of m, so we can update without locks.
|
||||
t := time.NewTicker(metacacheMaxClientWait / 10)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// Request is done, stop updating.
|
||||
return
|
||||
case <-t.C:
|
||||
m.lastHandout = time.Now()
|
||||
|
||||
if m2, err := rpc.UpdateMetacacheListing(ctx, m); err == nil {
|
||||
if m2.status != scanStateStarted {
|
||||
if serverDebugLog {
|
||||
console.Debugln("returning", m.id, "due to scan state", m2.status, time.Now().Format(time.RFC3339))
|
||||
}
|
||||
return
|
||||
}
|
||||
m = m2
|
||||
if serverDebugLog {
|
||||
console.Debugln("refreshed", m.id, time.Now().Format(time.RFC3339))
|
||||
}
|
||||
} else if serverDebugLog {
|
||||
console.Debugln("error refreshing", m.id, time.Now().Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// baseDirFromPrefix will return the base directory given an object path.
|
||||
// For example an object with name prefix/folder/object.ext will return `prefix/folder/`.
|
||||
func baseDirFromPrefix(prefix string) string {
|
||||
@@ -116,13 +149,17 @@ func baseDirFromPrefix(prefix string) string {
|
||||
// update cache with new status.
|
||||
// The updates are conditional so multiple callers can update with different states.
|
||||
func (m *metacache) update(update metacache) {
|
||||
m.lastUpdate = UTCNow()
|
||||
now := UTCNow()
|
||||
m.lastUpdate = now
|
||||
|
||||
if m.lastHandout.After(m.lastHandout) {
|
||||
m.lastHandout = UTCNow()
|
||||
if update.lastHandout.After(m.lastHandout) {
|
||||
m.lastHandout = update.lastUpdate
|
||||
if m.lastHandout.After(now) {
|
||||
m.lastHandout = now
|
||||
}
|
||||
}
|
||||
if m.status == scanStateStarted && update.status == scanStateSuccess {
|
||||
m.ended = UTCNow()
|
||||
m.ended = now
|
||||
}
|
||||
|
||||
if m.status == scanStateStarted && update.status != scanStateStarted {
|
||||
@@ -138,7 +175,7 @@ func (m *metacache) update(update metacache) {
|
||||
if m.error == "" && update.error != "" {
|
||||
m.error = update.error
|
||||
m.status = scanStateError
|
||||
m.ended = UTCNow()
|
||||
m.ended = now
|
||||
}
|
||||
m.fileNotFound = m.fileNotFound || update.fileNotFound
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ func fwdStatusToAPIError(resp *http.Response) *APIError {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetObjectLamdbaHandler - GET Object with transformed data via lambda functions
|
||||
// GetObjectLambdaHandler - GET Object with transformed data via lambda functions
|
||||
// ----------
|
||||
// This implementation of the GET operation applies lambda functions and returns the
|
||||
// response generated via the lambda functions. To use this API, you must have READ access
|
||||
|
||||
@@ -364,7 +364,7 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro
|
||||
for key := range checkHeader {
|
||||
logKeys = append(logKeys, key)
|
||||
}
|
||||
return fmt.Errorf("Each form field that you specify in a form (except %s) must appear in the list of conditions.", strings.Join(logKeys, ", "))
|
||||
return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", "))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -166,13 +166,13 @@ func connectLoadInitFormats(verboseLogging bool, firstDisk bool, storageDisks []
|
||||
if err != nil && !errors.Is(err, errXLBackend) && !errors.Is(err, errUnformattedDisk) {
|
||||
if errors.Is(err, errDiskNotFound) && verboseLogging {
|
||||
if globalEndpoints.NEndpoints() > 1 {
|
||||
logger.Error("Unable to connect to %s: %v", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
||||
logger.Info("Unable to connect to %s: %v, will be retried", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
||||
} else {
|
||||
logger.Fatal(err, "Unable to connect to %s: %v", endpoints[i], isServerResolvable(endpoints[i], time.Second))
|
||||
}
|
||||
} else {
|
||||
if globalEndpoints.NEndpoints() > 1 {
|
||||
logger.Error("Unable to use the drive %s: %v", endpoints[i], err)
|
||||
logger.Info("Unable to use the drive %s: %v, will be retried", endpoints[i], err)
|
||||
} else {
|
||||
logger.Fatal(errInvalidArgument, "Unable to use the drive %s: %v", endpoints[i], err)
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ func registerDistErasureRouters(router *mux.Router, endpointServerPools Endpoint
|
||||
registerLockRESTHandlers()
|
||||
|
||||
// Add grid to router
|
||||
router.Handle(grid.RoutePath, adminMiddleware(globalGrid.Load().Handler(), noGZFlag, noObjLayerFlag))
|
||||
router.Handle(grid.RoutePath, adminMiddleware(globalGrid.Load().Handler(storageServerRequestValidate), noGZFlag, noObjLayerFlag))
|
||||
}
|
||||
|
||||
// List of some generic middlewares which are applied for all incoming requests.
|
||||
|
||||
+16
-19
@@ -841,13 +841,14 @@ func serverMain(ctx *cli.Context) {
|
||||
|
||||
// Verify kernel release and version.
|
||||
if oldLinux() {
|
||||
warnings = append(warnings, color.YellowBold("- Detected Linux kernel version older than 4.0.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x.x linux kernel version for best performance"))
|
||||
warnings = append(warnings, color.YellowBold("Detected Linux kernel version older than 4.0 release, there are some known potential performance problems with this kernel version. MinIO recommends a minimum of 4.x linux kernel version for best performance"))
|
||||
}
|
||||
|
||||
maxProcs := runtime.GOMAXPROCS(0)
|
||||
cpuProcs := runtime.NumCPU()
|
||||
if maxProcs < cpuProcs {
|
||||
warnings = append(warnings, color.YellowBold("- Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance", maxProcs, cpuProcs))
|
||||
warnings = append(warnings, color.YellowBold("Detected GOMAXPROCS(%d) < NumCPU(%d), please make sure to provide all PROCS to MinIO for optimal performance",
|
||||
maxProcs, cpuProcs))
|
||||
}
|
||||
|
||||
// Initialize grid
|
||||
@@ -921,16 +922,18 @@ func serverMain(ctx *cli.Context) {
|
||||
}
|
||||
|
||||
bootstrapTrace("waitForQuorum", func() {
|
||||
result := newObject.Health(context.Background(), HealthOptions{})
|
||||
result := newObject.Health(context.Background(), HealthOptions{NoLogging: true})
|
||||
for !result.HealthyRead {
|
||||
if debugNoExit {
|
||||
logger.Info("Not waiting for quorum since we are debugging.. possible cause unhealthy sets (%s)", result)
|
||||
logger.Info("Not waiting for quorum since we are debugging.. possible cause unhealthy sets")
|
||||
logger.Info(result.String())
|
||||
break
|
||||
}
|
||||
d := time.Duration(r.Float64() * float64(time.Second))
|
||||
logger.Info("Waiting for quorum READ healthcheck to succeed.. possible cause unhealthy sets (%s), retrying in %s", result, d)
|
||||
logger.Info("Waiting for quorum READ healthcheck to succeed retrying in %s.. possible cause unhealthy sets", d)
|
||||
logger.Info(result.String())
|
||||
time.Sleep(d)
|
||||
result = newObject.Health(context.Background(), HealthOptions{})
|
||||
result = newObject.Health(context.Background(), HealthOptions{NoLogging: true})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -953,11 +956,11 @@ func serverMain(ctx *cli.Context) {
|
||||
}
|
||||
|
||||
if !globalServerCtxt.StrictS3Compat {
|
||||
warnings = append(warnings, color.YellowBold("- Strict AWS S3 compatible incoming PUT, POST content payload validation is turned off, caution is advised do not use in production"))
|
||||
warnings = append(warnings, color.YellowBold("Strict AWS S3 compatible incoming PUT, POST content payload validation is turned off, caution is advised do not use in production"))
|
||||
}
|
||||
})
|
||||
if globalActiveCred.Equal(auth.DefaultCredentials) {
|
||||
msg := fmt.Sprintf("- Detected default credentials '%s', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables",
|
||||
msg := fmt.Sprintf("Detected default credentials '%s', we recommend that you change these values with 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD' environment variables",
|
||||
globalActiveCred)
|
||||
warnings = append(warnings, color.YellowBold(msg))
|
||||
}
|
||||
@@ -1103,18 +1106,12 @@ func serverMain(ctx *cli.Context) {
|
||||
printStartupMessage(getAPIEndpoints(), err)
|
||||
|
||||
// Print a warning at the end of the startup banner so it is more noticeable
|
||||
if newObject.BackendInfo().StandardSCParity == 0 {
|
||||
warnings = append(warnings, color.YellowBold("- The standard parity is set to 0. This can lead to data loss."))
|
||||
if newObject.BackendInfo().StandardSCParity == 0 && !globalIsErasureSD {
|
||||
warnings = append(warnings, color.YellowBold("The standard parity is set to 0. This can lead to data loss."))
|
||||
}
|
||||
objAPI := newObjectLayerFn()
|
||||
if objAPI != nil {
|
||||
printStorageInfo(objAPI.StorageInfo(GlobalContext, true))
|
||||
}
|
||||
if len(warnings) > 0 {
|
||||
logger.Info(color.Yellow("STARTUP WARNINGS:"))
|
||||
for _, warn := range warnings {
|
||||
logger.Info(warn)
|
||||
}
|
||||
|
||||
for _, warn := range warnings {
|
||||
logger.Warning(warn)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
+21
-41
@@ -23,7 +23,6 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
"github.com/minio/minio/internal/color"
|
||||
"github.com/minio/minio/internal/logger"
|
||||
xnet "github.com/minio/pkg/v3/net"
|
||||
@@ -37,7 +36,11 @@ func getFormatStr(strLen int, padding int) string {
|
||||
|
||||
// Prints the formatted startup message.
|
||||
func printStartupMessage(apiEndpoints []string, err error) {
|
||||
logger.Info(color.Bold(MinioBannerName))
|
||||
banner := strings.Repeat("-", len(MinioBannerName))
|
||||
if globalIsDistErasure {
|
||||
logger.Startup(color.Bold(banner))
|
||||
}
|
||||
logger.Startup(color.Bold(MinioBannerName))
|
||||
if err != nil {
|
||||
if globalConsoleSys != nil {
|
||||
globalConsoleSys.Send(GlobalContext, fmt.Sprintf("Server startup failed with '%v', some features may be missing", err))
|
||||
@@ -47,7 +50,7 @@ func printStartupMessage(apiEndpoints []string, err error) {
|
||||
if !globalSubnetConfig.Registered() {
|
||||
var builder strings.Builder
|
||||
startupBanner(&builder)
|
||||
logger.Info(builder.String())
|
||||
logger.Startup(builder.String())
|
||||
}
|
||||
|
||||
strippedAPIEndpoints := stripStandardPorts(apiEndpoints, globalMinioHost)
|
||||
@@ -61,6 +64,9 @@ func printStartupMessage(apiEndpoints []string, err error) {
|
||||
|
||||
// Prints documentation message.
|
||||
printObjectAPIMsg()
|
||||
if globalIsDistErasure {
|
||||
logger.Startup(color.Bold(banner))
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if input is IPv6
|
||||
@@ -113,21 +119,21 @@ func printServerCommonMsg(apiEndpoints []string) {
|
||||
|
||||
apiEndpointStr := strings.TrimSpace(strings.Join(apiEndpoints, " "))
|
||||
// Colorize the message and print.
|
||||
logger.Info(color.Blue("API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
|
||||
logger.Startup(color.Blue("API: ") + color.Bold(fmt.Sprintf("%s ", apiEndpointStr)))
|
||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON && globalAPIConfig.permitRootAccess()) {
|
||||
logger.Info(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||
logger.Info(color.Blue(" RootPass: ") + color.Bold("%s \n", cred.SecretKey))
|
||||
logger.Startup(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||
logger.Startup(color.Blue(" RootPass: ") + color.Bold("%s \n", cred.SecretKey))
|
||||
if region != "" {
|
||||
logger.Info(color.Blue(" Region: ") + color.Bold("%s", fmt.Sprintf(getFormatStr(len(region), 2), region)))
|
||||
logger.Startup(color.Blue(" Region: ") + color.Bold("%s", fmt.Sprintf(getFormatStr(len(region), 2), region)))
|
||||
}
|
||||
}
|
||||
|
||||
if globalBrowserEnabled {
|
||||
consoleEndpointStr := strings.Join(stripStandardPorts(getConsoleEndpoints(), globalMinioConsoleHost), " ")
|
||||
logger.Info(color.Blue("WebUI: ") + color.Bold(fmt.Sprintf("%s ", consoleEndpointStr)))
|
||||
logger.Startup(color.Blue("WebUI: ") + color.Bold(fmt.Sprintf("%s ", consoleEndpointStr)))
|
||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && !globalServerCtxt.JSON && globalAPIConfig.permitRootAccess()) {
|
||||
logger.Info(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||
logger.Info(color.Blue(" RootPass: ") + color.Bold("%s ", cred.SecretKey))
|
||||
logger.Startup(color.Blue(" RootUser: ") + color.Bold("%s ", cred.AccessKey))
|
||||
logger.Startup(color.Blue(" RootPass: ") + color.Bold("%s ", cred.SecretKey))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +143,7 @@ func printServerCommonMsg(apiEndpoints []string) {
|
||||
|
||||
// Prints startup message for Object API access, prints link to our SDK documentation.
|
||||
func printObjectAPIMsg() {
|
||||
logger.Info(color.Blue("\nDocs: ") + "https://min.io/docs/minio/linux/index.html")
|
||||
logger.Startup(color.Blue("\nDocs: ") + "https://min.io/docs/minio/linux/index.html")
|
||||
}
|
||||
|
||||
func printLambdaTargets() {
|
||||
@@ -149,7 +155,7 @@ func printLambdaTargets() {
|
||||
for _, arn := range globalLambdaTargetList.List(globalSite.Region()) {
|
||||
arnMsg += color.Bold(fmt.Sprintf("%s ", arn))
|
||||
}
|
||||
logger.Info(arnMsg + "\n")
|
||||
logger.Startup(arnMsg + "\n")
|
||||
}
|
||||
|
||||
// Prints bucket notification configurations.
|
||||
@@ -168,7 +174,7 @@ func printEventNotifiers() {
|
||||
arnMsg += color.Bold(fmt.Sprintf("%s ", arn))
|
||||
}
|
||||
|
||||
logger.Info(arnMsg + "\n")
|
||||
logger.Startup(arnMsg + "\n")
|
||||
}
|
||||
|
||||
// Prints startup message for command line access. Prints link to our documentation
|
||||
@@ -181,35 +187,9 @@ func printCLIAccessMsg(endPoint string, alias string) {
|
||||
|
||||
// Configure 'mc', following block prints platform specific information for minio client.
|
||||
if color.IsTerminal() && (!globalServerCtxt.Anonymous && globalAPIConfig.permitRootAccess()) {
|
||||
logger.Info(color.Blue("\nCLI: ") + mcQuickStartGuide)
|
||||
logger.Startup(color.Blue("\nCLI: ") + mcQuickStartGuide)
|
||||
mcMessage := fmt.Sprintf("$ mc alias set '%s' '%s' '%s' '%s'", alias,
|
||||
endPoint, cred.AccessKey, cred.SecretKey)
|
||||
logger.Info(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
|
||||
}
|
||||
}
|
||||
|
||||
// Get formatted disk/storage info message.
|
||||
func getStorageInfoMsg(storageInfo StorageInfo) string {
|
||||
var msg string
|
||||
var mcMessage string
|
||||
onlineDisks, offlineDisks := getOnlineOfflineDisksStats(storageInfo.Disks)
|
||||
if storageInfo.Backend.Type == madmin.Erasure {
|
||||
if offlineDisks.Sum() > 0 {
|
||||
mcMessage = "Use `mc admin info` to look for latest server/drive info\n"
|
||||
}
|
||||
|
||||
diskInfo := fmt.Sprintf(" %d Online, %d Offline. ", onlineDisks.Sum(), offlineDisks.Sum())
|
||||
msg += color.Blue("Status:") + fmt.Sprintf(getFormatStr(len(diskInfo), 8), diskInfo)
|
||||
if len(mcMessage) > 0 {
|
||||
msg = fmt.Sprintf("%s %s", mcMessage, msg)
|
||||
}
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
// Prints startup message of storage capacity and erasure information.
|
||||
func printStorageInfo(storageInfo StorageInfo) {
|
||||
if msg := getStorageInfoMsg(storageInfo); msg != "" {
|
||||
logger.Info(msg)
|
||||
logger.Startup(fmt.Sprintf(getFormatStr(len(mcMessage), 3), mcMessage))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,32 +21,9 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/minio/madmin-go/v3"
|
||||
)
|
||||
|
||||
// Tests if we generate storage info.
|
||||
func TestStorageInfoMsg(t *testing.T) {
|
||||
infoStorage := StorageInfo{}
|
||||
infoStorage.Disks = []madmin.Disk{
|
||||
{Endpoint: "http://127.0.0.1:9000/data/1/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9000/data/2/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9000/data/3/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9000/data/4/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9001/data/1/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9001/data/2/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9001/data/3/", State: madmin.DriveStateOk},
|
||||
{Endpoint: "http://127.0.0.1:9001/data/4/", State: madmin.DriveStateOffline},
|
||||
}
|
||||
infoStorage.Backend.Type = madmin.Erasure
|
||||
|
||||
if msg := getStorageInfoMsg(infoStorage); !strings.Contains(msg, "7 Online, 1 Offline") {
|
||||
t.Fatal("Unexpected storage info message, found:", msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests stripping standard ports from apiEndpoints.
|
||||
func TestStripStandardPorts(t *testing.T) {
|
||||
apiEndpoints := []string{"http://127.0.0.1:9000", "http://127.0.0.2:80", "https://127.0.0.3:443"}
|
||||
|
||||
+44
-14
@@ -2250,10 +2250,18 @@ func (c *SiteReplicationSys) toErrorFromErrMap(errMap map[string]error, actionNa
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get ordered list of keys of errMap
|
||||
keys := []string{}
|
||||
for d := range errMap {
|
||||
keys = append(keys, d)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var success int
|
||||
msgs := []string{}
|
||||
for d, err := range errMap {
|
||||
for _, d := range keys {
|
||||
name := c.state.Peers[d].Name
|
||||
err := errMap[d]
|
||||
if err == nil {
|
||||
msgs = append(msgs, fmt.Sprintf("'%s' on site %s (%s): succeeded", actionName, name, d))
|
||||
success++
|
||||
@@ -2261,7 +2269,7 @@ func (c *SiteReplicationSys) toErrorFromErrMap(errMap map[string]error, actionNa
|
||||
msgs = append(msgs, fmt.Sprintf("'%s' on site %s (%s): failed(%v)", actionName, name, d, err))
|
||||
}
|
||||
}
|
||||
if success == len(errMap) {
|
||||
if success == len(keys) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Site replication error(s): \n%s", strings.Join(msgs, "\n"))
|
||||
@@ -5225,7 +5233,7 @@ func (c *SiteReplicationSys) healBucketReplicationConfig(ctx context.Context, ob
|
||||
}
|
||||
|
||||
if replMismatch {
|
||||
replLogIf(ctx, c.annotateErr(configureReplication, c.PeerBucketConfigureReplHandler(ctx, bucket)))
|
||||
replLogOnceIf(ctx, c.annotateErr(configureReplication, c.PeerBucketConfigureReplHandler(ctx, bucket)), "heal-bucket-relication-config")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -5318,7 +5326,10 @@ func (c *SiteReplicationSys) healPolicies(ctx context.Context, objAPI ObjectLaye
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err))
|
||||
replLogOnceIf(
|
||||
ctx,
|
||||
fmt.Errorf("Unable to heal IAM policy %s from peer site %s -> site %s : %w", policy, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-policy-%s", policy))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5379,7 +5390,8 @@ func (c *SiteReplicationSys) healUserPolicies(ctx context.Context, objAPI Object
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx, fmt.Errorf("Unable to heal IAM user policy mapping for %s from peer site %s -> site %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-policy-%s", user))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5442,7 +5454,9 @@ func (c *SiteReplicationSys) healGroupPolicies(ctx context.Context, objAPI Objec
|
||||
UpdatedAt: lastUpdate,
|
||||
})
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal IAM group policy mapping for %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-group-policy-%s", group))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5503,13 +5517,17 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
if creds.IsServiceAccount() {
|
||||
claims, err := globalIAMSys.GetClaimsForSvcAcc(ctx, creds.AccessKey)
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
|
||||
_, policy, err := globalIAMSys.GetServiceAccount(ctx, creds.AccessKey)
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -5517,7 +5535,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
if policy != nil {
|
||||
policyJSON, err = json.Marshal(policy)
|
||||
if err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -5540,7 +5560,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal service account %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -5553,7 +5575,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
// policy. The session token will contain info about policy to
|
||||
// be applied.
|
||||
if !errors.Is(err, errNoSuchUser) {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
@@ -5571,7 +5595,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal temporary credentials %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
}
|
||||
continue
|
||||
}
|
||||
@@ -5587,7 +5613,9 @@ func (c *SiteReplicationSys) healUsers(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal user %s from peer site %s -> %s : %w", user, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-user-%s", user))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -5651,7 +5679,9 @@ func (c *SiteReplicationSys) healGroups(ctx context.Context, objAPI ObjectLayer,
|
||||
},
|
||||
UpdatedAt: lastUpdate,
|
||||
}); err != nil {
|
||||
replLogIf(ctx, fmt.Errorf("Unable to heal group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err))
|
||||
replLogOnceIf(ctx,
|
||||
fmt.Errorf("Unable to heal group %s from peer site %s -> site %s : %w", group, latestPeerName, peerName, err),
|
||||
fmt.Sprintf("heal-group-%s", group))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+20
-13
@@ -109,6 +109,24 @@ func (s *storageRESTServer) writeErrorResponse(w http.ResponseWriter, err error)
|
||||
// DefaultSkewTime - skew time is 15 minutes between minio peers.
|
||||
const DefaultSkewTime = 15 * time.Minute
|
||||
|
||||
// validateStorageRequestToken will validate the token against the provided audience.
|
||||
func validateStorageRequestToken(token, audience string) error {
|
||||
claims := xjwt.NewStandardClaims()
|
||||
if err := xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
owner := claims.AccessKey == globalActiveCred.AccessKey || claims.Subject == globalActiveCred.AccessKey
|
||||
if !owner {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
if claims.Audience != audience {
|
||||
return errAuthentication
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Authenticates storage client's requests and validates for skewed time.
|
||||
func storageServerRequestValidate(r *http.Request) error {
|
||||
token, err := jwtreq.AuthorizationHeaderExtractor.ExtractToken(r)
|
||||
@@ -118,19 +136,8 @@ func storageServerRequestValidate(r *http.Request) error {
|
||||
}
|
||||
return errMalformedAuth
|
||||
}
|
||||
|
||||
claims := xjwt.NewStandardClaims()
|
||||
if err = xjwt.ParseWithStandardClaims(token, claims, []byte(globalActiveCred.SecretKey)); err != nil {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
owner := claims.AccessKey == globalActiveCred.AccessKey || claims.Subject == globalActiveCred.AccessKey
|
||||
if !owner {
|
||||
return errAuthentication
|
||||
}
|
||||
|
||||
if claims.Audience != r.URL.RawQuery {
|
||||
return errAuthentication
|
||||
if err = validateStorageRequestToken(token, r.URL.RawQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
requestTimeStr := r.Header.Get("X-Minio-Time")
|
||||
|
||||
@@ -733,6 +733,7 @@ func TestIAMWithLDAPServerSuite(t *testing.T) {
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpLDAP(c, ldapServer)
|
||||
suite.TestLDAPSTS(c)
|
||||
suite.TestLDAPPolicyEntitiesLookup(c)
|
||||
suite.TestLDAPUnicodeVariations(c)
|
||||
suite.TestLDAPSTSServiceAccounts(c)
|
||||
suite.TestLDAPSTSServiceAccountsWithUsername(c)
|
||||
@@ -764,6 +765,7 @@ func TestIAMWithLDAPNonNormalizedBaseDNConfigServerSuite(t *testing.T) {
|
||||
suite.SetUpSuite(c)
|
||||
suite.SetUpLDAPWithNonNormalizedBaseDN(c, ldapServer)
|
||||
suite.TestLDAPSTS(c)
|
||||
suite.TestLDAPPolicyEntitiesLookup(c)
|
||||
suite.TestLDAPUnicodeVariations(c)
|
||||
suite.TestLDAPSTSServiceAccounts(c)
|
||||
suite.TestLDAPSTSServiceAccountsWithUsername(c)
|
||||
@@ -2096,6 +2098,86 @@ func (s *TestSuiteIAM) TestLDAPAttributesLookup(c *check) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestLDAPPolicyEntitiesLookup(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testDefaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
groupDN := "cn=projectb,ou=groups,ou=swengg,dc=min,dc=io"
|
||||
groupPolicy := "readwrite"
|
||||
groupReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{groupPolicy},
|
||||
Group: groupDN,
|
||||
}
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, groupReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to attach group policy: %v", err)
|
||||
}
|
||||
type caseTemplate struct {
|
||||
inDN string
|
||||
expectedOutDN string
|
||||
expectedGroupDN string
|
||||
expectedGroupPolicy string
|
||||
}
|
||||
cases := []caseTemplate{
|
||||
{
|
||||
inDN: "uid=dillon,ou=people,ou=swengg,dc=min,dc=io",
|
||||
expectedOutDN: "uid=dillon,ou=people,ou=swengg,dc=min,dc=io",
|
||||
expectedGroupDN: groupDN,
|
||||
expectedGroupPolicy: groupPolicy,
|
||||
},
|
||||
}
|
||||
|
||||
policy := "readonly"
|
||||
for _, testCase := range cases {
|
||||
userReq := madmin.PolicyAssociationReq{
|
||||
Policies: []string{policy},
|
||||
User: testCase.inDN,
|
||||
}
|
||||
_, err := s.adm.AttachPolicyLDAP(ctx, userReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to attach policy: %v", err)
|
||||
}
|
||||
|
||||
entities, err := s.adm.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{
|
||||
Users: []string{testCase.inDN},
|
||||
Policy: []string{policy},
|
||||
})
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to fetch policy entities: %v", err)
|
||||
}
|
||||
|
||||
// switch statement to check all the conditions
|
||||
switch {
|
||||
case len(entities.UserMappings) != 1:
|
||||
c.Fatalf("Expected to find exactly one user mapping")
|
||||
case entities.UserMappings[0].User != testCase.expectedOutDN:
|
||||
c.Fatalf("Expected user DN `%s`, found `%s`", testCase.expectedOutDN, entities.UserMappings[0].User)
|
||||
case len(entities.UserMappings[0].Policies) != 1:
|
||||
c.Fatalf("Expected exactly one policy attached to user")
|
||||
case entities.UserMappings[0].Policies[0] != policy:
|
||||
c.Fatalf("Expected attached policy `%s`, found `%s`", policy, entities.UserMappings[0].Policies[0])
|
||||
case len(entities.UserMappings[0].MemberOfMappings) != 1:
|
||||
c.Fatalf("Expected exactly one group attached to user")
|
||||
case entities.UserMappings[0].MemberOfMappings[0].Group != testCase.expectedGroupDN:
|
||||
c.Fatalf("Expected attached group `%s`, found `%s`", testCase.expectedGroupDN, entities.UserMappings[0].MemberOfMappings[0].Group)
|
||||
case len(entities.UserMappings[0].MemberOfMappings[0].Policies) != 1:
|
||||
c.Fatalf("Expected exactly one policy attached to group")
|
||||
case entities.UserMappings[0].MemberOfMappings[0].Policies[0] != testCase.expectedGroupPolicy:
|
||||
c.Fatalf("Expected attached policy `%s`, found `%s`", testCase.expectedGroupPolicy, entities.UserMappings[0].MemberOfMappings[0].Policies[0])
|
||||
}
|
||||
|
||||
_, err = s.adm.DetachPolicyLDAP(ctx, userReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to detach policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.adm.DetachPolicyLDAP(ctx, groupReq)
|
||||
if err != nil {
|
||||
c.Fatalf("Unable to detach group policy: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *TestSuiteIAM) TestOpenIDSTS(c *check) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestMain(m *testing.M) {
|
||||
// Disable printing console messages during tests.
|
||||
color.Output = io.Discard
|
||||
// Disable Error logging in testing.
|
||||
logger.DisableErrorLog = true
|
||||
logger.DisableLog = true
|
||||
|
||||
// Uncomment the following line to see trace logs during unit tests.
|
||||
// logger.AddTarget(console.New())
|
||||
|
||||
@@ -178,7 +178,7 @@ When an object has only one version as a delete marker, the latter can be automa
|
||||
{
|
||||
"ID": "Removing all delete markers",
|
||||
"Expiration": {
|
||||
"DeleteMarker": true
|
||||
"ExpiredObjectDeleteMarker": true
|
||||
},
|
||||
"Status": "Enabled"
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ export MINIO_ROOT_USER="minioadmin"
|
||||
export MINIO_ROOT_PASSWORD="minioadmin"
|
||||
|
||||
./minio server --address ":9001" /tmp/xl/1/{1...4}/ 2>&1 >/tmp/dc1.log &
|
||||
pid1=$!
|
||||
./minio server --address ":9002" /tmp/xl/2/{1...4}/ 2>&1 >/tmp/dc2.log &
|
||||
pid2=$!
|
||||
|
||||
sleep 3
|
||||
|
||||
@@ -69,6 +71,8 @@ export MC_HOST_myminio2=http://minioadmin:minioadmin@localhost:9002
|
||||
|
||||
./mc replicate add myminio1/testbucket --remote-bucket http://minioadmin:minioadmin@localhost:9002/testbucket/ --priority 1
|
||||
|
||||
# Test replication of delete markers and permanent deletes
|
||||
|
||||
./mc cp README.md myminio1/testbucket/dir/file
|
||||
./mc cp README.md myminio1/testbucket/dir/file
|
||||
|
||||
@@ -111,5 +115,33 @@ if [ $ret -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test listing of non replicated permanent deletes
|
||||
|
||||
set -x
|
||||
|
||||
./mc mb myminio1/foobucket/ myminio2/foobucket/ --with-versioning
|
||||
./mc replicate add myminio1/foobucket --remote-bucket http://minioadmin:minioadmin@localhost:9002/foobucket/ --priority 1
|
||||
./mc cp README.md myminio1/foobucket/dir/file
|
||||
|
||||
versionId="$(./mc ls --json --versions myminio1/foobucket/dir/ | jq -r .versionId)"
|
||||
|
||||
kill ${pid2} && wait ${pid2} || true
|
||||
|
||||
aws s3api --endpoint-url http://localhost:9001 --profile minioadmin delete-object --bucket foobucket --key dir/file --version-id "$versionId"
|
||||
|
||||
out="$(./mc ls myminio1/foobucket/dir/)"
|
||||
if [ "$out" != "" ]; then
|
||||
echo "BUG: non versioned listing should not show pending/failed replicated delete:"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
out="$(./mc ls --versions myminio1/foobucket/dir/)"
|
||||
if [ "$out" != "" ]; then
|
||||
echo "BUG: versioned listing should not show pending/failed replicated deletes:"
|
||||
echo "$out"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Success"
|
||||
catch
|
||||
|
||||
@@ -2,7 +2,7 @@ version: '3.7'
|
||||
|
||||
# Settings and configurations that are common for all containers
|
||||
x-minio-common: &minio-common
|
||||
image: quay.io/minio/minio:RELEASE.2024-06-29T01-20-47Z
|
||||
image: quay.io/minio/minio:RELEASE.2024-07-10T18-41-49Z
|
||||
command: server --console-address ":9001" http://minio{1...4}/data{1...2}
|
||||
expose:
|
||||
- "9000"
|
||||
|
||||
@@ -31,8 +31,6 @@ MINIO_IDENTITY_OPENID_CLAIM_USERINFO (on|off) Enable fetching claims f
|
||||
MINIO_IDENTITY_OPENID_KEYCLOAK_REALM (string) Specify Keycloak 'realm' name, only honored if vendor was set to 'keycloak' as value, if no realm is specified 'master' is default
|
||||
MINIO_IDENTITY_OPENID_KEYCLOAK_ADMIN_URL (string) Specify Keycloak 'admin' REST API endpoint e.g. http://localhost:8080/auth/admin/
|
||||
MINIO_IDENTITY_OPENID_REDIRECT_URI_DYNAMIC (on|off) Enable 'Host' header based dynamic redirect URI (default: 'off')
|
||||
MINIO_IDENTITY_OPENID_CLAIM_PREFIX (string) [DEPRECATED use 'claim_name'] JWT claim namespace prefix e.g. "customer1/"
|
||||
MINIO_IDENTITY_OPENID_REDIRECT_URI (string) [DEPRECATED use env 'MINIO_BROWSER_REDIRECT_URL'] Configure custom redirect_uri for OpenID login flow callback
|
||||
MINIO_IDENTITY_OPENID_COMMENT (sentence) optionally add a comment to this setting
|
||||
```
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ summary=Maximum server performance for MinIO
|
||||
|
||||
[vm]
|
||||
transparent_hugepage=madvise
|
||||
transparent_hugepage.defrag=defer+madvise
|
||||
transparent_hugepage.khugepaged.max_ptes_none=0
|
||||
|
||||
[sysfs]
|
||||
/sys/kernel/mm/transparent_hugepage/defrag=defer+madvise
|
||||
/sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none=0
|
||||
|
||||
[cpu]
|
||||
force_latency=1
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/minio/minio
|
||||
|
||||
go 1.21
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
cloud.google.com/go/storage v1.42.0
|
||||
@@ -40,22 +40,22 @@ require (
|
||||
github.com/klauspost/filepathx v1.1.1
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/klauspost/readahead v1.4.0
|
||||
github.com/klauspost/reedsolomon v1.12.1
|
||||
github.com/klauspost/reedsolomon v1.12.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||
github.com/miekg/dns v1.1.61
|
||||
github.com/minio/cli v1.24.2
|
||||
github.com/minio/console v1.6.1
|
||||
github.com/minio/console v1.6.3
|
||||
github.com/minio/csvparser v1.0.0
|
||||
github.com/minio/dnscache v0.1.1
|
||||
github.com/minio/dperf v0.5.3
|
||||
github.com/minio/highwayhash v1.0.2
|
||||
github.com/minio/highwayhash v1.0.3
|
||||
github.com/minio/kms-go/kes v0.3.0
|
||||
github.com/minio/kms-go/kms v0.4.0
|
||||
github.com/minio/madmin-go/v3 v3.0.58
|
||||
github.com/minio/minio-go/v7 v7.0.73
|
||||
github.com/minio/mux v1.9.0
|
||||
github.com/minio/pkg/v3 v3.0.3
|
||||
github.com/minio/pkg/v3 v3.0.7
|
||||
github.com/minio/selfupdate v0.6.0
|
||||
github.com/minio/simdjson-go v0.4.5
|
||||
github.com/minio/sio v0.4.0
|
||||
@@ -82,7 +82,6 @@ require (
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/secure-io/sio-go v0.3.1
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/tidwall/gjson v1.17.1
|
||||
github.com/tinylib/msgp v1.1.9
|
||||
github.com/valyala/bytebufferpool v1.0.0
|
||||
github.com/xdg/scram v1.0.5
|
||||
@@ -219,7 +218,7 @@ require (
|
||||
github.com/muesli/termenv v0.15.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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/nats-streaming-server v0.24.6 // indirect
|
||||
github.com/nats-io/nkeys v0.4.7 // indirect
|
||||
github.com/nats-io/nuid v1.0.1 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
@@ -233,6 +232,7 @@ require (
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/safchain/ethtool v0.4.1 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/tidwall/gjson v1.17.1 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
|
||||
@@ -325,8 +325,8 @@ github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/raft v1.3.6 h1:v5xW5KzByoerQlN/o31VJrFNiozgzGyDoMgDJgXpsto=
|
||||
github.com/hashicorp/raft v1.3.6/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM=
|
||||
github.com/hashicorp/raft v1.3.9 h1:9yuo1aR0bFTr1cw7pj3S2Bk6MhJCsnr2NAxvIBrP2x4=
|
||||
github.com/hashicorp/raft v1.3.9/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -373,8 +373,8 @@ github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/readahead v1.4.0 h1:w4hQ3BpdLjBnRQkZyNi+nwdHU7eGP9buTexWK9lU7gY=
|
||||
github.com/klauspost/readahead v1.4.0/go.mod h1:7bolpMKhT5LKskLwYXGSDOyA2TYtMFgdgV0Y8gy7QhA=
|
||||
github.com/klauspost/reedsolomon v1.12.1 h1:NhWgum1efX1x58daOBGCFWcxtEhOhXKKl1HAPQUp03Q=
|
||||
github.com/klauspost/reedsolomon v1.12.1/go.mod h1:nEi5Kjb6QqtbofI6s+cbG/j1da11c96IBYBSnVGtuBs=
|
||||
github.com/klauspost/reedsolomon v1.12.2 h1:TC0hlL/tTRxiMNnqHCzKsY11E0fIIKGCoZ2vQoPKIEM=
|
||||
github.com/klauspost/reedsolomon v1.12.2/go.mod h1:nEi5Kjb6QqtbofI6s+cbG/j1da11c96IBYBSnVGtuBs=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
@@ -442,8 +442,8 @@ github.com/minio/cli v1.24.2 h1:J+fCUh9mhPLjN3Lj/YhklXvxj8mnyE/D6FpFduXJ2jg=
|
||||
github.com/minio/cli v1.24.2/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/colorjson v1.0.8 h1:AS6gEQ1dTRYHmC4xuoodPDRILHP/9Wz5wYUGDQfPLpg=
|
||||
github.com/minio/colorjson v1.0.8/go.mod h1:wrs39G/4kqNlGjwqHvPlAnXuc2tlPszo6JKdSBCLN8w=
|
||||
github.com/minio/console v1.6.1 h1:/rlXITBdZeDcX33PCjhEdA2vufeMMFMufj1XVukbu+0=
|
||||
github.com/minio/console v1.6.1/go.mod h1:XJ3HKHmigs1MgjaNjUwpyuOAJnwqlSMB+QnZCZ+BROY=
|
||||
github.com/minio/console v1.6.3 h1:XGI/Oyq3J2vs+a1cobE87m4L059jr3q1Scej7hrEcbM=
|
||||
github.com/minio/console v1.6.3/go.mod h1:yFhhM3Y3uT4N1WtphcYr3QAd7WYLU8CEuTcIiDpksWs=
|
||||
github.com/minio/csvparser v1.0.0 h1:xJEHcYK8ZAjeW4hNV9Zu30u+/2o4UyPnYgyjWp8b7ZU=
|
||||
github.com/minio/csvparser v1.0.0/go.mod h1:lKXskSLzPgC5WQyzP7maKH7Sl1cqvANXo9YCto8zbtM=
|
||||
github.com/minio/dnscache v0.1.1 h1:AMYLqomzskpORiUA1ciN9k7bZT1oB3YZN4cEIi88W5o=
|
||||
@@ -452,8 +452,9 @@ github.com/minio/dperf v0.5.3 h1:D58ZrMfxrRw83EvAhr4FggvRT0DwWXsWrvsM8Xne+EM=
|
||||
github.com/minio/dperf v0.5.3/go.mod h1:WrI7asRe/kv5zmnZ4XwHY74PV8OyUN+efeKINRgk5UI=
|
||||
github.com/minio/filepath v1.0.0 h1:fvkJu1+6X+ECRA6G3+JJETj4QeAYO9sV43I79H8ubDY=
|
||||
github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEXx9T/Bw=
|
||||
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
|
||||
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
|
||||
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
|
||||
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
|
||||
github.com/minio/kms-go/kes v0.3.0 h1:SU8VGVM/Hk9w1OiSby3OatkcojooUqIdDHl6dtM6NkY=
|
||||
github.com/minio/kms-go/kes v0.3.0/go.mod h1:w6DeVT878qEOU3nUrYVy1WOT5H1Ig9hbDIh698NYJKY=
|
||||
github.com/minio/kms-go/kms v0.4.0 h1:cLPZceEp+05xHotVBaeFJrgL7JcXM4lBy6PU0idkE7I=
|
||||
@@ -471,8 +472,8 @@ github.com/minio/mux v1.9.0 h1:dWafQFyEfGhJvK6AwLOt83bIG5bxKxKJnKMCi0XAaoA=
|
||||
github.com/minio/mux v1.9.0/go.mod h1:1pAare17ZRL5GpmNL+9YmqHoWnLmMZF9C/ioUCfy0BQ=
|
||||
github.com/minio/pkg/v2 v2.0.19 h1:r187/k/oVH9H0DDwvLY5WipkJaZ4CLd4KI3KgIUExR0=
|
||||
github.com/minio/pkg/v2 v2.0.19/go.mod h1:luK9LAhQlAPzSuF6F326XSCKjMc1G3Tbh+a9JYwqh8M=
|
||||
github.com/minio/pkg/v3 v3.0.3 h1:PUJVi5a6Hdn5mIhffC24koFMQwucvTyBHsIOjsisI+U=
|
||||
github.com/minio/pkg/v3 v3.0.3/go.mod h1:53gkSUVHcfYoskOs5YAJ3D99nsd2SKru90rdE9whlXU=
|
||||
github.com/minio/pkg/v3 v3.0.7 h1:1I2CbFKO+brioB6Pbnw0jLlFxo+YPy6hCTTXTSitgI8=
|
||||
github.com/minio/pkg/v3 v3.0.7/go.mod h1:njlf539caYrgXqn/CXewqvkqBIMDTQo9oBBEL34LzY0=
|
||||
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
|
||||
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
@@ -511,16 +512,17 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
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.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
|
||||
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.8.2/go.mod h1:vIdpKz3OG+DCg4q/xVPdXHoztEyKDWRtykQ4N7hd7C4=
|
||||
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-streaming-server v0.24.6 h1:iIZXuPSznnYkiy0P3L0AP9zEN9Etp+tITbbX1KKeq4Q=
|
||||
github.com/nats-io/nats-streaming-server v0.24.6/go.mod h1:tdKXltY3XLeBJ21sHiZiaPl+j8sK3vcCKBWVyxeQs10=
|
||||
github.com/nats-io/nats.go v1.13.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nats.go v1.13.1-0.20220308171302-2f2f6968e98d/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nats.go v1.14.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
|
||||
github.com/nats-io/nats.go v1.22.1/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA=
|
||||
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
|
||||
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
|
||||
@@ -737,8 +739,7 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP
|
||||
golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
@@ -830,9 +831,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220307203707-22a9840ba4d7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
@@ -74,12 +74,16 @@ func (r *MonitoredReader) Read(buf []byte) (n int, err error) {
|
||||
need = int(math.Min(float64(b), float64(need)))
|
||||
tokens = need
|
||||
}
|
||||
|
||||
// reduce tokens requested according to availability
|
||||
av := int(r.throttle.Tokens())
|
||||
if av < tokens && av > 0 {
|
||||
tokens = av
|
||||
need = int(math.Min(float64(tokens), float64(need)))
|
||||
}
|
||||
err = r.throttle.WaitN(r.ctx, tokens)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
n, err = r.r.Read(buf[:need])
|
||||
if err != nil {
|
||||
r.lastErr = err
|
||||
|
||||
+29
-11
@@ -40,6 +40,7 @@ const (
|
||||
apiListQuorum = "list_quorum"
|
||||
apiReplicationPriority = "replication_priority"
|
||||
apiReplicationMaxWorkers = "replication_max_workers"
|
||||
apiReplicationMaxLWorkers = "replication_max_lrg_workers"
|
||||
|
||||
apiTransitionWorkers = "transition_workers"
|
||||
apiStaleUploadsCleanupInterval = "stale_uploads_cleanup_interval"
|
||||
@@ -52,16 +53,18 @@ const (
|
||||
apiSyncEvents = "sync_events"
|
||||
apiObjectMaxVersions = "object_max_versions"
|
||||
|
||||
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
|
||||
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
|
||||
EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE"
|
||||
EnvAPICorsAllowOrigin = "MINIO_API_CORS_ALLOW_ORIGIN"
|
||||
EnvAPIRemoteTransportDeadline = "MINIO_API_REMOTE_TRANSPORT_DEADLINE"
|
||||
EnvAPITransitionWorkers = "MINIO_API_TRANSITION_WORKERS"
|
||||
EnvAPIListQuorum = "MINIO_API_LIST_QUORUM"
|
||||
EnvAPISecureCiphers = "MINIO_API_SECURE_CIPHERS" // default config.EnableOn
|
||||
EnvAPIReplicationPriority = "MINIO_API_REPLICATION_PRIORITY"
|
||||
EnvAPIReplicationMaxWorkers = "MINIO_API_REPLICATION_MAX_WORKERS"
|
||||
EnvAPIRequestsMax = "MINIO_API_REQUESTS_MAX"
|
||||
EnvAPIRequestsDeadline = "MINIO_API_REQUESTS_DEADLINE"
|
||||
EnvAPIClusterDeadline = "MINIO_API_CLUSTER_DEADLINE"
|
||||
EnvAPICorsAllowOrigin = "MINIO_API_CORS_ALLOW_ORIGIN"
|
||||
EnvAPIRemoteTransportDeadline = "MINIO_API_REMOTE_TRANSPORT_DEADLINE"
|
||||
EnvAPITransitionWorkers = "MINIO_API_TRANSITION_WORKERS"
|
||||
EnvAPIListQuorum = "MINIO_API_LIST_QUORUM"
|
||||
EnvAPISecureCiphers = "MINIO_API_SECURE_CIPHERS" // default config.EnableOn
|
||||
EnvAPIReplicationPriority = "MINIO_API_REPLICATION_PRIORITY"
|
||||
EnvAPIReplicationMaxWorkers = "MINIO_API_REPLICATION_MAX_WORKERS"
|
||||
EnvAPIReplicationMaxLWorkers = "MINIO_API_REPLICATION_MAX_LRG_WORKERS"
|
||||
|
||||
EnvAPIStaleUploadsCleanupInterval = "MINIO_API_STALE_UPLOADS_CLEANUP_INTERVAL"
|
||||
EnvAPIStaleUploadsExpiry = "MINIO_API_STALE_UPLOADS_EXPIRY"
|
||||
EnvAPIDeleteCleanupInterval = "MINIO_API_DELETE_CLEANUP_INTERVAL"
|
||||
@@ -117,6 +120,10 @@ var (
|
||||
Key: apiReplicationMaxWorkers,
|
||||
Value: "500",
|
||||
},
|
||||
config.KV{
|
||||
Key: apiReplicationMaxLWorkers,
|
||||
Value: "10",
|
||||
},
|
||||
config.KV{
|
||||
Key: apiTransitionWorkers,
|
||||
Value: "100",
|
||||
@@ -171,6 +178,7 @@ type Config struct {
|
||||
ListQuorum string `json:"list_quorum"`
|
||||
ReplicationPriority string `json:"replication_priority"`
|
||||
ReplicationMaxWorkers int `json:"replication_max_workers"`
|
||||
ReplicationMaxLWorkers int `json:"replication_max_lrg_workers"`
|
||||
TransitionWorkers int `json:"transition_workers"`
|
||||
StaleUploadsCleanupInterval time.Duration `json:"stale_uploads_cleanup_interval"`
|
||||
StaleUploadsExpiry time.Duration `json:"stale_uploads_expiry"`
|
||||
@@ -280,11 +288,21 @@ func LookupConfig(kvs config.KVS) (cfg Config, err error) {
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
if replicationMaxWorkers <= 0 || replicationMaxWorkers > 500 {
|
||||
return cfg, config.ErrInvalidReplicationWorkersValue(nil).Msg("Number of replication workers should be between 1 and 500")
|
||||
}
|
||||
cfg.ReplicationMaxWorkers = replicationMaxWorkers
|
||||
|
||||
replicationMaxLWorkers, err := strconv.Atoi(env.Get(EnvAPIReplicationMaxLWorkers, kvs.GetWithDefault(apiReplicationMaxLWorkers, DefaultKVS)))
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if replicationMaxLWorkers <= 0 || replicationMaxLWorkers > 10 {
|
||||
return cfg, config.ErrInvalidReplicationWorkersValue(nil).Msg("Number of replication workers for transfers >=128MiB should be between 1 and 10 per node")
|
||||
}
|
||||
|
||||
cfg.ReplicationMaxLWorkers = replicationMaxLWorkers
|
||||
|
||||
transitionWorkers, err := strconv.Atoi(env.Get(EnvAPITransitionWorkers, kvs.GetWithDefault(apiTransitionWorkers, DefaultKVS)))
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
|
||||
@@ -73,6 +73,12 @@ var (
|
||||
`Access key length should be at least 3, and secret key length at least 8 characters`,
|
||||
)
|
||||
|
||||
ErrInvalidRootUserCredentials = newErrFn(
|
||||
"Invalid credentials",
|
||||
"Please provide correct credentials",
|
||||
EnvRootUser+` length should be at least 3, and `+EnvRootPassword+` length at least 8 characters`,
|
||||
)
|
||||
|
||||
ErrMissingEnvCredentialRootUser = newErrFn(
|
||||
"Missing credential environment variable, \""+EnvRootUser+"\"",
|
||||
"Environment variable \""+EnvRootUser+"\" is missing",
|
||||
|
||||
@@ -179,6 +179,54 @@ func (l *Config) GetValidatedDNUnderBaseDN(conn *ldap.Conn, dn string, baseDNLis
|
||||
return searchRes, false, nil
|
||||
}
|
||||
|
||||
// GetValidatedDNWithGroups - Gets validated DN from given DN or short username
|
||||
// and returns the DN and the groups the user is a member of.
|
||||
//
|
||||
// If username is required in group search but a DN is passed, no groups are
|
||||
// returned.
|
||||
func (l *Config) GetValidatedDNWithGroups(username string) (*xldap.DNSearchResult, []string, error) {
|
||||
conn, err := l.LDAP.Connect()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Bind to the lookup user account
|
||||
if err = l.LDAP.LookupBind(conn); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var lookupRes *xldap.DNSearchResult
|
||||
shortUsername := ""
|
||||
// Check if the passed in username is a valid DN.
|
||||
if !l.ParsesAsDN(username) {
|
||||
// We consider it as a login username and attempt to check it exists in
|
||||
// the directory.
|
||||
lookupRes, err = l.LDAP.LookupUsername(conn, username)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "User DN not found for") {
|
||||
return nil, nil, nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
shortUsername = username
|
||||
} else {
|
||||
// Since the username parses as a valid DN, check that it exists and is
|
||||
// under a configured base DN in the LDAP directory.
|
||||
var isUnderBaseDN bool
|
||||
lookupRes, isUnderBaseDN, err = l.GetValidatedUserDN(conn, username)
|
||||
if err == nil && !isUnderBaseDN {
|
||||
return nil, nil, fmt.Errorf("Unable to find user DN: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
groups, err := l.LDAP.SearchForUserGroups(conn, shortUsername, lookupRes.ActualDN)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return lookupRes, groups, nil
|
||||
}
|
||||
|
||||
// Bind - binds to ldap, searches LDAP and returns the distinguished name of the
|
||||
// user and the list of groups.
|
||||
func (l *Config) Bind(username, password string) (*xldap.DNSearchResult, []string, error) {
|
||||
@@ -354,3 +402,21 @@ func (l *Config) LookupGroupMemberships(userDistNames []string, userDNToUsername
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// QuickNormalizeDN - normalizes the given DN without checking if it is valid or
|
||||
// exists in the LDAP directory. Returns input if error
|
||||
func (l Config) QuickNormalizeDN(dn string) string {
|
||||
if normDN, err := xldap.NormalizeDN(dn); err == nil {
|
||||
return normDN
|
||||
}
|
||||
return dn
|
||||
}
|
||||
|
||||
// DecodeDN - denormalizes the given DN by unescaping any escaped characters.
|
||||
// Returns input if error
|
||||
func (l Config) DecodeDN(dn string) string {
|
||||
if decodedDN, err := xldap.DecodeDN(dn); err == nil {
|
||||
return decodedDN
|
||||
}
|
||||
return dn
|
||||
}
|
||||
|
||||
@@ -101,12 +101,14 @@ var (
|
||||
Value: "",
|
||||
},
|
||||
config.KV{
|
||||
Key: ClaimPrefix,
|
||||
Value: "",
|
||||
Key: ClaimPrefix,
|
||||
Value: "",
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: RedirectURI,
|
||||
Value: "",
|
||||
Key: RedirectURI,
|
||||
Value: "",
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: RedirectURIDynamic,
|
||||
|
||||
@@ -138,7 +138,7 @@ func (target *WebhookTarget) isActive() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Stat - returns lamdba webhook target statistics such as
|
||||
// Stat - returns lambda webhook target statistics such as
|
||||
// current calls in progress, successfully completed functions
|
||||
// failed functions.
|
||||
func (target *WebhookTarget) Stat() event.TargetStat {
|
||||
|
||||
@@ -861,20 +861,24 @@ var (
|
||||
Value: config.EnableOff,
|
||||
},
|
||||
config.KV{
|
||||
Key: target.NATSStreaming,
|
||||
Value: config.EnableOff,
|
||||
Key: target.NATSStreaming,
|
||||
Value: config.EnableOff,
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: target.NATSStreamingAsync,
|
||||
Value: config.EnableOff,
|
||||
Key: target.NATSStreamingAsync,
|
||||
Value: config.EnableOff,
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: target.NATSStreamingMaxPubAcksInFlight,
|
||||
Value: "0",
|
||||
Key: target.NATSStreamingMaxPubAcksInFlight,
|
||||
Value: "0",
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: target.NATSStreamingClusterID,
|
||||
Value: "",
|
||||
Key: target.NATSStreamingClusterID,
|
||||
Value: "",
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: target.NATSQueueDir,
|
||||
|
||||
@@ -42,12 +42,14 @@ const (
|
||||
var (
|
||||
DefaultKVS = config.KVS{
|
||||
config.KV{
|
||||
Key: URL,
|
||||
Value: "",
|
||||
Key: URL,
|
||||
Value: "",
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
config.KV{
|
||||
Key: AuthToken,
|
||||
Value: "",
|
||||
Key: AuthToken,
|
||||
Value: "",
|
||||
HiddenIfEmpty: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -27,23 +27,23 @@ var (
|
||||
// HelpSubnet - provides help for subnet api key config
|
||||
HelpSubnet = config.HelpKVS{
|
||||
config.HelpKV{
|
||||
Key: config.License, // Deprecated Dec 2021
|
||||
Key: config.License,
|
||||
Type: "string",
|
||||
Description: "[DEPRECATED use api_key] Subnet license token for the cluster" + defaultHelpPostfix(config.License),
|
||||
Description: "Enterprise license for the cluster" + defaultHelpPostfix(config.License),
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.APIKey,
|
||||
Type: "string",
|
||||
Description: "Subnet api key for the cluster" + defaultHelpPostfix(config.APIKey),
|
||||
Description: "Enterprise license API key for the cluster" + defaultHelpPostfix(config.APIKey),
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
config.HelpKV{
|
||||
Key: config.Proxy,
|
||||
Type: "string",
|
||||
Description: "HTTP(S) proxy URL to use for connecting to SUBNET" + defaultHelpPostfix(config.Proxy),
|
||||
Description: "HTTP(s) proxy URL to use for connecting to SUBNET" + defaultHelpPostfix(config.Proxy),
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
|
||||
@@ -49,8 +49,8 @@ var generateKeyTests = []struct {
|
||||
}
|
||||
|
||||
func TestGenerateKey(t *testing.T) {
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
defer func(l bool) { logger.DisableLog = l }(logger.DisableLog)
|
||||
logger.DisableLog = true
|
||||
|
||||
for i, test := range generateKeyTests {
|
||||
i, test := i, test
|
||||
@@ -75,8 +75,8 @@ var generateIVTests = []struct {
|
||||
}
|
||||
|
||||
func TestGenerateIV(t *testing.T) {
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
defer func(l bool) { logger.DisableLog = l }(logger.DisableLog)
|
||||
logger.DisableLog = true
|
||||
|
||||
for i, test := range generateIVTests {
|
||||
i, test := i, test
|
||||
|
||||
@@ -313,8 +313,8 @@ var s3CreateMetadataTests = []struct {
|
||||
}
|
||||
|
||||
func TestS3CreateMetadata(t *testing.T) {
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
defer func(l bool) { logger.DisableLog = l }(logger.DisableLog)
|
||||
logger.DisableLog = true
|
||||
for i, test := range s3CreateMetadataTests {
|
||||
metadata := S3.CreateMetadata(nil, test.KeyID, test.SealedDataKey, test.SealedKey)
|
||||
keyID, kmsKey, sealedKey, err := S3.ParseMetadata(metadata)
|
||||
@@ -358,8 +358,8 @@ var ssecCreateMetadataTests = []struct {
|
||||
}
|
||||
|
||||
func TestSSECCreateMetadata(t *testing.T) {
|
||||
defer func(l bool) { logger.DisableErrorLog = l }(logger.DisableErrorLog)
|
||||
logger.DisableErrorLog = true
|
||||
defer func(l bool) { logger.DisableLog = l }(logger.DisableLog)
|
||||
logger.DisableLog = true
|
||||
for i, test := range ssecCreateMetadataTests {
|
||||
metadata := SSEC.CreateMetadata(nil, test.SealedKey)
|
||||
sealedKey, err := SSEC.ParseMetadata(metadata)
|
||||
|
||||
+13
-42
@@ -20,7 +20,6 @@ package grid
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -28,7 +27,6 @@ import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -100,9 +98,9 @@ type Connection struct {
|
||||
// Client or serverside.
|
||||
side ws.State
|
||||
|
||||
// Transport for outgoing connections.
|
||||
dialer ContextDialer
|
||||
header http.Header
|
||||
// Dialer for outgoing connections.
|
||||
dial ConnDialer
|
||||
authFn AuthFn
|
||||
|
||||
handleMsgWg sync.WaitGroup
|
||||
|
||||
@@ -112,10 +110,8 @@ type Connection struct {
|
||||
handlers *handlers
|
||||
|
||||
remote *RemoteClient
|
||||
auth AuthFn
|
||||
clientPingInterval time.Duration
|
||||
connPingInterval time.Duration
|
||||
tlsConfig *tls.Config
|
||||
blockConnect chan struct{}
|
||||
|
||||
incomingBytes func(n int64) // Record incoming bytes.
|
||||
@@ -205,13 +201,12 @@ type connectionParams struct {
|
||||
ctx context.Context
|
||||
id uuid.UUID
|
||||
local, remote string
|
||||
dial ContextDialer
|
||||
handlers *handlers
|
||||
auth AuthFn
|
||||
tlsConfig *tls.Config
|
||||
incomingBytes func(n int64) // Record incoming bytes.
|
||||
outgoingBytes func(n int64) // Record outgoing bytes.
|
||||
publisher *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]
|
||||
dialer ConnDialer
|
||||
authFn AuthFn
|
||||
|
||||
blockConnect chan struct{}
|
||||
}
|
||||
@@ -227,16 +222,14 @@ func newConnection(o connectionParams) *Connection {
|
||||
outgoing: xsync.NewMapOfPresized[uint64, *muxClient](1000),
|
||||
inStream: xsync.NewMapOfPresized[uint64, *muxServer](1000),
|
||||
outQueue: make(chan []byte, defaultOutQueue),
|
||||
dialer: o.dial,
|
||||
side: ws.StateServerSide,
|
||||
connChange: &sync.Cond{L: &sync.Mutex{}},
|
||||
handlers: o.handlers,
|
||||
auth: o.auth,
|
||||
header: make(http.Header, 1),
|
||||
remote: &RemoteClient{Name: o.remote},
|
||||
clientPingInterval: clientPingInterval,
|
||||
connPingInterval: connPingInterval,
|
||||
tlsConfig: o.tlsConfig,
|
||||
dial: o.dialer,
|
||||
authFn: o.authFn,
|
||||
}
|
||||
if debugPrint {
|
||||
// Random Mux ID
|
||||
@@ -648,41 +641,17 @@ func (c *Connection) connect() {
|
||||
if c.State() == StateShutdown {
|
||||
return
|
||||
}
|
||||
toDial := strings.Replace(c.Remote, "http://", "ws://", 1)
|
||||
toDial = strings.Replace(toDial, "https://", "wss://", 1)
|
||||
toDial += RoutePath
|
||||
|
||||
dialer := ws.DefaultDialer
|
||||
dialer.ReadBufferSize = readBufferSize
|
||||
dialer.WriteBufferSize = writeBufferSize
|
||||
dialer.Timeout = defaultDialTimeout
|
||||
if c.dialer != nil {
|
||||
dialer.NetDial = c.dialer.DialContext
|
||||
}
|
||||
if c.header == nil {
|
||||
c.header = make(http.Header, 2)
|
||||
}
|
||||
c.header.Set("Authorization", "Bearer "+c.auth(""))
|
||||
c.header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
||||
|
||||
if len(c.header) > 0 {
|
||||
dialer.Header = ws.HandshakeHeaderHTTP(c.header)
|
||||
}
|
||||
dialer.TLSConfig = c.tlsConfig
|
||||
dialStarted := time.Now()
|
||||
if debugPrint {
|
||||
fmt.Println(c.Local, "Connecting to ", toDial)
|
||||
}
|
||||
conn, br, _, err := dialer.Dial(c.ctx, toDial)
|
||||
if br != nil {
|
||||
ws.PutReader(br)
|
||||
fmt.Println(c.Local, "Connecting to ", c.Remote)
|
||||
}
|
||||
conn, err := c.dial(c.ctx, c.Remote)
|
||||
c.connMu.Lock()
|
||||
c.debugOutConn = conn
|
||||
c.connMu.Unlock()
|
||||
retry := func(err error) {
|
||||
if debugPrint {
|
||||
fmt.Printf("%v Connecting to %v: %v. Retrying.\n", c.Local, toDial, err)
|
||||
fmt.Printf("%v Connecting to %v: %v. Retrying.\n", c.Local, c.Remote, err)
|
||||
}
|
||||
sleep := defaultDialTimeout + time.Duration(rng.Int63n(int64(defaultDialTimeout)))
|
||||
next := dialStarted.Add(sleep / 2)
|
||||
@@ -696,7 +665,7 @@ func (c *Connection) connect() {
|
||||
}
|
||||
if gotState != StateConnecting {
|
||||
// Don't print error on first attempt, and after that only once per hour.
|
||||
gridLogOnceIf(c.ctx, fmt.Errorf("grid: %s re-connecting to %s: %w (%T) Sleeping %v (%v)", c.Local, toDial, err, err, sleep, gotState), toDial)
|
||||
gridLogOnceIf(c.ctx, fmt.Errorf("grid: %s re-connecting to %s: %w (%T) Sleeping %v (%v)", c.Local, c.Remote, err, err, sleep, gotState), c.Remote)
|
||||
}
|
||||
c.updateState(StateConnectionError)
|
||||
time.Sleep(sleep)
|
||||
@@ -712,7 +681,9 @@ func (c *Connection) connect() {
|
||||
req := connectReq{
|
||||
Host: c.Local,
|
||||
ID: c.id,
|
||||
Time: time.Now(),
|
||||
}
|
||||
req.addToken(c.authFn)
|
||||
err = c.sendMsg(conn, m, &req)
|
||||
if err != nil {
|
||||
retry(err)
|
||||
|
||||
@@ -52,11 +52,13 @@ func TestDisconnect(t *testing.T) {
|
||||
localHost := hosts[0]
|
||||
remoteHost := hosts[1]
|
||||
local, err := NewManager(context.Background(), ManagerOptions{
|
||||
Dialer: dialer.DialContext,
|
||||
Dialer: ConnectWS(dialer.DialContext,
|
||||
dummyNewToken,
|
||||
nil),
|
||||
Local: localHost,
|
||||
Hosts: hosts,
|
||||
AddAuth: func(aud string) string { return aud },
|
||||
AuthRequest: dummyRequestValidate,
|
||||
AuthFn: dummyNewToken,
|
||||
AuthToken: dummyTokenValidate,
|
||||
BlockConnect: connReady,
|
||||
})
|
||||
errFatal(err)
|
||||
@@ -74,17 +76,19 @@ func TestDisconnect(t *testing.T) {
|
||||
}))
|
||||
|
||||
remote, err := NewManager(context.Background(), ManagerOptions{
|
||||
Dialer: dialer.DialContext,
|
||||
Dialer: ConnectWS(dialer.DialContext,
|
||||
dummyNewToken,
|
||||
nil),
|
||||
Local: remoteHost,
|
||||
Hosts: hosts,
|
||||
AddAuth: func(aud string) string { return aud },
|
||||
AuthRequest: dummyRequestValidate,
|
||||
AuthFn: dummyNewToken,
|
||||
AuthToken: dummyTokenValidate,
|
||||
BlockConnect: connReady,
|
||||
})
|
||||
errFatal(err)
|
||||
|
||||
localServer := startServer(t, listeners[0], wrapServer(local.Handler()))
|
||||
remoteServer := startServer(t, listeners[1], wrapServer(remote.Handler()))
|
||||
localServer := startServer(t, listeners[0], wrapServer(local.Handler(dummyRequestValidate)))
|
||||
remoteServer := startServer(t, listeners[1], wrapServer(remote.Handler(dummyRequestValidate)))
|
||||
close(connReady)
|
||||
|
||||
defer func() {
|
||||
@@ -165,10 +169,6 @@ func TestDisconnect(t *testing.T) {
|
||||
<-gotCall
|
||||
}
|
||||
|
||||
func dummyRequestValidate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestShouldConnect(t *testing.T) {
|
||||
var c Connection
|
||||
var cReverse Connection
|
||||
|
||||
+23
-8
@@ -82,20 +82,20 @@ func SetupTestGrid(n int) (*TestGrid, error) {
|
||||
res.cancel = cancel
|
||||
for i, host := range hosts {
|
||||
manager, err := NewManager(ctx, ManagerOptions{
|
||||
Dialer: dialer.DialContext,
|
||||
Local: host,
|
||||
Hosts: hosts,
|
||||
AuthRequest: func(r *http.Request) error {
|
||||
return nil
|
||||
},
|
||||
AddAuth: func(aud string) string { return aud },
|
||||
Dialer: ConnectWS(dialer.DialContext,
|
||||
dummyNewToken,
|
||||
nil),
|
||||
Local: host,
|
||||
Hosts: hosts,
|
||||
AuthFn: dummyNewToken,
|
||||
AuthToken: dummyTokenValidate,
|
||||
BlockConnect: ready,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := mux.NewRouter()
|
||||
m.Handle(RoutePath, manager.Handler())
|
||||
m.Handle(RoutePath, manager.Handler(dummyRequestValidate))
|
||||
res.Managers = append(res.Managers, manager)
|
||||
res.Servers = append(res.Servers, startHTTPServer(listeners[i], m))
|
||||
res.Listeners = append(res.Listeners, listeners[i])
|
||||
@@ -164,3 +164,18 @@ func startHTTPServer(listener net.Listener, handler http.Handler) (server *httpt
|
||||
server.Start()
|
||||
return server
|
||||
}
|
||||
|
||||
func dummyRequestValidate(r *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func dummyTokenValidate(token, audience string) error {
|
||||
if token == audience {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("invalid token. want %s, got %s", audience, token)
|
||||
}
|
||||
|
||||
func dummyNewToken(audience string) string {
|
||||
return audience
|
||||
}
|
||||
|
||||
@@ -20,12 +20,17 @@ package grid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
)
|
||||
|
||||
@@ -179,3 +184,45 @@ func bytesOrLength(b []byte) string {
|
||||
}
|
||||
return fmt.Sprint(b)
|
||||
}
|
||||
|
||||
// ConnDialer is a function that dials a connection to the given address.
|
||||
// There should be no retries in this function,
|
||||
// and should have a timeout of something like 2 seconds.
|
||||
// The returned net.Conn should also have quick disconnect on errors.
|
||||
// The net.Conn must support all features as described by the net.Conn interface.
|
||||
type ConnDialer func(ctx context.Context, address string) (net.Conn, error)
|
||||
|
||||
// ConnectWS returns a function that dials a websocket connection to the given address.
|
||||
// Route and auth are added to the connection.
|
||||
func ConnectWS(dial ContextDialer, auth AuthFn, tls *tls.Config) func(ctx context.Context, remote string) (net.Conn, error) {
|
||||
return func(ctx context.Context, remote string) (net.Conn, error) {
|
||||
toDial := strings.Replace(remote, "http://", "ws://", 1)
|
||||
toDial = strings.Replace(toDial, "https://", "wss://", 1)
|
||||
toDial += RoutePath
|
||||
|
||||
dialer := ws.DefaultDialer
|
||||
dialer.ReadBufferSize = readBufferSize
|
||||
dialer.WriteBufferSize = writeBufferSize
|
||||
dialer.Timeout = defaultDialTimeout
|
||||
if dial != nil {
|
||||
dialer.NetDial = dial
|
||||
}
|
||||
header := make(http.Header, 2)
|
||||
header.Set("Authorization", "Bearer "+auth(""))
|
||||
header.Set("X-Minio-Time", time.Now().UTC().Format(time.RFC3339))
|
||||
|
||||
if len(header) > 0 {
|
||||
dialer.Header = ws.HandshakeHeaderHTTP(header)
|
||||
}
|
||||
dialer.TLSConfig = tls
|
||||
|
||||
conn, br, _, err := dialer.Dial(ctx, toDial)
|
||||
if br != nil {
|
||||
ws.PutReader(br)
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateTokenFn must validate the token and return an error if it is invalid.
|
||||
type ValidateTokenFn func(token, audience string) error
|
||||
|
||||
+117
-89
@@ -19,13 +19,14 @@ package grid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/ws"
|
||||
"github.com/gobwas/ws/wsutil"
|
||||
@@ -62,40 +63,48 @@ type Manager struct {
|
||||
// local host name.
|
||||
local string
|
||||
|
||||
// Validate incoming requests.
|
||||
authRequest func(r *http.Request) error
|
||||
// authToken is a function that will validate a token.
|
||||
authToken ValidateTokenFn
|
||||
}
|
||||
|
||||
// ManagerOptions are options for creating a new grid manager.
|
||||
type ManagerOptions struct {
|
||||
Dialer ContextDialer // Outgoing dialer.
|
||||
Local string // Local host name.
|
||||
Hosts []string // All hosts, including local in the grid.
|
||||
AddAuth AuthFn // Add authentication to the given audience.
|
||||
AuthRequest func(r *http.Request) error // Validate incoming requests.
|
||||
TLSConfig *tls.Config // TLS to apply to the connections.
|
||||
Incoming func(n int64) // Record incoming bytes.
|
||||
Outgoing func(n int64) // Record outgoing bytes.
|
||||
BlockConnect chan struct{} // If set, incoming and outgoing connections will be blocked until closed.
|
||||
Local string // Local host name.
|
||||
Hosts []string // All hosts, including local in the grid.
|
||||
Incoming func(n int64) // Record incoming bytes.
|
||||
Outgoing func(n int64) // Record outgoing bytes.
|
||||
BlockConnect chan struct{} // If set, incoming and outgoing connections will be blocked until closed.
|
||||
TraceTo *pubsub.PubSub[madmin.TraceInfo, madmin.TraceType]
|
||||
Dialer ConnDialer
|
||||
// Sign a token for the given audience.
|
||||
AuthFn AuthFn
|
||||
// Callbacks to validate incoming connections.
|
||||
AuthToken ValidateTokenFn
|
||||
}
|
||||
|
||||
// NewManager creates a new grid manager
|
||||
func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
|
||||
found := false
|
||||
if o.AuthRequest == nil {
|
||||
return nil, fmt.Errorf("grid: AuthRequest must be set")
|
||||
if o.AuthToken == nil {
|
||||
return nil, fmt.Errorf("grid: AuthToken not set")
|
||||
}
|
||||
if o.Dialer == nil {
|
||||
return nil, fmt.Errorf("grid: Dialer not set")
|
||||
}
|
||||
if o.AuthFn == nil {
|
||||
return nil, fmt.Errorf("grid: AuthFn not set")
|
||||
}
|
||||
m := &Manager{
|
||||
ID: uuid.New(),
|
||||
targets: make(map[string]*Connection, len(o.Hosts)),
|
||||
local: o.Local,
|
||||
authRequest: o.AuthRequest,
|
||||
ID: uuid.New(),
|
||||
targets: make(map[string]*Connection, len(o.Hosts)),
|
||||
local: o.Local,
|
||||
authToken: o.AuthToken,
|
||||
}
|
||||
m.handlers.init()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
for _, host := range o.Hosts {
|
||||
if host == o.Local {
|
||||
if found {
|
||||
@@ -110,14 +119,13 @@ func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
|
||||
id: m.ID,
|
||||
local: o.Local,
|
||||
remote: host,
|
||||
dial: o.Dialer,
|
||||
handlers: &m.handlers,
|
||||
auth: o.AddAuth,
|
||||
blockConnect: o.BlockConnect,
|
||||
tlsConfig: o.TLSConfig,
|
||||
publisher: o.TraceTo,
|
||||
incomingBytes: o.Incoming,
|
||||
outgoingBytes: o.Outgoing,
|
||||
dialer: o.Dialer,
|
||||
authFn: o.AuthFn,
|
||||
})
|
||||
}
|
||||
if !found {
|
||||
@@ -128,13 +136,13 @@ func NewManager(ctx context.Context, o ManagerOptions) (*Manager, error) {
|
||||
}
|
||||
|
||||
// AddToMux will add the grid manager to the given mux.
|
||||
func (m *Manager) AddToMux(router *mux.Router) {
|
||||
router.Handle(RoutePath, m.Handler())
|
||||
func (m *Manager) AddToMux(router *mux.Router, authReq func(r *http.Request) error) {
|
||||
router.Handle(RoutePath, m.Handler(authReq))
|
||||
}
|
||||
|
||||
// Handler returns a handler that can be used to serve grid requests.
|
||||
// This should be connected on RoutePath to the main server.
|
||||
func (m *Manager) Handler() http.HandlerFunc {
|
||||
func (m *Manager) Handler(authReq func(r *http.Request) error) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
if debugPrint {
|
||||
@@ -151,7 +159,7 @@ func (m *Manager) Handler() http.HandlerFunc {
|
||||
fmt.Printf("grid: Got a %s request for: %v\n", req.Method, req.URL)
|
||||
}
|
||||
ctx := req.Context()
|
||||
if err := m.authRequest(req); err != nil {
|
||||
if err := authReq(req); err != nil {
|
||||
gridLogOnceIf(ctx, fmt.Errorf("auth %s: %w", req.RemoteAddr, err), req.RemoteAddr)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
@@ -164,76 +172,96 @@ func (m *Manager) Handler() http.HandlerFunc {
|
||||
w.WriteHeader(http.StatusUpgradeRequired)
|
||||
return
|
||||
}
|
||||
// will write an OpConnectResponse message to the remote and log it once locally.
|
||||
writeErr := func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
gridLogOnceIf(ctx, err, req.RemoteAddr)
|
||||
resp := connectResp{
|
||||
ID: m.ID,
|
||||
Accepted: false,
|
||||
RejectedReason: err.Error(),
|
||||
}
|
||||
if b, err := resp.MarshalMsg(nil); err == nil {
|
||||
msg := message{
|
||||
Op: OpConnectResponse,
|
||||
Payload: b,
|
||||
}
|
||||
if b, err := msg.MarshalMsg(nil); err == nil {
|
||||
wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpBinary, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
if debugPrint {
|
||||
fmt.Printf("grid: Upgraded request: %v\n", req.URL)
|
||||
}
|
||||
|
||||
msg, _, err := wsutil.ReadClientData(conn)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("reading connect: %w", err))
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if debugPrint {
|
||||
fmt.Printf("%s handler: Got message, length %v\n", m.local, len(msg))
|
||||
}
|
||||
|
||||
var message message
|
||||
_, _, err = message.parse(msg)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing grid connect: %w", err))
|
||||
return
|
||||
}
|
||||
if message.Op != OpConnect {
|
||||
writeErr(fmt.Errorf("unexpected connect op: %v", message.Op))
|
||||
return
|
||||
}
|
||||
var cReq connectReq
|
||||
_, err = cReq.UnmarshalMsg(message.Payload)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing connectReq: %w", err))
|
||||
return
|
||||
}
|
||||
remote := m.targets[cReq.Host]
|
||||
if remote == nil {
|
||||
writeErr(fmt.Errorf("unknown incoming host: %v", cReq.Host))
|
||||
return
|
||||
}
|
||||
if debugPrint {
|
||||
fmt.Printf("handler: Got Connect Req %+v\n", cReq)
|
||||
}
|
||||
writeErr(remote.handleIncoming(ctx, conn, cReq))
|
||||
m.IncomingConn(ctx, conn)
|
||||
}
|
||||
}
|
||||
|
||||
// IncomingConn will handle an incoming connection.
|
||||
// This should be called with the incoming connection after accept.
|
||||
// Auth is handled internally, as well as disconnecting any connections from the same host.
|
||||
func (m *Manager) IncomingConn(ctx context.Context, conn net.Conn) {
|
||||
remoteAddr := conn.RemoteAddr().String()
|
||||
// will write an OpConnectResponse message to the remote and log it once locally.
|
||||
defer conn.Close()
|
||||
writeErr := func(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if errors.Is(err, io.EOF) {
|
||||
return
|
||||
}
|
||||
gridLogOnceIf(ctx, err, remoteAddr)
|
||||
resp := connectResp{
|
||||
ID: m.ID,
|
||||
Accepted: false,
|
||||
RejectedReason: err.Error(),
|
||||
}
|
||||
if b, err := resp.MarshalMsg(nil); err == nil {
|
||||
msg := message{
|
||||
Op: OpConnectResponse,
|
||||
Payload: b,
|
||||
}
|
||||
if b, err := msg.MarshalMsg(nil); err == nil {
|
||||
wsutil.WriteMessage(conn, ws.StateServerSide, ws.OpBinary, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
defer conn.Close()
|
||||
if debugPrint {
|
||||
fmt.Printf("grid: Upgraded request: %v\n", remoteAddr)
|
||||
}
|
||||
|
||||
msg, _, err := wsutil.ReadClientData(conn)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("reading connect: %w", err))
|
||||
return
|
||||
}
|
||||
if debugPrint {
|
||||
fmt.Printf("%s handler: Got message, length %v\n", m.local, len(msg))
|
||||
}
|
||||
|
||||
var message message
|
||||
_, _, err = message.parse(msg)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing grid connect: %w", err))
|
||||
return
|
||||
}
|
||||
if message.Op != OpConnect {
|
||||
writeErr(fmt.Errorf("unexpected connect op: %v", message.Op))
|
||||
return
|
||||
}
|
||||
var cReq connectReq
|
||||
_, err = cReq.UnmarshalMsg(message.Payload)
|
||||
if err != nil {
|
||||
writeErr(fmt.Errorf("error parsing connectReq: %w", err))
|
||||
return
|
||||
}
|
||||
remote := m.targets[cReq.Host]
|
||||
if remote == nil {
|
||||
writeErr(fmt.Errorf("unknown incoming host: %v", cReq.Host))
|
||||
return
|
||||
}
|
||||
if time.Since(cReq.Time).Abs() > 5*time.Minute {
|
||||
writeErr(fmt.Errorf("time difference too large between servers: %v", time.Since(cReq.Time).Abs()))
|
||||
return
|
||||
}
|
||||
if err := m.authToken(cReq.Token, cReq.audience()); err != nil {
|
||||
writeErr(fmt.Errorf("auth token: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if debugPrint {
|
||||
fmt.Printf("handler: Got Connect Req %+v\n", cReq)
|
||||
}
|
||||
writeErr(remote.handleIncoming(ctx, conn, cReq))
|
||||
}
|
||||
|
||||
// AuthFn should provide an authentication string for the given aud.
|
||||
type AuthFn func(aud string) string
|
||||
|
||||
// ValidateAuthFn should check authentication for the given aud.
|
||||
type ValidateAuthFn func(auth, aud string) string
|
||||
|
||||
// Connection will return the connection for the specified host.
|
||||
// If the host does not exist nil will be returned.
|
||||
func (m *Manager) Connection(host string) *Connection {
|
||||
|
||||
+15
-2
@@ -21,6 +21,7 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tinylib/msgp/msgp"
|
||||
"github.com/zeebo/xxh3"
|
||||
@@ -255,8 +256,20 @@ type sender interface {
|
||||
}
|
||||
|
||||
type connectReq struct {
|
||||
ID [16]byte
|
||||
Host string
|
||||
ID [16]byte
|
||||
Host string
|
||||
Time time.Time
|
||||
Token string
|
||||
}
|
||||
|
||||
// audience returns the audience for the connect call.
|
||||
func (c *connectReq) audience() string {
|
||||
return fmt.Sprintf("%s-%d", c.Host, c.Time.Unix())
|
||||
}
|
||||
|
||||
// addToken will add the token to the connect request.
|
||||
func (c *connectReq) addToken(fn AuthFn) {
|
||||
c.Token = fn(c.audience())
|
||||
}
|
||||
|
||||
func (connectReq) Op() Op {
|
||||
|
||||
@@ -192,6 +192,18 @@ func (z *connectReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
err = msgp.WrapError(err, "Host")
|
||||
return
|
||||
}
|
||||
case "Time":
|
||||
z.Time, err = dc.ReadTime()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Time")
|
||||
return
|
||||
}
|
||||
case "Token":
|
||||
z.Token, err = dc.ReadString()
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Token")
|
||||
return
|
||||
}
|
||||
default:
|
||||
err = dc.Skip()
|
||||
if err != nil {
|
||||
@@ -205,9 +217,9 @@ func (z *connectReq) DecodeMsg(dc *msgp.Reader) (err error) {
|
||||
|
||||
// EncodeMsg implements msgp.Encodable
|
||||
func (z *connectReq) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
// map header, size 2
|
||||
// map header, size 4
|
||||
// write "ID"
|
||||
err = en.Append(0x82, 0xa2, 0x49, 0x44)
|
||||
err = en.Append(0x84, 0xa2, 0x49, 0x44)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -226,19 +238,45 @@ func (z *connectReq) EncodeMsg(en *msgp.Writer) (err error) {
|
||||
err = msgp.WrapError(err, "Host")
|
||||
return
|
||||
}
|
||||
// write "Time"
|
||||
err = en.Append(0xa4, 0x54, 0x69, 0x6d, 0x65)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteTime(z.Time)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Time")
|
||||
return
|
||||
}
|
||||
// write "Token"
|
||||
err = en.Append(0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = en.WriteString(z.Token)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Token")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalMsg implements msgp.Marshaler
|
||||
func (z *connectReq) MarshalMsg(b []byte) (o []byte, err error) {
|
||||
o = msgp.Require(b, z.Msgsize())
|
||||
// map header, size 2
|
||||
// map header, size 4
|
||||
// string "ID"
|
||||
o = append(o, 0x82, 0xa2, 0x49, 0x44)
|
||||
o = append(o, 0x84, 0xa2, 0x49, 0x44)
|
||||
o = msgp.AppendBytes(o, (z.ID)[:])
|
||||
// string "Host"
|
||||
o = append(o, 0xa4, 0x48, 0x6f, 0x73, 0x74)
|
||||
o = msgp.AppendString(o, z.Host)
|
||||
// string "Time"
|
||||
o = append(o, 0xa4, 0x54, 0x69, 0x6d, 0x65)
|
||||
o = msgp.AppendTime(o, z.Time)
|
||||
// string "Token"
|
||||
o = append(o, 0xa5, 0x54, 0x6f, 0x6b, 0x65, 0x6e)
|
||||
o = msgp.AppendString(o, z.Token)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -272,6 +310,18 @@ func (z *connectReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
err = msgp.WrapError(err, "Host")
|
||||
return
|
||||
}
|
||||
case "Time":
|
||||
z.Time, bts, err = msgp.ReadTimeBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Time")
|
||||
return
|
||||
}
|
||||
case "Token":
|
||||
z.Token, bts, err = msgp.ReadStringBytes(bts)
|
||||
if err != nil {
|
||||
err = msgp.WrapError(err, "Token")
|
||||
return
|
||||
}
|
||||
default:
|
||||
bts, err = msgp.Skip(bts)
|
||||
if err != nil {
|
||||
@@ -286,7 +336,7 @@ func (z *connectReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
|
||||
|
||||
// Msgsize returns an upper bound estimate of the number of bytes occupied by the serialized message
|
||||
func (z *connectReq) Msgsize() (s int) {
|
||||
s = 1 + 3 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + 5 + msgp.StringPrefixSize + len(z.Host)
|
||||
s = 1 + 3 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + 5 + msgp.StringPrefixSize + len(z.Host) + 5 + msgp.TimeSize + 6 + msgp.StringPrefixSize + len(z.Token)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2015-2021 MinIO, Inc.
|
||||
// Copyright (c) 2015-2024 MinIO, Inc.
|
||||
//
|
||||
// This file is part of MinIO Object Storage stack
|
||||
//
|
||||
@@ -110,7 +110,7 @@ func (f fatalMsg) quiet(msg string, args ...interface{}) {
|
||||
}
|
||||
|
||||
var (
|
||||
logTag = "ERROR"
|
||||
logTag = "FATAL"
|
||||
logBanner = color.BgRed(color.FgWhite(color.Bold(logTag))) + " "
|
||||
emptyBanner = color.BgRed(strings.Repeat(" ", len(logTag))) + " "
|
||||
bannerWidth = len(logTag) + 1
|
||||
@@ -187,7 +187,7 @@ func (i infoMsg) pretty(msg string, args ...interface{}) {
|
||||
if msg == "" {
|
||||
fmt.Fprintln(Output, args...)
|
||||
} else {
|
||||
fmt.Fprintf(Output, msg, args...)
|
||||
fmt.Fprintf(Output, `INFO: `+msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,13 +222,13 @@ func (i errorMsg) pretty(msg string, args ...interface{}) {
|
||||
if msg == "" {
|
||||
fmt.Fprintln(Output, args...)
|
||||
} else {
|
||||
fmt.Fprintf(Output, msg, args...)
|
||||
fmt.Fprintf(Output, `ERRO: `+msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error :
|
||||
func Error(msg string, data ...interface{}) {
|
||||
if DisableErrorLog {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
consoleLog(errorMessage, msg, data...)
|
||||
@@ -236,8 +236,92 @@ func Error(msg string, data ...interface{}) {
|
||||
|
||||
// Info :
|
||||
func Info(msg string, data ...interface{}) {
|
||||
if DisableErrorLog {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
consoleLog(info, msg, data...)
|
||||
}
|
||||
|
||||
// Startup :
|
||||
func Startup(msg string, data ...interface{}) {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
consoleLog(startup, msg, data...)
|
||||
}
|
||||
|
||||
type startupMsg struct{}
|
||||
|
||||
var startup startupMsg
|
||||
|
||||
func (i startupMsg) json(msg string, args ...interface{}) {
|
||||
var message string
|
||||
if msg != "" {
|
||||
message = fmt.Sprintf(msg, args...)
|
||||
} else {
|
||||
message = fmt.Sprint(args...)
|
||||
}
|
||||
logJSON, err := json.Marshal(&log.Entry{
|
||||
Level: InfoKind,
|
||||
Message: message,
|
||||
Time: time.Now().UTC(),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Fprintln(Output, string(logJSON))
|
||||
}
|
||||
|
||||
func (i startupMsg) quiet(msg string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func (i startupMsg) pretty(msg string, args ...interface{}) {
|
||||
if msg == "" {
|
||||
fmt.Fprintln(Output, args...)
|
||||
} else {
|
||||
fmt.Fprintf(Output, msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
type warningMsg struct{}
|
||||
|
||||
var warningMessage warningMsg
|
||||
|
||||
func (i warningMsg) json(msg string, args ...interface{}) {
|
||||
var message string
|
||||
if msg != "" {
|
||||
message = fmt.Sprintf(msg, args...)
|
||||
} else {
|
||||
message = fmt.Sprint(args...)
|
||||
}
|
||||
logJSON, err := json.Marshal(&log.Entry{
|
||||
Level: WarningKind,
|
||||
Message: message,
|
||||
Time: time.Now().UTC(),
|
||||
Trace: &log.Trace{Message: message, Source: []string{getSource(6)}},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Fprintln(Output, string(logJSON))
|
||||
}
|
||||
|
||||
func (i warningMsg) quiet(msg string, args ...interface{}) {
|
||||
i.pretty(msg, args...)
|
||||
}
|
||||
|
||||
func (i warningMsg) pretty(msg string, args ...interface{}) {
|
||||
if msg == "" {
|
||||
fmt.Fprintln(Output, args...)
|
||||
} else {
|
||||
fmt.Fprintf(Output, `WARN: `+msg, args...)
|
||||
}
|
||||
}
|
||||
|
||||
// Warning :
|
||||
func Warning(msg string, data ...interface{}) {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
consoleLog(warningMessage, msg, data...)
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// DisableErrorLog avoids printing error/event/info kind of logs
|
||||
DisableErrorLog = false
|
||||
// DisableLog avoids printing error/event/info kind of logs
|
||||
DisableLog = false
|
||||
// Output allows configuring custom writer, defaults to os.Stderr
|
||||
Output io.Writer = os.Stderr
|
||||
)
|
||||
@@ -386,7 +386,7 @@ func buildLogEntry(ctx context.Context, subsystem, message string, trace []strin
|
||||
// consoleLogIf prints a detailed error message during
|
||||
// the execution of the server.
|
||||
func consoleLogIf(ctx context.Context, subsystem string, err error, errKind ...interface{}) {
|
||||
if DisableErrorLog {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
@@ -401,7 +401,7 @@ func consoleLogIf(ctx context.Context, subsystem string, err error, errKind ...i
|
||||
// logIf prints a detailed error message during
|
||||
// the execution of the server.
|
||||
func logIf(ctx context.Context, subsystem string, err error, errKind ...interface{}) {
|
||||
if DisableErrorLog {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
@@ -430,7 +430,7 @@ func sendLog(ctx context.Context, entry log.Entry) {
|
||||
|
||||
// Event sends a event log to log targets
|
||||
func Event(ctx context.Context, subsystem, msg string, args ...interface{}) {
|
||||
if DisableErrorLog {
|
||||
if DisableLog {
|
||||
return
|
||||
}
|
||||
entry := logToEntry(ctx, subsystem, fmt.Sprintf(msg, args...), EventKind)
|
||||
|
||||
Reference in New Issue
Block a user