Compare commits

..

204 Commits

Author SHA1 Message Date
Taran Pelkey 6c6f0987dc Add groups to policy entities (#20052)
* Add groups to policy entities

* update comment

---------

Co-authored-by: Harshavardhana <harsha@minio.io>
2024-07-10 11:41:49 -07:00
Austin Chang 5f64658faa clarify error message for root user credential (#20043)
Signed-off-by: Austin Chang <austin880625@gmail.com>
2024-07-10 09:57:01 -07:00
Anis Eleuch ce183cb2b4 heal: List and heal again for any listing error (#19999)
When a fresh drive healing is finished, add more checks for the drive listing
errors. If any, re-list and heal again. Although this is an infrequent use
case to have listPathRaw() returning nil when minDisks is set to 1, we
still need to handle all possible use cases to avoid missing healing
any object.

Also, check for HealObject result to decide of an object is healed in the
fresh disk since HealObject returns nil if an object is healed in any
disk, and not in the new fresh drive.
2024-07-10 09:55:36 -07:00
Klaus Post b3bac73c0f Clarify post policy error message (#20067)
It is not really clear that the listed keys are missing.

Clarify the error
2024-07-10 07:18:44 -07:00
Anis Eleuch e726d8ff0f list: Hide objects/versions with pending/failed replicated deletion (#20047)
In regular listing, this commit will avoid showing an object when its
latest version has a pending or failed deletion. In replicated setup.
It will also prevent showing older versions in the same case.
2024-07-09 15:26:42 -07:00
Shubhendu f4230777b3 Log replication errors once (#20063)
Also, sort the error map for multiple sites in ascending order
of deployment IDs, so that the error message generated is always
definitive order and same.

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-07-09 10:10:31 -07:00
Krishnan Parthasarathi 380233d646 batch: Update job info object on success (#20053) 2024-07-08 18:45:54 -07:00
Allan Roger Reid d592bc0c1c Fix documentation for removal of delete markers ILM rule (#20056) 2024-07-08 18:45:38 -07:00
Klaus Post 0d0b0aa599 Abstract grid connections (#20038)
Add `ConnDialer` to abstract connection creation.

- `IncomingConn(ctx context.Context, conn net.Conn)` is provided as an entry point for 
   incoming custom connections.

- `ConnectWS` is provided to create web socket connections.
2024-07-08 14:44:00 -07:00
Anis Eleuch b433bf14ba Add typos check to Makefile (#20051) 2024-07-08 14:39:49 -07:00
Minio Trusted cf371da346 Update yaml files to latest version RELEASE.2024-07-04T14-25-45Z 2024-07-04 14:58:08 +00:00
Klaus Post 107d951893 Log ILM failed object name (#20040)
Log so we know which object we are dealing with.

Log each object once.
2024-07-04 07:25:45 -07:00
Shireesh Anjal 22c53b1c70 Remove license update job (#20037) 2024-07-03 11:49:48 -07:00
Mark Theunissen 88926ad8e9 return appropriate error upon tier update for incorrect credentials (#20034) 2024-07-03 00:17:20 -07:00
Harshavardhana 32d04091a2 resume any batch jobs in a goroutine (#20035)
Bonus: move batch job initialization to the last item after all other initialization, 
            allowing for faster startup time for different subsystems.
2024-07-03 00:16:05 -07:00
Harshavardhana b6d4a77b94 update vulncheck 2024-07-02 14:34:59 -07:00
Harshavardhana be84a4fd68 do not proxy invalid object names (#20031) 2024-07-02 14:28:55 -07:00
Anis Eleuch 2ec1f404ac info: Always refresh the root disk status (#20023)
Add root drive status in the disk info cache function, so unmounting a
drive without restarting a local node reflects the correct value.
2024-07-02 13:41:29 -07:00
Klaus Post 2040559f71 Fix SkipReader performance with small initial read (#20030)
If `SkipReader` is called with a small initial buffer it may be doing a huge number if Reads to skip the requested number of bytes. If a small buffer is provided grab a 32K buffer and use that.

Fixes slow execution of `testAPIGetObjectWithMPHandler`.

Bonuses:

* Use `-short` with `-race` test.
* Do all suite test types with `-short`.
* Enable compressed+encrypted in `testAPIGetObjectWithMPHandler`.
* Disable big file tests in `testAPIGetObjectWithMPHandler` when using `-short`.
2024-07-02 08:13:05 -07:00
Anis Eleuch ca0ce4c6ef tests: Fix setting max openfds as memory limit (#20029)
The code was advertenly passing max openfds to debug.SetMemoryLimit(),
fixing this accelerate go test in my machine.

This is only a testing bug, since the server context has always a valid
MaxMem, so the buggy code was never called in users environments.
2024-07-02 08:09:36 -07:00
Anis Eleuch 757cf413cb Add batch status API (#19679)
Currently the status of a completed or failed batch is held in the
memory, a simple restart will lose the status and the user will not
have any visibility of the job that was long running.

In addition to the metrics, add a new API that reads the batch status
from the drives. A batch job will be cleaned up three days after
completion.

Also add the batch type in the batch id, the reason is that the batch
job request is removed immediately when the job is finished, then we
do not know the type of batch job anymore, hence a difficulty to locate
the job report
2024-07-02 01:17:52 -07:00
Anis Eleuch b35acb3dbc heal: Add support of healing particular pool/set (#20024) 2024-07-01 15:02:25 -07:00
Sveinn e404abf103 Letting password enable auth bypass caPublicKey (only if passauth is … (#20022) 2024-07-01 15:02:01 -07:00
jiuker f7ff19cb18 fix: warning for decommissioned pool while start (#20019) 2024-07-01 07:38:46 -07:00
Minio Trusted f736702da8 Update yaml files to latest version RELEASE.2024-06-29T01-20-47Z 2024-06-29 16:45:31 +00:00
Poorna 91faaa1387 fix panic in batch replicate (#20014)
Fixes:

```
panic: send on closed channel
	panic: close of closed channel

goroutine 878 [running]:
github.com/minio/minio/internal/ioutil.SafeClose[...](...)
	/Users/kp/code/src/github.com/minio/minio/internal/ioutil/ioutil.go:407
github.com/minio/minio/cmd.(*erasureServerPools).Walk.func2.2()
	/Users/kp/code/src/github.com/minio/minio/cmd/erasure-server-pool.go:2229 +0xc0
panic({0x108c25e60?, 0x1090b28d0?})
	/usr/local/go/src/runtime/panic.go:770 +0x124
github.com/minio/minio/cmd.(*erasureServerPools).Walk.func2.3({{0x1400e397316, 0x5}, {0x1400d88b8a8, 0x8}, {0x1f99d80, 0xede101c42, 0x0}, 0x3bc, 0x0, 0x0, ...})
	/Users/kp/code/src/github.com/minio/minio/cmd/erasure-server-pool.go:2235 +0xb4
github.com/minio/minio/cmd.(*erasureServerPools).Walk.func2()
	/Users/kp/code/src/github.com/minio/minio/cmd/erasure-server-pool.go:2277 +0xabc
created by github.com/minio/minio/cmd.(*erasureServerPools).Walk in goroutine 575
	/Users/kp/code/src/github.com/minio/minio/cmd/erasure-server-pool.go:2210 +0x33c
```
2024-06-28 18:20:47 -07:00
Poorna 68a9f521d5 fix object lock metadata filter (#20011) 2024-06-28 18:20:27 -07:00
Harshavardhana f365a98029 fix: hot-reloading STS credential policy documents (#20012)
* fix: hot-reloading STS credential policy documents
* Support Role ARNs hot load policies (#28)

---------

Co-authored-by: Anis Eleuch <vadmeste@users.noreply.github.com>
2024-06-28 16:17:22 -07:00
Minio Trusted 47bbc272df Update yaml files to latest version RELEASE.2024-06-28T09-06-49Z 2024-06-28 14:11:36 +00:00
Anis Eleuch aebac90013 tests: Fix minor issue in the config yaml file testing (#20005)
Convert x86_64 to amd64 in the test script to correctly download mc binary.
2024-06-28 02:06:49 -07:00
Taran Pelkey 7ca4ba77c4 Update tests to use AttachPolicy(LDAP) instead of deprecated SetPolicy (#19972) 2024-06-28 02:06:25 -07:00
Poorna 13512170b5 list: Do not decrypt SSE-S3 Etags in a non encrypted format (#20008) 2024-06-27 19:44:56 -07:00
Krishnan Parthasarathi 154fcaeb56 Allow rebalance start when it's stopped/completed (#20009) 2024-06-27 17:22:30 -07:00
Anis Eleuch 722118386d iam: Hot load of the policy during request authorization (#20007)
Hot load a policy document when during account authorization evaluation
to avoid returning 403 during server startup, when not all policies are
already loaded.

Add this support for group policies as well.
2024-06-27 17:03:07 -07:00
Harshavardhana 709612cb37 fix: rebalance upon pool expansion would crash when in progress (#20004)
you can attempt a rebalance first i.e, start with 2 pools.

```
mc admin rebalance start alias/
```

and after that you can add a new pool, this would
potentially crash.

```
Jun 27 09:22:19 xxx minio[7828]: panic: runtime error: invalid memory address or nil pointer dereference
Jun 27 09:22:19 xxx minio[7828]: [signal SIGSEGV: segmentation violation code=0x1 addr=0x58 pc=0x22cc225]
Jun 27 09:22:19 xxx minio[7828]: goroutine 1 [running]:
Jun 27 09:22:19 xxx minio[7828]: github.com/minio/minio/cmd.(*erasureServerPools).findIndex(...)
```
2024-06-27 11:35:34 -07:00
Harshavardhana b35d083872 fix; change retry-after 60sec for 503s and 10s for 429s (#19996) 2024-06-26 01:32:06 -07:00
Harshavardhana 5e7b243bde extend cluster health to return errors for IAM, and Bucket metadata (#19995)
Bonus: make API freeze to be opt-in instead of default
2024-06-26 00:44:34 -07:00
Minio Trusted f8f9fc77ac Update yaml files to latest version RELEASE.2024-06-26T01-06-18Z 2024-06-26 02:12:12 +00:00
Harshavardhana 499531f0b5 update minio/console v1.6.1
Signed-off-by: Harshavardhana <harsha@minio.io>
2024-06-25 18:06:18 -07:00
Taran Pelkey 3c2141513f add ListAccessKeysLDAPBulk API to list accessKeys for multiple/all LDAP users (#19835) 2024-06-25 14:21:28 -07:00
Aditya Manthramurthy 602f6a9ad0 Add IAM (re)load timing logs (#19984)
This is useful to debug large IAM load times - the usual cause is when
there are a large amount of temporary accounts.
2024-06-25 10:33:10 -07:00
Harshavardhana 22c5a5b91b add healing retries when there are failed heal attempts (#19986)
transient errors for long running tasks are normal, allow for
drive to retry again upto 3 times before giving up on healing
the drive.
2024-06-25 10:32:56 -07:00
jiuker 41f508765d fix: format the scanner object error (#19991) 2024-06-25 08:54:24 -07:00
Aditya Manthramurthy 7dccd1f589 fix: bootstrap msgs should only be sent at startup (#19985) 2024-06-24 19:30:28 -07:00
Allan Roger Reid 55ff598b23 Refactor the documentation on minio server config notation (#19987)
Refactor minio server config notation to add bracket notation to the TODO list
2024-06-24 19:30:18 -07:00
Harshavardhana a22ce4550c protect workers and simplify use of atomics (#19982)
without atomic load() it is possible that for
a slow receiver we would get into a hot-loop, when
logCh is full and there are many incoming callers.

to avoid this as a workaround enable BATCH_SIZE
greater than 100 to ensure that your slow receiver
receives data in bulk to avoid being throttled in
some manner.

this PR however fixes the unprotected access to
the current workers value.
2024-06-24 18:15:27 -07:00
Taran Pelkey 168ae81b1f Fix error when validating DN that is not under base DN (#19971) 2024-06-21 23:35:35 -07:00
Minio Trusted 5f6a25cdd0 Update yaml files to latest version RELEASE.2024-06-22T05-26-45Z 2024-06-22 06:20:13 +00:00
Harshavardhana be97ae4c5d fix: gcs tier going offline due to customer HTTPclient (#19973)
specifying customer HTTP client makes the gcs SDK
ignore the passed credentials, instead let the GCS
SDK manage the transport.

this PR fixes #19922 a regression from #19565
2024-06-21 22:26:45 -07:00
Anis Eleuch 4d7d008741 bootstrap: Speed up bucket metadata loading (#19969)
Currently, bucket metadata is being loaded serially inside ListBuckets
Objet API. Fix that by loading the bucket metadata as the number of
erasure sets * 10, which is a good approximation.
2024-06-21 15:22:24 -07:00
Klaus Post 2d7a3d1516 Return error from mergeEntryChannels (#19970)
- Add error from mergeEntryChannels to `results.`
- Make sure we check the context error before we close the channel.
2024-06-21 12:06:51 -07:00
Harshavardhana dfab400d43 reject bootup, if binaries are different in a cluster (#19968) 2024-06-21 07:49:49 -07:00
Pedro Juarez 70078eab10 Fix browser UI animation (#19966)
Browse UI is not showing the animation because the default 
content-security-policy do not trust the file https://unpkg.com/detect-gpu@5.0.38/dist/benchmarks/d-apple.json 
the GPU library needs to identify if the web browser can play it.
2024-06-20 17:58:58 -07:00
Klaus Post 3415c4dd1e Fix reconnected deadlock with full queue (#19964)
When a reconnection happens, `handleMessages` must be able to complete and exit. 

This can be prevented in a full queue.

Deadlock chain (May 10th release)

```
1 @ 0x44110e 0x453125 0x109f88c 0x109f7d5 0x10a472c 0x10a3f72 0x10a34ed 0x4795e1
#	0x109f88b	github.com/minio/minio/internal/grid.(*Connection).send+0x3eb			github.com/minio/minio/internal/grid/connection.go:548
#	0x109f7d4	github.com/minio/minio/internal/grid.(*Connection).queueMsg+0x334		github.com/minio/minio/internal/grid/connection.go:586
#	0x10a472b	github.com/minio/minio/internal/grid.(*Connection).handleAckMux+0xab		github.com/minio/minio/internal/grid/connection.go:1284
#	0x10a3f71	github.com/minio/minio/internal/grid.(*Connection).handleMsg+0x231		github.com/minio/minio/internal/grid/connection.go:1211
#	0x10a34ec	github.com/minio/minio/internal/grid.(*Connection).handleMessages.func1+0x6cc	github.com/minio/minio/internal/grid/connection.go:1019

---> blocks ---> via (Connection).handleMsgWg

1 @ 0x44110e 0x454165 0x454134 0x475325 0x486b08 0x10a161a 0x10a1465 0x2470e67 0x7395a9 0x20e61af 0x20e5f1f 0x7395a9 0x22f781c 0x7395a9 0x22f89a5 0x7395a9 0x22f6e82 0x7395a9 0x22f49a2 0x7395a9 0x2206e45 0x7395a9 0x22f4d9c 0x7395a9 0x210ba06 0x7395a9 0x23089c2 0x7395a9 0x22f86e9 0x7395a9 0xd42582 0x2106c04
#	0x475324	sync.runtime_Semacquire+0x24								runtime/sema.go:62
#	0x486b07	sync.(*WaitGroup).Wait+0x47								sync/waitgroup.go:116
#	0x10a1619	github.com/minio/minio/internal/grid.(*Connection).reconnected+0xb9			github.com/minio/minio/internal/grid/connection.go:857
#	0x10a1464	github.com/minio/minio/internal/grid.(*Connection).handleIncoming+0x384			github.com/minio/minio/internal/grid/connection.go:825
```

Add a queue cleaner in reconnected that will pop old messages so `handleMessages` can 
send messages without blocking and exit appropriately for the connection to be re-established.

Messages are likely dropped by the remote, but we may have some that can succeed, 
so we only drop when running out of space.
2024-06-20 16:11:40 -07:00
Shireesh Anjal e200808ab7 fix errors in metrics code on macos (#19965)
- do not load proc fs metrics in case of macos
- null-check TimeStat before accessing
2024-06-20 10:55:03 -07:00
Klaus Post fae563b85d Add fixed timed restarts to updates (#19960) 2024-06-20 07:49:22 -07:00
Klaus Post 3e6dc02f8f Add actual inline data to JSON output in xl-meta (#19958)
Add the inlined data as base64 encoded field and try to add a string version if feasible.

Example:

```
λ xl-meta -data xl.meta
{
  "8e03504e-1123-4957-b272-7bc53eda0d55": {
    "bitrot_valid": true,
    "bytes": 58,
    "data_base64": "Z29sYW5nLm9yZy94L3N5cyB2MC4xNS4wIC8=",
    "data_string": "golang.org/x/sys v0.15.0 /"
}
```

The string will have quotes, newlines escaped to produce valid JSON.

If content isn't valid utf8 or the encoding otherwise fails, only the base64 data will be added.

`-export` can still be used separately to extract the data as files (including bitrot).
2024-06-20 07:46:44 -07:00
Anis Eleuch 95e4cbbfde Do not ping event targets during cluster initialization (#19959)
S3 operations are frozen during startup, therefore we should avoid pinging
event targets during the initialization since it can stall.
2024-06-20 07:46:02 -07:00
Harshavardhana 2825294b7b allow server startup to come online with READ success (#19957) 2024-06-19 22:21:31 -07:00
Sveinn bce93b5cfa Removing timeout on shutdown (#19956) 2024-06-19 11:42:47 -07:00
Harshavardhana 7a4b250c8b avoid waiting for quorum health while debugging (#19955) 2024-06-19 10:12:20 -07:00
Anis Eleuch e5335450a4 test: Healing test to avoid infinite waiting for servers to be up (#19954)
tests: Healing test to avoid infinite waiting for servers to be up

Quit after 15 minutes and print server logs instead
2024-06-19 09:00:38 -07:00
Klaus Post a6ffdf1dd4 Do not block on distributed unlocks (#19952)
* Prevents blocking when losing quorum (standard on cluster restarts).
* Time out to prevent endless buildup. Timed-out remote locks will be canceled because they miss the refresh anyway.
* Reduces latency for all calls since the wall time for the roundtrip to remotes no longer adds to the requests.
2024-06-19 07:35:19 -07:00
Harshavardhana 69e41f87ef compute localIPs only once per server startup() (#19951)
repeatedly calling this function is not necessary,
on systems with lots of interfaces, including virtual
ones can make this reasonably delayed.
2024-06-19 07:34:00 -07:00
Harshavardhana ee48f9f206 perform healthchecks before initializing everything fully (#19953)
adds more informative logs that provide details on which
erasure set is losing quorum etc.
2024-06-19 07:33:40 -07:00
Sveinn 9ba39d7fad Removing a channel that was not being used (#19948) 2024-06-19 01:59:39 -07:00
Harshavardhana d2fb371f80 do not need response record body (#19949)
since the connection is active, the
response recorder body can grow endlessly
causing leak, as this bytes buffer is
never given back to GC due to an goroutine.
2024-06-19 01:59:21 -07:00
Klaus Post 2f9018f03b Do regular checks for healing status while scanning (#19946) 2024-06-18 09:11:04 -07:00
Harshavardhana eb990f64a9 update pkger to use v2.3.1 2024-06-17 22:48:41 -07:00
Harshavardhana bbb64eaade skip healing properly in the scanner when a drive is hotplugged (#19939)
skip healing properly in scanner when drive is hotplugged

due to how the state is passed around the SkipHealing
might not be the true state() of the system always, causing
a situation where we might healing from the scanner on the
same drive which is being. Due to this competing heals get
triggered that slow each other down.
2024-06-17 16:39:11 -07:00
Harshavardhana 7bd1d899bc remove overzealous check during HEAD() (#19940)
due to a historic bug in CopyObject() where
an inlined object loses its metadata, the
check causes an incorrect fallback verifying
data-dir.

CopyObject() bug was fixed in ffa91f9794 however
the occurrence of this problem is historic, so
the aforementioned check is stretching too much.

Bonus: simplify fileInfoRaw() to read xl.json as well,
also recreate buckets properly.
2024-06-17 07:29:18 -07:00
Harshavardhana c91d1ec2e3 fix: avoid metadata cache without data for all callers (#19935) 2024-06-14 06:28:35 -07:00
Minio Trusted c50b64027d Update yaml files to latest version RELEASE.2024-06-13T22-53-53Z 2024-06-14 05:40:03 +00:00
Cesar N 20960b6a2d Update console to v1.6.0 (#19933) 2024-06-13 15:53:53 -07:00
Shubhendu 3bd3470d0b Corrected names of node replication metrics (#19932)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-06-13 15:26:54 -07:00
Harshavardhana ba39ed9af7 loadUser() if not able to load() credential return error (#19931) 2024-06-13 15:26:38 -07:00
jiuker 62e6dc950d fix: do not update metadata cache upon headObject() (#19929) 2024-06-13 08:42:02 -07:00
Harshavardhana 5a5046ce45 upgrade all deps and credits (#19930)
Signed-off-by: Harshavardhana <harsha@minio.io>
2024-06-13 08:34:20 -07:00
Klaus Post ad04afe381 Fix SSEC multipart checksum replication (#19915)
* Multipart SSEC checksums were not transferred.
* Remove key mismatch logging. This key is user-controlled with SSEC.
* If the source is SSEC and the destination reports ErrSSEEncryptedObject, 
  assume replication is good.
2024-06-12 23:56:12 -07:00
Harshavardhana ba9f0f2480 fix: attempt to fix CI/CD upgrade tests with docker-compose (#19926) 2024-06-12 22:08:11 -07:00
Harshavardhana d06b63d056 load credential for in-flights requests as singleflight (#19920)
avoid concurrent callers for LoadUser() to even initiate
object read() requests, if an on-going operation is in progress.

this avoids many callers hitting the drives causing I/O
spikes, also allows for loading credentials faster.
2024-06-12 13:47:56 -07:00
Andreas Auernhammer 7ce28c3b1d kms: use GetClientCertificate callback for KES API keys (#19921)
This commit fixes an issue in the KES client configuration
that can cause the following error when connecting to KES:
```
ERROR Failed to connect to KMS: failed to generate data key with KMS key: tls: client certificate is required
```

The Go TLS stack seems to not send a client certificate if it
thinks the client certificate cannot be validated by the peer.
In case of an API key, we don't care about this since we use
public key pinning and the X.509 certificate is just a transport
encoding.

The `GetClientCertificate` seems to be honored always such that
this error does not occur.

Signed-off-by: Andreas Auernhammer <github@aead.dev>
2024-06-12 07:31:26 -07:00
Harshavardhana e3ac4035b9 decrement requests inqueue correctly after the request is processed (#19918) 2024-06-12 01:13:12 -07:00
Harshavardhana d21b6daa49 fix: avoid crash when delete() returns an error in batch expiration (#19909) 2024-06-11 06:50:53 -07:00
Minio Trusted 76ebb16688 Update yaml files to latest version RELEASE.2024-06-11T03-13-30Z 2024-06-11 06:11:10 +00:00
Harshavardhana 55aa431578 fix: on windows avoid ':' as part of the object name (#19907)
fixes #18865
avoid-colon
2024-06-10 20:13:30 -07:00
Harshavardhana 614981e566 allow purge expired STS while loading credentials (#19905)
the reason for this is to avoid STS mappings to be
purged without a successful load of other policies,
and all the credentials only loaded successfully
are properly handled.

This also avoids unnecessary cache store which was
implemented earlier for optimization.
2024-06-10 11:45:50 -07:00
Harshavardhana b8b956a05d add changes to Makefile to support dev build 2024-06-10 10:41:02 -07:00
Klaus Post d2eed44c78 Fix replication checksum transfer (#19906)
Compression will be disabled by default if SSE-C is specified. So we can still honor SSE-C.
2024-06-10 10:40:33 -07:00
Anis Eleuch 789cbc6fb2 heal: Dangling check to evaluate object parts separately (#19797) 2024-06-10 08:51:27 -07:00
jiuker 0662c90b5c fix: copyObject restore with a specific version, update test cases (#19895) 2024-06-10 08:50:49 -07:00
Klaus Post a2cab02554 Fix SSE-C checksums (#19896)
Compression will be disabled by default if SSE-C is specified. So we can still honor SSE-C.
2024-06-10 08:31:51 -07:00
Harshavardhana 6c7a21df6b turn-off unexpected debug logging in List() calls (#19903) 2024-06-09 21:34:26 -07:00
Ali Afsharzadeh f933b0b708 Upgrade setup-helm action from v3 to v4 (#19897) 2024-06-09 02:13:09 -07:00
Shubhendu 9f305273a7 Added tests for replication of checksum headers (#19879)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-06-08 09:24:15 -07:00
Cesar N cbd9efcb43 Update console to v1.5.0 (#19899) 2024-06-08 01:12:00 -07:00
Harshavardhana 29a25a538f fix: make sure we list freeVersions like DEL marker with --versions (#19878)
freeVersions() was being incorrectly skipped; list it as
valid objects properly.

Co-authored-by: Krishnan Parthasarathi <Krishnan Parthasarathi>
2024-06-07 15:18:44 -07:00
Harshavardhana 2dd8faaedc remove unnecessary log in Listing() 2024-06-07 14:52:55 -07:00
Klaus Post f00187033d Two way streams for upcoming locking enhancements (#19796) 2024-06-07 08:51:52 -07:00
Aditya Manthramurthy c5141d65ac Update docker build script to pull all changes (#19892) 2024-06-07 08:43:38 -07:00
Krishnan Parthasarathi 069c4015cd Don't tier directory objects (#19891)
Directory objects are used by applications that simulate the folder
structure of an on-disk filesystem. These are zero-byte objects with names
ending with '/'. They are only used to check whether a 'folder' exists in
the namespace.
2024-06-07 08:43:17 -07:00
Shubhendu 2f6e03fb60 Calculate correct object size while replication (#19888)
It was missing in case of `replicateObject` but was present for
`replicateAll` already

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-06-06 12:31:01 -07:00
Klaus Post 0fbb945e13 Disable caching of encrypted objects (#19890)
Don't write encrypted objects to cache, if configured.
2024-06-06 11:39:18 -07:00
Anis Eleuch b94dd835c9 decom: Fix CurrentSize output when generating the status (#19883)
StartSize starts with the raw free space of all disks in the given pool,
however during the status, CurrentSize is not showing the current free
raw space, as expected at least by `mc admin decom status` since it was
written.
2024-06-06 07:30:43 -07:00
Minio Trusted 44fc707423 Update yaml files to latest version RELEASE.2024-06-06T09-36-42Z 2024-06-06 13:36:05 +00:00
Poorna 5aaef9790f replication: pass checksum headers to replica (#19834) 2024-06-06 02:36:42 -07:00
Bala FA 7edc352d23 Add ILM metrics in metrics-v3 (#19539)
Signed-off-by: Bala.FA <bala@minio.io>
2024-06-06 02:36:25 -07:00
Poorna 850a84b08a simplify site replication multipart proxying (#19885) 2024-06-05 18:01:15 -07:00
Taran Pelkey 4148754ce0 Check both given and normalized group DN on LDAP policy detach requests (#19876) 2024-06-05 15:42:40 -07:00
Harshavardhana 2107722829 upgrade go-oidc to fix GO-2024-2631 (#19884) 2024-06-05 15:00:34 -07:00
jiuker d326ba52e9 feat: support batchJob for windows (#19877) 2024-06-05 08:44:53 -07:00
Sveinn 91e1487de4 Add LDAP public key authentication to SFTP (#19833) 2024-06-05 00:51:13 -07:00
Minio Trusted 5ffb2a9605 Update yaml files to latest version RELEASE.2024-06-04T19-20-08Z 2024-06-04 22:25:53 +00:00
Harshavardhana 17fe91d6d1 chore: update all deps (#19875) 2024-06-04 12:20:08 -07:00
jiuker 90a9f2dd70 fix: log diskerror when detect the disk space failed (#19861) 2024-06-04 09:42:03 -07:00
Harshavardhana d5e48cfd65 fix: remove DriveOPTimeout for REST callers as they don't work properly (#19873)
Go's net/http is notoriously difficult to have a streaming
deadlines per READ/WRITE on the net.Conn if we add them they
interfere with the Go's internal requirements for a HTTP
connection.

Remove this support for now

fixes #19853
2024-06-04 08:12:57 -07:00
Anis Eleuch d274566463 race: Fix rare race detected by testing (#19872)
Below is the race warning:

```
WARNING: DATA RACE
Write at 0x00c02d3d27c0 by goroutine 1210:
  github.com/minio/minio/cmd.(*healingTracker).bucketDone()
      github.com/minio/minio/cmd/background-newdisks-heal-ops.go:273 +0x13a
  github.com/minio/minio/cmd.(*erasureObjects).healErasureSet()
      github.com/minio/minio/cmd/global-heal.go:525 +0x2158
  github.com/minio/minio/cmd.healFreshDisk()
      github.com/minio/minio/cmd/background-newdisks-heal-ops.go:450 +0x107e
  github.com/minio/minio/cmd.monitorLocalDisksAndHeal.func1()
      github.com/minio/minio/cmd/background-newdisks-heal-ops.go:528 +0x150
  github.com/minio/minio/cmd.monitorLocalDisksAndHeal.gowrap2()
      github.com/minio/minio/cmd/background-newdisks-heal-ops.go:538 +0x82

Previous read at 0x00c02d3d27c0 by goroutine 1446:
  github.com/minio/minio/cmd.(*erasureObjects).healErasureSet.func5()
      github.com/minio/minio/cmd/global-heal.go:232 +0xfd
```
2024-06-04 08:12:32 -07:00
Shubhendu 39ac720826 Remove hardcoded override as not needed (#19868)
Fixes: https://github.com/minio/minio/issues/19867

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-06-04 06:24:37 -07:00
Shubhendu 21b6204692 Test proxying of DEL marker for bucket replication (#19870)
Make sure to avoid proxying for DEL markers

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-06-04 04:38:26 -07:00
Taran Pelkey d98faeb26a Check if LDAP User has attached policy before creating Service Account (#19843)
Check if ldap user has policy before creating
2024-06-03 12:58:48 -07:00
Klaus Post 0a63dc199c Add trace sizes to more trace types (#19864)
Add trace sizes to

* ILM traces
* Replication traces
* Healing traces
* Decommission traces
* Rebalance traces
* (s)ftp traces
* http traces.
2024-06-03 08:45:54 -07:00
Anis Eleuch 3ba857dfa1 race: Fix detected test race in the internal audit code (#19865) 2024-06-03 08:44:50 -07:00
Klaus Post a8554c4022 Update madmin (#19862)
Make it include https://github.com/minio/madmin-go/pull/285
2024-06-03 05:00:14 -07:00
Harshavardhana ba54b39c02 fix: crash when audit webhook queue_dir is not writable (#19854)
This is regression introduced in #19275 refactor
2024-06-01 20:03:39 -07:00
Anis Eleuch 2a75225569 kafka: _MINIO_KAFKA_DEBUG to enable sarama debug messages (#19849) 2024-06-01 08:02:59 -07:00
Klaus Post e72429c79c Add sizes to traces (#19851)
added to storage and grid traces. Can provide more context for traces that aren't HTTP. Others may apply.
2024-05-31 22:17:37 -07:00
Klaus Post c5b3f5553f Add per connection RPC metrics (#19852)
Provides individual and aggregate stats for each RPC connection.

Example:

```
  "rpc": {
   "collectedAt": "2024-05-31T14:33:29.1373103+02:00",
   "connected": 30,
   "disconnected": 0,
   "outgoingStreams": 69,
   "incomingStreams": 0,
   "outgoingBytes": 174822796,
   "incomingBytes": 175821566,
   "outgoingMessages": 768595,
   "incomingMessages": 768589,
   "outQueue": 0,
   "lastPongTime": "2024-05-31T12:33:28Z",
   "byDestination": {
    "http://127.0.0.1:9001": {
     "collectedAt": "2024-05-31T14:33:29.1373103+02:00",
     "connected": 5,
     "disconnected": 0,
     "outgoingStreams": 2,
     "incomingStreams": 0,
     "outgoingBytes": 38432543,
     "incomingBytes": 66604052,
     "outgoingMessages": 229496,
     "incomingMessages": 229575,
     "outQueue": 0,
     "lastPongTime": "2024-05-31T12:33:27Z"
    },
    "http://127.0.0.1:9002": {
     "collectedAt": "2024-05-31T14:33:29.1373103+02:00",
     "connected": 5,
     "disconnected": 0,
     "outgoingStreams": 6,
     "incomingStreams": 0,
     "outgoingBytes": 38215680,
     "incomingBytes": 66121283,
     "outgoingMessages": 228525,
     "incomingMessages": 228510,
     "outQueue": 0,
     "lastPongTime": "2024-05-31T12:33:27Z"
    },
...
```
2024-05-31 22:16:24 -07:00
Klaus Post d3ae0aaad3 Add max buffering to SFTP (#19848)
Prevent OOM by adversarial use of SFTP upload by setting a 100MB max upload buffer.
2024-05-31 14:28:07 -07:00
Klaus Post d67bccf861 Add xl-meta partial shard reconstruction (#19841)
Add partial shard reconstruction

* Add partial shard reconstruction
* Fix padding causing the last shard to be rejected
* Add md5 checks on single parts
* Move md5 verified to `verified/filename.ext`
* Move complete (without md5) to `complete/filename.ext.partno`

It's not pretty, but at least now the md5 gives some confidence it works correctly.
2024-05-31 07:49:23 -07:00
Anis Eleuch 1277ad69a6 heal: Remove .healing.bin when all ES drives are healing (#19846)
In the very rare case when all drives in a erasure set need to be healed,
remove .healing.bin from all drives, otherwise it will be stuck in a
loop

Also, fix a unit test that fails sometimes due to wrong test.
2024-05-31 07:48:50 -07:00
Harshavardhana 8f93e81afb change service account embedded policy size limit (#19840)
Bonus: trim-off all the unnecessary spaces to allow
for real 2048 characters in policies for STS handlers
and re-use the code in all STS handlers.
2024-05-30 11:10:41 -07:00
Harshavardhana 4af31e654b avoid pre-populating buffers for deployments < 32GiB memory (#19839) 2024-05-30 04:58:12 -07:00
Harshavardhana aad50579ba fix: wire up ILM sub-system properly for help (#19836) 2024-05-30 01:14:58 -07:00
Harshavardhana 38d059b0ae fix: single node multi-drive must register local drives properly (#19832)
since #19688 there was a regression introduced during drive
lookups for single node multi-drive setups, drive replacement
would not work correctly without this PR.
2024-05-29 13:12:44 -07:00
Klaus Post bd4eeb4522 Fix flipped EcM, EcN in metadata header (#19831)
Since this is a tuple encoded field we can just flip the struct members.
2024-05-29 12:14:09 -07:00
jiuker 03e3493288 fix: correct parse the tagging error for PostPolicyBucketHandler (#19825) 2024-05-29 11:50:46 -07:00
Harshavardhana 64baedf5a4 fix: hide prefixes for Hadoop properly (#19821) 2024-05-28 15:53:15 -07:00
Minio Trusted 2f64d5f77e Update yaml files to latest version RELEASE.2024-05-28T17-19-04Z 2024-05-28 19:23:04 +00:00
Anis Eleuch f79a4ef4d0 policy: More defensive code validating svc:DurationSeconds (#19820)
This does not fix any current issue, but merging https://github.com/minio/madmin-go/pull/282
can lose the validation of the service account expiration time.

Add more defensive code for now. In the future, we should avoid doing
validation in another library.
2024-05-28 10:19:04 -07:00
Taran Pelkey 2d53854b19 Restrict access keys for users and groups to not allow '=' or ',' (#19749)
* initial commit

* Add UTF check

---------

Co-authored-by: Harshavardhana <harsha@minio.io>
2024-05-28 10:14:16 -07:00
Harshavardhana e5c83535af chore: upgrade deps (#19819)
Signed-off-by: Harshavardhana <harsha@minio.io>
2024-05-28 02:27:44 -07:00
jiuker c904ef966e feat: support tags for PostPolicy upload (#19816) 2024-05-27 21:44:00 -07:00
Minio Trusted 8f266e0772 Update yaml files to latest version RELEASE.2024-05-27T19-17-46Z 2024-05-27 23:52:43 +00:00
Harshavardhana e0fe7cc391 fix: information disclosure bug in preconditions GET (#19810)
precondition check was being honored before, validating
if anonymous access is allowed on the metadata of an
object, leading to metadata disclosure of the following
headers.

```
Last-Modified
Etag
x-amz-version-id
Expires:
Cache-Control:
```

although the information presented is minimal in nature,
and of opaque nature. It still simply discloses that an
object by a specific name exists or not without even having
enough permissions.
2024-05-27 12:17:46 -07:00
Harshavardhana 9d20dec56a Revert "remove dataErrs from er.deleteIfDangling code"
This reverts commit 7d75b1e758.

This fails multipart tests we need this code to handle
existing challenges, so wait for the comprehensive fix.
2024-05-26 11:13:29 -07:00
Harshavardhana 597a785253 fix: authenticate LDAP via actual DN instead of normalized DN (#19805)
fix: authenticate LDAP via actual DN instead of normalized DN

Normalized DN is only for internal representation, not for
external communication, any communication to LDAP must be
based on actual user DN. LDAP servers do not understand
normalized DN.

fixes #19757
2024-05-25 06:43:06 -07:00
Harshavardhana 7d75b1e758 remove dataErrs from er.deleteIfDangling code
avoid this until a comprehensive change is
merged such as https://github.com/minio/minio/pull/19797
2024-05-24 18:20:04 -07:00
Aditya Manthramurthy 5f78691fcf ldap: Add user DN attributes list config param (#19758)
This change uses the updated ldap library in minio/pkg (bumped
up to v3). A new config parameter is added for LDAP configuration to
specify extra user attributes to load from the LDAP server and to store
them as additional claims for the user.

A test is added in sts_handlers.go that shows how to access the LDAP
attributes as a claim.

This is in preparation for adding SSH pubkey authentication to MinIO's SFTP
integration.
2024-05-24 16:05:23 -07:00
Shireesh Anjal a591e06ae5 Add cluster scanner metrics in metrics-v3 (#19517)
endpoint: /minio/metrics/v3/cluster/scanner
metrics:
 - bucket_scans_finished (counter)
 - bucket_scans_started (counter)
 - directories_scanned (counter)
 - last_activity_nano_seconds (gauge)
 - objects_scanned (counter)
 - versions_scanned (counter)
2024-05-24 12:29:25 -07:00
Harshavardhana 443c93c634 compute time spent in ILM properly (#19806) 2024-05-24 12:28:51 -07:00
Shireesh Anjal 5659cddc84 Add cluster config metrics in metrics-v3 (#19507)
endpoint: /minio/metrics/v3/cluster/config
metrics:
- write_quorum
- rrs_parity
- standard_parity
2024-05-24 05:50:46 -07:00
Shireesh Anjal 2a03a34bde Upgrade madmin-go to v3.0.52 (#19798)
This will ensure that content of /proc/cmdline from each server is
captured in the health report.
2024-05-24 05:34:57 -07:00
Shubhendu 1654a9b7e6 Use point in time values for gauge metrics in graphs (#19690)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-05-24 04:11:51 -07:00
Shireesh Anjal 673a521711 Change endpoint of v3 notification metrics (#19804)
from /cluster/notification to /notification
2024-05-24 04:10:24 -07:00
Harshavardhana 2e23076688 move windows runners to in-house (#19800)
GitHub CI runners for windows have gotten very slow,
moving them to our own hosted runners
2024-05-23 15:29:33 -07:00
Klaus Post b92ac55250 Add multipart combination to xl-meta (#19780)
Add combination of multiple parts.

Parts will be reconstructed and saved separately and can manually be combined to the complete object.

Parts will be named `(version_id)-(filename).(partnum).(in)complete`.
2024-05-23 09:37:31 -07:00
Shireesh Anjal 7981509cc8 Add cluster and bucket replication metrics in metrics-v3 (#19546)
endpoint: /minio/metrics/v3/cluster/replication
metrics:
- average_active_workers
- average_queued_bytes
- average_queued_count
- average_transfer_rate
- current_active_workers
- current_transfer_rate
- last_minute_queued_bytes
- last_minute_queued_count
- max_active_workers
- max_queued_bytes
- max_queued_count
- max_transfer_rate
- recent_backlog_count

endpoint: /minio/metrics/v3/api/bucket/replication
metrics:
- last_hour_failed_bytes
- last_hour_failed_count
- last_minute_failed_bytes
- last_minute_failed_count
- latency_ms
- proxied_delete_tagging_requests_total
- proxied_get_requests_failures
- proxied_get_requests_total
- proxied_get_tagging_requests_failures
- proxied_get_tagging_requests_total
- proxied_head_requests_failures
- proxied_head_requests_total
- proxied_put_tagging_requests_failures
- proxied_put_tagging_requests_total
- sent_bytes
- sent_count
- total_failed_bytes
- total_failed_count
- proxied_delete_tagging_requests_failures
2024-05-23 00:41:18 -07:00
Krishnan Parthasarathi 6d5bc045bc Disallow ExpiredObjectAllVersions with object lock (#19792)
Relaxes restrictions on Expiration and NoncurrentVersionExpiration
placed by https://github.com/minio/minio/pull/19785.
ref: https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock-managing.html#object-lock-managing-lifecycle

> Object lifecycle management configurations continue functioning
normally on protected objects, including placing delete markers.
However, a locked version of an object cannot be deleted by a S3
Lifecycle expiration policy. Object Lock is maintained regardless of
the object's storage class and throughout S3 Lifecycle
transitions between storage classes.
2024-05-22 18:12:48 -07:00
Harshavardhana d38e020b29 remove errant logs for disconnected remote (#19793)
Signed-off-by: Harshavardhana <harsha@minio.io>
2024-05-22 18:12:23 -07:00
Poorna 7d29030292 fix list results returned for spark max-keys=2 listing (#19791)
This PR continues fix #19725 for some unhandled cases
2024-05-22 16:16:34 -07:00
Shubhendu 7c7650b7c3 Add sufficient deadlines and countermeasures to handle hung node scenario (#19688)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
Signed-off-by: Harshavardhana <harsha@minio.io>
2024-05-22 16:07:14 -07:00
Harshavardhana ca80eced24 usage of deadline conn at Accept() breaks websocket (#19789)
fortunately not wired up to use, however if anyone
enables deadlines for conn then sporadically MinIO
startups fail.
2024-05-22 10:49:27 -07:00
Anis Eleuch d0e0b81d8e Fix race get/set system/audit targest to avoid race errors (#19790) 2024-05-22 09:23:03 -07:00
jiuker 391baa1c9a test: add reject ilm rule test case (#19788) 2024-05-22 04:26:59 -07:00
Harshavardhana ae14681c3e Revert "Fix two-way stream cancelation and pings (#19763)"
This reverts commit 4d698841f4.
2024-05-22 03:00:00 -07:00
Klaus Post 4d698841f4 Fix two-way stream cancelation and pings (#19763)
Do not log errors on oneway streams when sending ping fails. Instead, cancel the stream.

This also makes sure pings are sent when blocked on sending responses.
2024-05-22 01:25:25 -07:00
jiuker 9906b3ade9 fix: reject ilm rule when bucket LockEnabled (#19785) 2024-05-21 23:50:03 -07:00
Anis Eleuch bf1769d3e0 xl: Avoid marking a drive offline after one part read failure (#19779)
This commit will fix one rare case of a multipart object that
can be read in theory but GetObject API returned an error.

It turned out that a six years old code was marking a drive offline
when the bitrot streaming fails to read a part in a disk with any error.
This can affect reading a subsequent part, though having enough shards,
but unable to construct because one drive was marked offline earlier.

This commit will remove the drive marking offline code. It will also
close the bitrotstreaming reader before marking it as nil.
2024-05-21 07:36:21 -07:00
Harshavardhana 63e1ad9f29 fix: the user-agent for Veeam 2024-05-20 11:54:52 -07:00
Klaus Post 2c7bcee53f Add cross-version remapped merges to xl-meta (#19765)
Adds `-xver` which can be used with `-export` and `-combine` to attempt to combine files across versions if data is suspected to be the same. Overlapping data is compared.

Bonus: Make `inspect` accept wildcards.
2024-05-19 08:31:54 -07:00
Harshavardhana 1fd90c93ff re-use StorageAPI while loading drive formats (#19770)
Bonus: safe settings for deployment ID to avoid races
2024-05-19 01:06:49 -07:00
Poorna e947a844c9 Fix test scripts to use mc ready (#19768) 2024-05-18 11:19:01 -07:00
Poorna 4e2d39293a Fix build script to wait for server to come up (#19767) 2024-05-17 14:43:59 -07:00
Krishnan Parthasarathi 1228d6bf1a Return NumVersions in quorum when available (#19766)
Similar to https://github.com/minio/minio/pull/17925
2024-05-17 13:57:37 -07:00
Shireesh Anjal fc4561c64c Start callhome immediately after enabling (#19764)
Currently, on enabling callhome (or restarting the server), the callhome
job gets scheduled. This means that one has to wait for 24hrs (the
default frequency duration) to see it in action and to figure out if it
is working as expected.

It will be a better user experience to perform the first callhome
execution immediately after enabling it (or on server start if already
enabled).

Also, generate audit event on callhome execution, setting the error
field in case the execution has failed.
2024-05-17 09:53:34 -07:00
Klaus Post 3b7747b42b Tweak multipart uploads (#19756)
* Store ModTime in the upload ID; return it when listing instead of the current time.
* Use this ModTime to expire and skip reading the file info.
* Consistent upload sorting in listing (since it now has the ModTime).
* Exclude healing disks to avoid returning an empty list.
2024-05-17 09:40:09 -07:00
Harshavardhana e432e79324 avoid calling 'admin info' for disk, cpu, net metrics collection (#19762)
resource metrics collection was incorrectly making fan-out
liveness peer calls where it's not needed.
2024-05-17 08:15:13 -07:00
Harshavardhana 08d74819b6 handle racy updates to globalSite config (#19750)
```
==================
WARNING: DATA RACE
Read at 0x0000082be990 by goroutine 205:
  github.com/minio/minio/cmd.setCommonHeaders()

Previous write at 0x0000082be990 by main goroutine:
  github.com/minio/minio/cmd.lookupConfigs()
```
2024-05-16 16:13:47 -07:00
Poorna aa3fde1784 Add ListObjectsV2 unit test (#19753)
for PR: #19725
2024-05-15 20:40:51 -07:00
Harshavardhana 0b3eb7f218 add more deadlines and pass around context under most situations (#19752) 2024-05-15 15:19:00 -07:00
Anis Eleuch 69c9496c71 Upgrade github.com/minio/pkg/v2 and other deps (#19747) 2024-05-15 11:04:40 -07:00
Klaus Post b792b36495 Add Veeam storage class override (#19748)
Recent Veeam is very picky about storage class names. Add `_MINIO_VEEAM_FORCE_SC` env var.

It will override the storage class returned by the storage backend if it is non-standard
and we detect a Veeam client by checking the User Agent.

Applies to HeadObject/GetObject/ListObject*
2024-05-15 11:04:16 -07:00
Harshavardhana d3db7d31a3 fix: add deadlines for all synchronous REST callers (#19741)
add deadlines that can be dynamically changed via
the drive max timeout values.

Bonus: optimize "file not found" case and hung drives/network - circuit break the check and return right
away instead of waiting.
2024-05-15 09:52:29 -07:00
Shireesh Anjal c05ca63158 Fix crash on /minio/metrics/v3?list (#19745)
An unchecked map access was causing panic.
2024-05-15 09:06:35 -07:00
Klaus Post 6d3e0c7db6 Tweak one way stream ping (#19743)
Do not log errors on oneway streams when sending ping fails. Instead cancel the stream.

This also makes sure pings are sent when blocked on sending responses.

I will do a separate PR that includes this and adds pings to two-way streams as well as tests for pings.
2024-05-15 08:39:21 -07:00
Shireesh Anjal 0e59e50b39 Capture ttfb api metrics only for GetObject (#19733)
as that is the only API where the TTFB metric is beneficial, and
capturing this for all APIs exponentially increases the response size in
large clusters.
2024-05-14 23:25:13 -07:00
Klaus Post d4b391de1b Add PutObject Ring Buffer (#19605)
Replace the `io.Pipe` from streamingBitrotWriter -> CreateFile with a fixed size ring buffer.

This will add an output buffer for encoded shards to be written to disk - potentially via RPC.

This will remove blocking when `(*streamingBitrotWriter).Write` is called, and it writes hashes and data.

With current settings, the write looks like this:

```
Outbound
┌───────────────────┐             ┌────────────────┐               ┌───────────────┐                      ┌────────────────┐
│                   │   Parr.     │                │  (http body)  │               │                      │                │
│ Bitrot Hash       │     Write   │      Pipe      │      Read     │  HTTP buffer  │    Write (syscall)   │  TCP Buffer    │
│ Erasure Shard     │ ──────────► │  (unbuffered)  │ ────────────► │   (64K Max)   │ ───────────────────► │    (4MB)       │
│                   │             │                │               │  (io.Copy)    │                      │                │
└───────────────────┘             └────────────────┘               └───────────────┘                      └────────────────┘
```

We write a Hash (32 bytes). Since the pipe is unbuffered, it will block until the 32 bytes have 
been delivered to the TCP buffer, and the next Read hits the Pipe.

Then we write the shard data. This will typically be bigger than 64KB, so it will block until two blocks 
have been read from the pipe.

When we insert a ring buffer:

```
Outbound
┌───────────────────┐             ┌────────────────┐               ┌───────────────┐                      ┌────────────────┐
│                   │             │                │  (http body)  │               │                      │                │
│ Bitrot Hash       │     Write   │  Ring Buffer   │      Read     │  HTTP buffer  │    Write (syscall)   │  TCP Buffer    │
│ Erasure Shard     │ ──────────► │    (2MB)       │ ────────────► │   (64K Max)   │ ───────────────────► │    (4MB)       │
│                   │             │                │               │  (io.Copy)    │                      │                │
└───────────────────┘             └────────────────┘               └───────────────┘                      └────────────────┘
```

The hash+shard will fit within the ring buffer, so writes will not block - but will complete after a 
memcopy. Reads can fill the 64KB buffer if there is data for it.

If the network is congested, the ring buffer will become filled, and all syscalls will be on full buffers.
Only when the ring buffer is filled will erasure coding start blocking.

Since there is always "space" to write output data, we remove the parallel writing since we are 
always writing to memory now, and the goroutine synchronization overhead probably not worth taking. 

If the output were blocked in the existing, we would still wait for it to unblock in parallel write, so it would 
make no difference there - except now the ring buffer smoothes out the load.

There are some micro-optimizations we could look at later. The biggest is that, in most cases, 
we could encode directly to the ring buffer - if we are not at a boundary. Also, "force filling" the 
Read requests (i.e., blocking until a full read can be completed) could be investigated and maybe 
allow concurrent memory on read and write.
2024-05-14 17:11:04 -07:00
Shubhendu de4d3dac00 Added tests for IAM policies for bucket operations (#19734)
* Added tests for bucket access policies

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>

* move to correct category of tests

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>

---------

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-05-14 08:43:07 -07:00
Olli Janatuinen 534e7161df SFTP: Correctly inform client about unsupported commands (#19735) 2024-05-14 03:29:30 -07:00
Harshavardhana 9b219cd646 fix: return quorum based error, temporary failures must be ignored (#19732) 2024-05-14 03:29:17 -07:00
Shireesh Anjal 3bab4822f3 Add logger webhook metrics in metrics-v3 (#19515)
endpoint: /minio/metrics/v3/cluster/webhook
metrics:
- failed_messages (counter)
- online (gauge)
- queue_length (gauge)
- total_messages (counter)
2024-05-14 00:27:33 -07:00
coderwander 3c5f2d8916 fix some typo in struct name comments (#19513)
Signed-off-by: coderwander <770732124@qq.com>
2024-05-14 00:26:50 -07:00
Shireesh Anjal 5808190398 Add more metrics to v3/cluster/erasure-set (#19714)
Metrics being added:

- read_tolerance: No of drive failures that can be tolerated without
  disrupting read operations
- write_tolerance: No of drive failures that can be tolerated without
  disrupting write operations
- read_health: Health of the erasure set in a pool for read operations
  (1=healthy, 0=unhealthy)
- write_health: Health of the erasure set in a pool for write operations
  (1=healthy, 0=unhealthy)
2024-05-14 00:25:56 -07:00
Shireesh Anjal b2a82248b1 Move /system/go to /debug/go (#19707) 2024-05-14 00:25:37 -07:00
dependabot[bot] 4e5fcca8b9 build(deps): bump golang.org/x/net (#23)
Bumps the go_modules group with 1 update in the /docs/debugging/s3-verify directory: [golang.org/x/net](https://github.com/golang/net).


Updates `golang.org/x/net` from 0.24.0 to 0.25.0
- [Commits](https://github.com/golang/net/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-13 10:59:52 -07:00
Klaus Post c36eaedb93 Re-add "Fix incorrect merging of slash-suffixed objects (#19729)
Adds regression test for #19699

Failures are a bit luck based, since it requires objects to be placed on different sets.

However this generates a failure prior to #19699

* Revert "Revert "Fix incorrect merging of slash-suffixed objects (#19699)""

This reverts commit f30417d9a8.

* Don't override when suffix doesn't match. Instead rely on quorum for each.
2024-05-13 09:30:24 -07:00
Poorna 7752b03add optimize max-keys=2 listing for spark workloads (#19725)
to return results appropriately for versioned buckets, especially
when underlying prefixes have been deleted
2024-05-13 07:57:42 -07:00
jiuker 01bfc78535 Optimization: reuse hashedSecret when LookupConfig (#19724) 2024-05-12 22:52:27 -07:00
Shireesh Anjal 074d70112d Consolidate drive health related metrics into single metric (#19706)
Instead of having "online" and "healing" as two metrics, replace with a
single metric "health" which can have following values:

0 = offline
1 = healthy
2 = healing
2024-05-12 10:23:50 -07:00
Harshavardhana e8d14c0d90 verify preconditions during CompleteMultipart (#19713)
Bonus: hold the write lock properly to apply
optimistic concurrency during NewMultipartUpload()
2024-05-10 17:31:22 -07:00
Shireesh Anjal 60d7e8143a Move /cluster/audit to /audit (#19708)
As the audit metrics are server level and not 
overall cluster level.
2024-05-10 07:50:39 -07:00
Klaus Post 9667a170de Add usage cache cleanup and lower forced top compaction (#19719)
Lower forced compaction to 250K entries.

If there is more than 250K entries on the top level force compact it and log an error.
2024-05-10 07:49:50 -07:00
Shubhendu abae30f9e1 Added decom test with KES using sse-s3 and sse-kms (#19695)
Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-05-10 01:24:14 -07:00
Minio Trusted f9311bc9d1 Update yaml files to latest version RELEASE.2024-05-10T01-41-38Z 2024-05-10 02:00:49 +00:00
389 changed files with 15039 additions and 5351 deletions
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
+4 -4
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
@@ -22,7 +21,7 @@ jobs:
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.22.x]
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, Windows]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
@@ -30,14 +29,15 @@ jobs:
go-version: ${{ matrix.go-version }} go-version: ${{ matrix.go-version }}
check-latest: true check-latest: true
- name: Build on ${{ matrix.os }} - name: Build on ${{ matrix.os }}
if: matrix.os == 'windows-latest' if: matrix.os == 'Windows'
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
GO111MODULE: on GO111MODULE: on
run: | run: |
Set-MpPreference -DisableRealtimeMonitoring $true
netsh int ipv4 set dynamicport tcp start=60000 num=61000 netsh int ipv4 set dynamicport tcp start=60000 num=61000
go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe go build --ldflags="-s -w" -o %GOPATH%\bin\minio.exe
go test -v --timeout 50m ./... go test -v --timeout 120m ./...
- name: Build on ${{ matrix.os }} - name: Build on ${{ matrix.os }}
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-latest'
env: env:
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
+1 -2
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
@@ -23,7 +22,7 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Install Helm - name: Install Helm
uses: azure/setup-helm@v3 uses: azure/setup-helm@v4
- name: Run helm lint - name: Run helm lint
run: | run: |
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
+5 -1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
@@ -56,6 +55,11 @@ jobs:
run: | run: |
${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}" ${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "erasure" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
# FIXME: renable this back when we have a valid way to add deadlines for PUT()s (internode CreateFile)
# - name: resiliency
# run: |
# ${GITHUB_WORKSPACE}/.github/workflows/run-mint.sh "resiliency" "minio" "minio123" "${{ steps.vars.outputs.sha_short }}"
- name: The job must cleanup - name: The job must cleanup
if: ${{ always() }} if: ${{ always() }}
run: | run: |
@@ -0,0 +1,78 @@
version: '3.7'
# Settings and configurations that are common for all containers
x-minio-common: &minio-common
image: quay.io/minio/minio:${JOB_NAME}
command: server --console-address ":9001" http://minio{1...4}/rdata{1...2}
expose:
- "9000"
- "9001"
environment:
MINIO_CI_CD: "on"
MINIO_ROOT_USER: "minio"
MINIO_ROOT_PASSWORD: "minio123"
MINIO_KMS_SECRET_KEY: "my-minio-key:OSMM+vkKUTCvQs9YL/CVMIMt43HFhkUpqJxTmGl6rYw="
MINIO_DRIVE_MAX_TIMEOUT: "5s"
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5
# starts 4 docker containers running minio server instances.
# using nginx reverse proxy, load balancing, you can access
# it through port 9000.
services:
minio1:
<<: *minio-common
hostname: minio1
volumes:
- rdata1-1:/rdata1
- rdata1-2:/rdata2
minio2:
<<: *minio-common
hostname: minio2
volumes:
- rdata2-1:/rdata1
- rdata2-2:/rdata2
minio3:
<<: *minio-common
hostname: minio3
volumes:
- rdata3-1:/rdata1
- rdata3-2:/rdata2
minio4:
<<: *minio-common
hostname: minio4
volumes:
- rdata4-1:/rdata1
- rdata4-2:/rdata2
nginx:
image: nginx:1.19.2-alpine
hostname: nginx
volumes:
- ./nginx-4-node.conf:/etc/nginx/nginx.conf:ro
ports:
- "9000:9000"
- "9001:9001"
depends_on:
- minio1
- minio2
- minio3
- minio4
## By default this config uses default local driver,
## For custom volumes replace with volume driver configuration.
volumes:
rdata1-1:
rdata1-2:
rdata2-1:
rdata2-2:
rdata3-1:
rdata3-2:
rdata4-1:
rdata4-2:
+3 -4
View File
@@ -23,10 +23,9 @@ http {
# include /etc/nginx/conf.d/*.conf; # include /etc/nginx/conf.d/*.conf;
upstream minio { upstream minio {
server minio1:9000; server minio1:9000 max_fails=1 fail_timeout=10s;
server minio2:9000; server minio2:9000 max_fails=1 fail_timeout=10s;
server minio3:9000; server minio3:9000 max_fails=1 fail_timeout=10s;
server minio4:9000;
} }
upstream console { upstream console {
+8 -8
View File
@@ -23,14 +23,14 @@ http {
# include /etc/nginx/conf.d/*.conf; # include /etc/nginx/conf.d/*.conf;
upstream minio { upstream minio {
server minio1:9000; server minio1:9000 max_fails=1 fail_timeout=10s;
server minio2:9000; server minio2:9000 max_fails=1 fail_timeout=10s;
server minio3:9000; server minio3:9000 max_fails=1 fail_timeout=10s;
server minio4:9000; server minio4:9000 max_fails=1 fail_timeout=10s;
server minio5:9000; server minio5:9000 max_fails=1 fail_timeout=10s;
server minio6:9000; server minio6:9000 max_fails=1 fail_timeout=10s;
server minio7:9000; server minio7:9000 max_fails=1 fail_timeout=10s;
server minio8:9000; server minio8:9000 max_fails=1 fail_timeout=10s;
} }
upstream console { upstream console {
+4 -4
View File
@@ -23,10 +23,10 @@ http {
# include /etc/nginx/conf.d/*.conf; # include /etc/nginx/conf.d/*.conf;
upstream minio { upstream minio {
server minio1:9000; server minio1:9000 max_fails=1 fail_timeout=10s;
server minio2:9000; server minio2:9000 max_fails=1 fail_timeout=10s;
server minio3:9000; server minio3:9000 max_fails=1 fail_timeout=10s;
server minio4:9000; server minio4:9000 max_fails=1 fail_timeout=10s;
} }
upstream console { upstream console {
+7 -9
View File
@@ -24,8 +24,6 @@ if [ ! -f ./mc ]; then
chmod +x mc chmod +x mc
fi fi
go install -v github.com/minio/minio/docs/debugging/s3-check-md5@latest
export RELEASE=RELEASE.2023-08-29T23-07-35Z export RELEASE=RELEASE.2023-08-29T23-07-35Z
docker-compose -f docker-compose-site1.yaml up -d docker-compose -f docker-compose-site1.yaml up -d
@@ -45,10 +43,10 @@ sleep 30s
sleep 5 sleep 5
s3-check-md5 -h ./s3-check-md5 -h
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l) failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l) failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
if [ $failed_count_site1 -ne 0 ]; then if [ $failed_count_site1 -ne 0 ]; then
echo "failed with multipart on site1 uploads" echo "failed with multipart on site1 uploads"
@@ -64,8 +62,8 @@ fi
sleep 5 sleep 5
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l) failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l) failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
## we do not need to fail here, since we are going to test ## we do not need to fail here, since we are going to test
## upgrading to master, healing and being able to recover ## upgrading to master, healing and being able to recover
@@ -93,8 +91,8 @@ for i in $(seq 1 10); do
./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null ./mc admin heal -r --remove --json site2/ 2>&1 >/dev/null
done done
failed_count_site1=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l) failed_count_site1=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site1-nginx:9001 -bucket testbucket 2>&1 | grep FAILED | wc -l)
failed_count_site2=$(s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l) failed_count_site2=$(./s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://site2-nginx:9002 -bucket testbucket 2>&1 | grep FAILED | wc -l)
if [ $failed_count_site1 -ne 0 ]; then if [ $failed_count_site1 -ne 0 ]; then
echo "failed with multipart on site1 uploads" echo "failed with multipart on site1 uploads"
+6 -1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
@@ -42,6 +41,12 @@ jobs:
sudo sysctl net.ipv6.conf.default.disable_ipv6=0 sudo sysctl net.ipv6.conf.default.disable_ipv6=0
make test-ilm make test-ilm
- name: Test PBAC
run: |
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
make test-pbac
- name: Test Config File - name: Test Config File
run: | run: |
sudo sysctl net.ipv6.conf.all.disable_ipv6=0 sudo sysctl net.ipv6.conf.all.disable_ipv6=0
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
+16 -1
View File
@@ -16,7 +16,7 @@ docker volume rm $(docker volume ls -f dangling=true) || true
cd .github/workflows/mint cd .github/workflows/mint
docker-compose -f minio-${MODE}.yaml up -d docker-compose -f minio-${MODE}.yaml up -d
sleep 30s sleep 1m
docker system prune -f || true docker system prune -f || true
docker volume prune -f || true docker volume prune -f || true
@@ -26,6 +26,9 @@ docker volume rm $(docker volume ls -q -f dangling=true) || true
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2 [ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio2
[ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6 [ "${MODE}" == "pools" ] && docker-compose -f minio-${MODE}.yaml stop minio6
# Pause one node, to check that all S3 calls work while one node goes wrong
[ "${MODE}" == "resiliency" ] && docker-compose -f minio-${MODE}.yaml pause minio4
docker run --rm --net=mint_default \ docker run --rm --net=mint_default \
--name="mint-${MODE}-${JOB_NAME}" \ --name="mint-${MODE}-${JOB_NAME}" \
-e SERVER_ENDPOINT="nginx:9000" \ -e SERVER_ENDPOINT="nginx:9000" \
@@ -35,6 +38,18 @@ docker run --rm --net=mint_default \
-e MINT_MODE="${MINT_MODE}" \ -e MINT_MODE="${MINT_MODE}" \
docker.io/minio/mint:edge docker.io/minio/mint:edge
# FIXME: enable this after fixing aws-sdk-java-v2 tests
# # unpause the node, to check that all S3 calls work while one node goes wrong
# [ "${MODE}" == "resiliency" ] && docker-compose -f minio-${MODE}.yaml unpause minio4
# [ "${MODE}" == "resiliency" ] && docker run --rm --net=mint_default \
# --name="mint-${MODE}-${JOB_NAME}" \
# -e SERVER_ENDPOINT="nginx:9000" \
# -e ACCESS_KEY="${ACCESS_KEY}" \
# -e SECRET_KEY="${SECRET_KEY}" \
# -e ENABLE_HTTPS=0 \
# -e MINT_MODE="${MINT_MODE}" \
# docker.io/minio/mint:edge
docker-compose -f minio-${MODE}.yaml down || true docker-compose -f minio-${MODE}.yaml down || true
sleep 10s sleep 10s
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
permissions: permissions:
contents: read contents: read
-1
View File
@@ -4,7 +4,6 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- next
# This ensures that previous jobs for the PR are canceled when the PR is # This ensures that previous jobs for the PR are canceled when the PR is
# updated. # updated.
+1 -2
View File
@@ -21,8 +21,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.22.3 go-version: 1.22.5
check-latest: true
- name: Get official govulncheck - name: Get official govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@latest run: go install golang.org/x/vuln/cmd/govulncheck@latest
shell: bash shell: bash
+9
View File
@@ -44,3 +44,12 @@ docs/debugging/pprofgoparser/pprofgoparser
docs/debugging/reorder-disks/reorder-disks docs/debugging/reorder-disks/reorder-disks
docs/debugging/populate-hard-links/populate-hardlinks docs/debugging/populate-hard-links/populate-hardlinks
docs/debugging/xattr/xattr docs/debugging/xattr/xattr
hash-set
healing-bin
inspect
pprofgoparser
reorder-disks
s3-check-md5
s3-verify
xattr
xl-meta
+1
View File
@@ -2,6 +2,7 @@
extend-exclude = [ extend-exclude = [
".git/", ".git/",
"docs/", "docs/",
"CREDITS",
] ]
ignore-hidden = false ignore-hidden = false
+2605 -834
View File
File diff suppressed because it is too large Load Diff
+30 -24
View File
@@ -34,20 +34,23 @@ verifiers: lint check-gen
check-gen: ## check for updated autogenerated files check-gen: ## check for updated autogenerated files
@go generate ./... >/dev/null @go generate ./... >/dev/null
@go mod tidy -compat=1.21
@(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false) @(! git diff --name-only | grep '_gen.go$$') || (echo "Non-committed changes in auto-generated code is detected, please commit them to proceed." && false)
@(! git diff --name-only | grep 'go.sum') || (echo "Non-committed changes in auto-generated go.sum is detected, please commit them to proceed." && false)
lint: getdeps ## runs golangci-lint suite of linters lint: getdeps ## runs golangci-lint suite of linters
@echo "Running $@ check" @echo "Running $@ check"
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml @$(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 lint-fix: getdeps ## runs golangci-lint suite of linters with automatic fixes
@echo "Running $@ check" @echo "Running $@ check"
@$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix @$(GOLANGCI) run --build-tags kqueue --timeout=10m --config ./.golangci.yml --fix
check: test check: test
test: verifiers build build-debugging ## builds minio, runs linters, tests test: verifiers build ## builds minio, runs linters, tests
@echo "Running unit tests" @echo "Running unit tests"
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue ./... @MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -v -tags kqueue,dev ./...
test-root-disable: install-race test-root-disable: install-race
@echo "Running minio root lockdown tests" @echo "Running minio root lockdown tests"
@@ -57,12 +60,17 @@ test-ilm: install-race
@echo "Running ILM tests" @echo "Running ILM tests"
@env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh @env bash $(PWD)/docs/bucket/replication/setup_ilm_expiry_replication.sh
test-pbac: install-race
@echo "Running bucket policies tests"
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh
test-decom: install-race test-decom: install-race
@echo "Running minio decom tests" @echo "Running minio decom tests"
@env bash $(PWD)/docs/distributed/decom.sh @env bash $(PWD)/docs/distributed/decom.sh
@env bash $(PWD)/docs/distributed/decom-encrypted.sh @env bash $(PWD)/docs/distributed/decom-encrypted.sh
@env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh @env bash $(PWD)/docs/distributed/decom-encrypted-sse-s3.sh
@env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh @env bash $(PWD)/docs/distributed/decom-compressed-sse-s3.sh
@env bash $(PWD)/docs/distributed/decom-encrypted-kes.sh
test-versioning: install-race test-versioning: install-race
@echo "Running minio versioning tests" @echo "Running minio versioning tests"
@@ -79,13 +87,13 @@ test-race: verifiers build ## builds minio, runs linters, tests (race)
@echo "Running unit tests under -race" @echo "Running unit tests under -race"
@(env bash $(PWD)/buildscripts/race.sh) @(env bash $(PWD)/buildscripts/race.sh)
test-iam: build ## verify IAM (external IDP, etcd backends) test-iam: install-race ## verify IAM (external IDP, etcd backends)
@echo "Running tests for IAM (external IDP, etcd backends)" @echo "Running tests for IAM (external IDP, etcd backends)"
@MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -tags kqueue -v -run TestIAM* ./cmd @MINIO_API_REQUESTS_MAX=10000 CGO_ENABLED=0 go test -timeout 15m -tags kqueue,dev -v -run TestIAM* ./cmd
@echo "Running tests for IAM (external IDP, etcd backends) with -race" @echo "Running tests for IAM (external IDP, etcd backends) with -race"
@MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -race -tags kqueue -v -run TestIAM* ./cmd @MINIO_API_REQUESTS_MAX=10000 GORACE=history_size=7 CGO_ENABLED=1 go test -timeout 15m -race -tags kqueue,dev -v -run TestIAM* ./cmd
test-iam-ldap-upgrade-import: build ## verify IAM (external LDAP IDP) test-iam-ldap-upgrade-import: install-race ## verify IAM (external LDAP IDP)
@echo "Running upgrade tests for IAM (LDAP backend)" @echo "Running upgrade tests for IAM (LDAP backend)"
@env bash $(PWD)/buildscripts/minio-iam-ldap-upgrade-import-test.sh @env bash $(PWD)/buildscripts/minio-iam-ldap-upgrade-import-test.sh
@@ -101,7 +109,10 @@ test-replication-3site:
test-delete-replication: test-delete-replication:
@(env bash $(PWD)/docs/bucket/replication/delete-replication.sh) @(env bash $(PWD)/docs/bucket/replication/delete-replication.sh)
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error ## verify multi site replication test-delete-marker-proxying:
@(env bash $(PWD)/docs/bucket/replication/test_del_marker_proxying.sh)
test-replication: install-race test-replication-2site test-replication-3site test-delete-replication test-sio-error test-delete-marker-proxying ## verify multi site replication
@echo "Running tests for replicating three sites" @echo "Running tests for replicating three sites"
test-site-replication-ldap: install-race ## verify automatic site replication test-site-replication-ldap: install-race ## verify automatic site replication
@@ -122,37 +133,32 @@ test-site-replication-minio: install-race ## verify automatic site replication
@echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site" @echo "Running tests for automatic site replication of SSE-C objects with compression enabled for site"
@(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh) @(env bash $(PWD)/docs/site-replication/run-ssec-object-replication-with-compression.sh)
verify: ## verify minio various setups verify: install-race ## verify minio various setups
@echo "Verifying build with race" @echo "Verifying build with race"
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/verify-build.sh) @(env bash $(PWD)/buildscripts/verify-build.sh)
verify-healing: ## verify healing and replacing disks with minio binary verify-healing: install-race ## verify healing and replacing disks with minio binary
@echo "Verify healing build with race" @echo "Verify healing build with race"
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/verify-healing.sh) @(env bash $(PWD)/buildscripts/verify-healing.sh)
@(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh) @(env bash $(PWD)/buildscripts/verify-healing-empty-erasure-set.sh)
@(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh) @(env bash $(PWD)/buildscripts/heal-inconsistent-versions.sh)
verify-healing-with-root-disks: ## verify healing root disks verify-healing-with-root-disks: install-race ## verify healing root disks
@echo "Verify healing with root drives" @echo "Verify healing with root drives"
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh) @(env bash $(PWD)/buildscripts/verify-healing-with-root-disks.sh)
verify-healing-with-rewrite: ## verify healing to rewrite old xl.meta -> new xl.meta verify-healing-with-rewrite: install-race ## verify healing to rewrite old xl.meta -> new xl.meta
@echo "Verify healing with rewrite" @echo "Verify healing with rewrite"
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/rewrite-old-new.sh) @(env bash $(PWD)/buildscripts/rewrite-old-new.sh)
verify-healing-inconsistent-versions: ## verify resolving inconsistent versions verify-healing-inconsistent-versions: install-race ## verify resolving inconsistent versions
@echo "Verify resolving inconsistent versions build with race" @echo "Verify resolving inconsistent versions build with race"
@GORACE=history_size=7 CGO_ENABLED=1 go build -race -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@(env bash $(PWD)/buildscripts/resolve-right-versions.sh) @(env bash $(PWD)/buildscripts/resolve-right-versions.sh)
build-debugging: build-debugging:
@(env bash $(PWD)/docs/debugging/build.sh) @(env bash $(PWD)/docs/debugging/build.sh)
build: checks ## builds minio to $(PWD) build: checks build-debugging ## builds minio to $(PWD)
@echo "Building minio binary to './minio'" @echo "Building minio binary to './minio'"
@CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @CGO_ENABLED=0 go build -tags kqueue -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@@ -162,9 +168,9 @@ hotfix-vars:
$(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD)) $(eval VERSION := $(shell git describe --tags --abbrev=0).hotfix.$(shell git rev-parse --short HEAD))
hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags hotfix: hotfix-vars clean install ## builds minio binary with hotfix tags
@wget -q -c https://github.com/minio/pkger/releases/download/v2.2.9/pkger_2.2.9_linux_amd64.deb @wget -q -c https://github.com/minio/pkger/releases/download/v2.3.1/pkger_2.3.1_linux_amd64.deb
@wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service @wget -q -c https://raw.githubusercontent.com/minio/minio-service/v1.0.1/linux-systemd/distributed/minio.service
@sudo apt install ./pkger_2.2.9_linux_amd64.deb --yes @sudo apt install ./pkger_2.3.1_linux_amd64.deb --yes
@mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive @mkdir -p minio-release/$(GOOS)-$(GOARCH)/archive
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio @cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio
@cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION) @cp -af ./minio minio-release/$(GOOS)-$(GOARCH)/minio.$(VERSION)
@@ -191,15 +197,15 @@ docker: build ## builds minio docker container
@echo "Building minio docker image '$(TAG)'" @echo "Building minio docker image '$(TAG)'"
@docker build -q --no-cache -t $(TAG) . -f Dockerfile @docker build -q --no-cache -t $(TAG) . -f Dockerfile
install-race: checks ## builds minio to $(PWD) install-race: checks build-debugging ## builds minio to $(PWD)
@echo "Building minio binary with -race to './minio'" @echo "Building minio binary with -race to './minio'"
@GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null @GORACE=history_size=7 CGO_ENABLED=1 go build -tags kqueue,dev -race -trimpath --ldflags "$(LDFLAGS)" -o $(PWD)/minio 1>/dev/null
@echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'" @echo "Installing minio binary with -race to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio @mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
install: build ## builds minio and installs it to $GOPATH/bin. install: build ## builds minio and installs it to $GOPATH/bin.
@echo "Installing minio binary to '$(GOPATH)/bin/minio'" @echo "Installing minio binary to '$(GOPATH)/bin/minio'"
@mkdir -p $(GOPATH)/bin && cp -f $(PWD)/minio $(GOPATH)/bin/minio @mkdir -p $(GOPATH)/bin && cp -af $(PWD)/minio $(GOPATH)/bin/minio
@echo "Installation successful. To learn more, try \"minio --help\"." @echo "Installation successful. To learn more, try \"minio --help\"."
clean: ## cleanup all generated assets clean: ## cleanup all generated assets
Regular → Executable
View File
+8 -5
View File
@@ -32,6 +32,7 @@ fi
set +e set +e
export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/ export MC_HOST_minioadm=http://minioadmin:minioadmin@localhost:9100/
./mc ready minioadm
./mc ls minioadm/ ./mc ls minioadm/
@@ -56,7 +57,7 @@ done
set +e set +e
sleep 10 ./mc ready minioadm/
./mc ls minioadm/ ./mc ls minioadm/
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@@ -81,11 +82,12 @@ minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \ minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 & "http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
sleep 20s
export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001 export MC_HOST_sitea=http://minioadmin:minioadmin@127.0.0.1:9001
export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004 export MC_HOST_siteb=http://minioadmin:minioadmin@127.0.0.1:9004
./mc ready sitea
./mc ready siteb
./mc admin replicate add sitea siteb ./mc admin replicate add sitea siteb
./mc admin user add sitea foobar foo12345 ./mc admin user add sitea foobar foo12345
@@ -109,11 +111,12 @@ minio server --address 127.0.0.1:9003 "http://127.0.0.1:9003/tmp/multisiteb/data
minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \ minio server --address 127.0.0.1:9004 "http://127.0.0.1:9003/tmp/multisiteb/data/disterasure/xl{1...4}" \
"http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 & "http://127.0.0.1:9004/tmp/multisiteb/data/disterasure/xl{5...8}" >/tmp/siteb_2.log 2>&1 &
sleep 20s
export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001 export MC_HOST_sitea=http://foobar:foo12345@127.0.0.1:9001
export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004 export MC_HOST_siteb=http://foobar:foo12345@127.0.0.1:9004
./mc ready sitea
./mc ready siteb
./mc admin user add sitea foobar-admin foo12345 ./mc admin user add sitea foobar-admin foo12345
sleep 2s sleep 2s
@@ -56,6 +56,7 @@ create_iam_content_in_old_minio() {
set -x set -x
mc alias set old-minio http://localhost:9000 minioadmin minioadmin mc alias set old-minio http://localhost:9000 minioadmin minioadmin
mc ready old-minio
mc idp ldap add old-minio \ mc idp ldap add old-minio \
server_addr=localhost:1389 \ server_addr=localhost:1389 \
server_insecure=on \ server_insecure=on \
@@ -87,6 +88,7 @@ import_iam_content_in_new_minio() {
set -x set -x
mc alias set new-minio http://localhost:9000 minioadmin minioadmin mc alias set new-minio http://localhost:9000 minioadmin minioadmin
echo "BEFORE IMPORT mappings:" echo "BEFORE IMPORT mappings:"
mc ready new-minio
mc idp ldap policy entities new-minio mc idp ldap policy entities new-minio
mc admin cluster iam import new-minio ./old-minio-iam-info.zip mc admin cluster iam import new-minio ./old-minio-iam-info.zip
echo "AFTER IMPORT mappings:" echo "AFTER IMPORT mappings:"
Regular → Executable
+18 -4
View File
@@ -4,10 +4,22 @@ trap 'cleanup $LINENO' ERR
# shellcheck disable=SC2120 # shellcheck disable=SC2120
cleanup() { cleanup() {
MINIO_VERSION=dev docker-compose \ MINIO_VERSION=dev /tmp/gopath/bin/docker-compose \
-f "buildscripts/upgrade-tests/compose.yml" \ -f "buildscripts/upgrade-tests/compose.yml" \
rm -s -f down || true
MINIO_VERSION=dev /tmp/gopath/bin/docker-compose \
-f "buildscripts/upgrade-tests/compose.yml" \
rm || true
for volume in $(docker volume ls -q | grep upgrade); do
docker volume rm ${volume} || true
done
docker volume prune -f docker volume prune -f
docker system prune -f || true
docker volume prune -f || true
docker volume rm $(docker volume ls -q -f dangling=true) || true
} }
verify_checksum_after_heal() { verify_checksum_after_heal() {
@@ -60,6 +72,8 @@ __init__() {
go install github.com/docker/compose/v2/cmd@latest go install github.com/docker/compose/v2/cmd@latest
mv -v /tmp/gopath/bin/cmd /tmp/gopath/bin/docker-compose mv -v /tmp/gopath/bin/cmd /tmp/gopath/bin/docker-compose
cleanup
TAG=minio/minio:dev make docker TAG=minio/minio:dev make docker
MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \ MINIO_VERSION=RELEASE.2019-12-19T22-52-26Z docker-compose \
@@ -77,11 +91,11 @@ __init__() {
curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum curl -s http://127.0.0.1:9000/minio-test/to-read/hosts | sha256sum
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop MINIO_VERSION=dev /tmp/gopath/bin/docker-compose -f "buildscripts/upgrade-tests/compose.yml" stop
} }
main() { main() {
MINIO_VERSION=dev docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build MINIO_VERSION=dev /tmp/gopath/bin/docker-compose -f "buildscripts/upgrade-tests/compose.yml" up -d --build
add_alias add_alias
+7 -7
View File
@@ -45,7 +45,8 @@ function verify_rewrite() {
"${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 & "${MINIO_OLD[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$! pid=$!
disown $pid disown $pid
sleep 10
"${WORK_DIR}/mc" ready minio/
if ! ps -p ${pid} 1>&2 >/dev/null; then if ! ps -p ${pid} 1>&2 >/dev/null; then
echo "server1 log:" echo "server1 log:"
@@ -77,7 +78,8 @@ function verify_rewrite() {
"${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 & "${MINIO[@]}" --address ":$start_port" "${WORK_DIR}/xl{1...16}" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$! pid=$!
disown $pid disown $pid
sleep 10
"${WORK_DIR}/mc" ready minio/
if ! ps -p ${pid} 1>&2 >/dev/null; then if ! ps -p ${pid} 1>&2 >/dev/null; then
echo "server1 log:" echo "server1 log:"
@@ -87,14 +89,12 @@ function verify_rewrite() {
exit 1 exit 1
fi fi
go install -v github.com/minio/minio/docs/debugging/s3-check-md5@latest if ! ./s3-check-md5 \
if ! s3-check-md5 \
-debug \ -debug \
-versions \ -versions \
-access-key minio \ -access-key minio \
-secret-key minio123 \ -secret-key minio123 \
-endpoint http://127.0.0.1:${start_port}/ 2>&1 | grep INTACT; then -endpoint "http://127.0.0.1:${start_port}/" 2>&1 | grep INTACT; then
echo "server1 log:" echo "server1 log:"
cat "${WORK_DIR}/server1.log" cat "${WORK_DIR}/server1.log"
echo "FAILED" echo "FAILED"
@@ -114,7 +114,7 @@ function verify_rewrite() {
go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123" go run ./buildscripts/heal-manual.go "127.0.0.1:${start_port}" "minio" "minio123"
sleep 1 sleep 1
if ! s3-check-md5 \ if ! ./s3-check-md5 \
-debug \ -debug \
-versions \ -versions \
-access-key minio \ -access-key minio \
-2
View File
@@ -1,5 +1,3 @@
version: '3.7'
# Settings and configurations that are common for all containers # Settings and configurations that are common for all containers
x-minio-common: &minio-common x-minio-common: &minio-common
image: minio/minio:${MINIO_VERSION} image: minio/minio:${MINIO_VERSION}
+12 -8
View File
@@ -15,13 +15,14 @@ WORK_DIR="$PWD/.verify-$RANDOM"
export MINT_MODE=core export MINT_MODE=core
export MINT_DATA_DIR="$WORK_DIR/data" export MINT_DATA_DIR="$WORK_DIR/data"
export SERVER_ENDPOINT="127.0.0.1:9000" export SERVER_ENDPOINT="127.0.0.1:9000"
export MC_HOST_verify="http://minio:minio123@${SERVER_ENDPOINT}/"
export MC_HOST_verify_ipv6="http://minio:minio123@[::1]:9000/"
export ACCESS_KEY="minio" export ACCESS_KEY="minio"
export SECRET_KEY="minio123" export SECRET_KEY="minio123"
export ENABLE_HTTPS=0 export ENABLE_HTTPS=0
export GO111MODULE=on export GO111MODULE=on
export GOGC=25 export GOGC=25
export ENABLE_ADMIN=1 export ENABLE_ADMIN=1
export MINIO_CI_CD=1 export MINIO_CI_CD=1
MINIO_CONFIG_DIR="$WORK_DIR/.minio" MINIO_CONFIG_DIR="$WORK_DIR/.minio"
@@ -36,18 +37,21 @@ function start_minio_fs() {
export MINIO_ROOT_USER=$ACCESS_KEY export MINIO_ROOT_USER=$ACCESS_KEY
export MINIO_ROOT_PASSWORD=$SECRET_KEY export MINIO_ROOT_PASSWORD=$SECRET_KEY
"${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 & "${MINIO[@]}" server "${WORK_DIR}/fs-disk" >"$WORK_DIR/fs-minio.log" 2>&1 &
sleep 10
"${WORK_DIR}/mc" ready verify
} }
function start_minio_erasure() { function start_minio_erasure() {
"${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 & "${MINIO[@]}" server "${WORK_DIR}/erasure-disk1" "${WORK_DIR}/erasure-disk2" "${WORK_DIR}/erasure-disk3" "${WORK_DIR}/erasure-disk4" >"$WORK_DIR/erasure-minio.log" 2>&1 &
sleep 15
"${WORK_DIR}/mc" ready verify
} }
function start_minio_erasure_sets() { function start_minio_erasure_sets() {
export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}" export MINIO_ENDPOINTS="${WORK_DIR}/erasure-disk-sets{1...32}"
"${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 & "${MINIO[@]}" server >"$WORK_DIR/erasure-minio-sets.log" 2>&1 &
sleep 15
"${WORK_DIR}/mc" ready verify
} }
function start_minio_pool_erasure_sets() { function start_minio_pool_erasure_sets() {
@@ -57,7 +61,7 @@ function start_minio_pool_erasure_sets() {
"${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 & "${MINIO[@]}" server --address ":9000" >"$WORK_DIR/pool-minio-9000.log" 2>&1 &
"${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 & "${MINIO[@]}" server --address ":9001" >"$WORK_DIR/pool-minio-9001.log" 2>&1 &
sleep 40 "${WORK_DIR}/mc" ready verify
} }
function start_minio_pool_erasure_sets_ipv6() { function start_minio_pool_erasure_sets_ipv6() {
@@ -67,7 +71,7 @@ function start_minio_pool_erasure_sets_ipv6() {
"${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 & "${MINIO[@]}" server --address="[::1]:9000" >"$WORK_DIR/pool-minio-ipv6-9000.log" 2>&1 &
"${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 & "${MINIO[@]}" server --address="[::1]:9001" >"$WORK_DIR/pool-minio-ipv6-9001.log" 2>&1 &
sleep 40 "${WORK_DIR}/mc" ready verify_ipv6
} }
function start_minio_dist_erasure() { function start_minio_dist_erasure() {
@@ -78,7 +82,7 @@ function start_minio_dist_erasure() {
"${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 & "${MINIO[@]}" server --address ":900${i}" >"$WORK_DIR/dist-minio-900${i}.log" 2>&1 &
done done
sleep 40 "${WORK_DIR}/mc" ready verify
} }
function run_test_fs() { function run_test_fs() {
@@ -222,7 +226,7 @@ function __init__() {
exit 1 exit 1
fi fi
(cd "${MC_BUILD_DIR}" && go build -o "$WORK_DIR/mc") (cd "${MC_BUILD_DIR}" && go build -o "${WORK_DIR}/mc")
# remove mc source. # remove mc source.
purge "${MC_BUILD_DIR}" purge "${MC_BUILD_DIR}"
@@ -19,7 +19,7 @@ function start_minio_3_node() {
export MINIO_ERASURE_SET_DRIVE_COUNT=6 export MINIO_ERASURE_SET_DRIVE_COUNT=6
export MINIO_CI_CD=1 export MINIO_CI_CD=1
start_port=$2 start_port=$1
args="" args=""
for i in $(seq 1 3); do for i in $(seq 1 3); do
args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/" args="$args http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/1/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/2/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/3/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/4/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/5/ http://127.0.0.1:$((start_port + i))${WORK_DIR}/$i/6/"
@@ -37,40 +37,48 @@ function start_minio_3_node() {
pid3=$! pid3=$!
disown $pid3 disown $pid3
sleep "$1" export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
timeout 15m /tmp/mc ready myminio || fail
# Wait for all drives to be online and formatted
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].state | select(. != "ok")' | wc -l) -gt 0 ]; do sleep 1; done
# Wait for all drives to be healed
while [ $(/tmp/mc admin info --json myminio | jq '.info.servers[].drives[].healing | select(. != null) | select(. == true)' | wc -l) -gt 0 ]; do sleep 1; done
# Wait for Status: in MinIO output
while true; do
rv=$(check_online)
if [ "$rv" != "1" ]; then
# success
break
fi
# Check if we should retry
retry=$((retry + 1))
if [ $retry -le 20 ]; then
sleep 5
continue
fi
# Failure
fail
done
if ! ps -p $pid1 1>&2 >/dev/null; then if ! ps -p $pid1 1>&2 >/dev/null; then
echo "server1 log:" echo "minio-server-1 is not running." && fail
cat "${WORK_DIR}/dist-minio-server1.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
if ! ps -p $pid2 1>&2 >/dev/null; then if ! ps -p $pid2 1>&2 >/dev/null; then
echo "server2 log:" echo "minio-server-2 is not running." && fail
cat "${WORK_DIR}/dist-minio-server2.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
if ! ps -p $pid3 1>&2 >/dev/null; then if ! ps -p $pid3 1>&2 >/dev/null; then
echo "server3 log:" echo "minio-server-3 is not running." && fail
cat "${WORK_DIR}/dist-minio-server3.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
if ! pkill minio; then if ! pkill minio; then
for i in $(seq 1 3); do fail
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
sleep 1 sleep 1
@@ -82,6 +90,16 @@ function start_minio_3_node() {
fi fi
} }
function fail() {
for i in $(seq 1 3); do
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
echo "FAILED"
purge "$WORK_DIR"
exit 1
}
function check_online() { function check_online() {
if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then if ! grep -q 'Status:' ${WORK_DIR}/dist-minio-*.log; then
echo "1" echo "1"
@@ -89,7 +107,7 @@ function check_online() {
} }
function purge() { function purge() {
rm -rf "$1" echo rm -rf "$1"
} }
function __init__() { function __init__() {
@@ -99,10 +117,15 @@ function __init__() {
## version is purposefully set to '3' for minio to migrate configuration file ## version is purposefully set to '3' for minio to migrate configuration file
echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json" echo '{"version": "3", "credential": {"accessKey": "minio", "secretKey": "minio123"}, "region": "us-east-1"}' >"$MINIO_CONFIG_DIR/config.json"
if [ ! -f /tmp/mc ]; then
wget --quiet -O /tmp/mc https://dl.minio.io/client/mc/release/linux-amd64/mc &&
chmod +x /tmp/mc
fi
} }
function perform_test() { function perform_test() {
start_minio_3_node 120 $2 start_minio_3_node $2
echo "Testing Distributed Erasure setup healing of drives" echo "Testing Distributed Erasure setup healing of drives"
echo "Remove the contents of the disks belonging to '${1}' erasure set" echo "Remove the contents of the disks belonging to '${1}' erasure set"
@@ -110,19 +133,7 @@ function perform_test() {
rm -rf ${WORK_DIR}/${1}/*/ rm -rf ${WORK_DIR}/${1}/*/
set -x set -x
start_minio_3_node 120 $2 start_minio_3_node $2
rv=$(check_online)
if [ "$rv" == "1" ]; then
for i in $(seq 1 3); do
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
pkill -9 minio
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi
} }
function main() { function main() {
+27 -39
View File
@@ -15,6 +15,10 @@ MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
GOPATH=/tmp/gopath GOPATH=/tmp/gopath
function start_minio_3_node() { function start_minio_3_node() {
for i in $(seq 1 3); do
rm "${WORK_DIR}/dist-minio-server$i.log"
done
export MINIO_ROOT_USER=minio export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123 export MINIO_ROOT_PASSWORD=minio123
export MINIO_ERASURE_SET_DRIVE_COUNT=6 export MINIO_ERASURE_SET_DRIVE_COUNT=6
@@ -22,7 +26,7 @@ function start_minio_3_node() {
first_time=$(find ${WORK_DIR}/ | grep format.json | wc -l) first_time=$(find ${WORK_DIR}/ | grep format.json | wc -l)
start_port=$2 start_port=$1
args="" args=""
for d in $(seq 1 3 5); do for d in $(seq 1 3 5); do
args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ " args="$args http://127.0.0.1:$((start_port + 1))${WORK_DIR}/1/${d}/ http://127.0.0.1:$((start_port + 2))${WORK_DIR}/2/${d}/ http://127.0.0.1:$((start_port + 3))${WORK_DIR}/3/${d}/ "
@@ -42,42 +46,26 @@ function start_minio_3_node() {
pid3=$! pid3=$!
disown $pid3 disown $pid3
sleep "$1" export MC_HOST_myminio="http://minio:minio123@127.0.0.1:$((start_port + 1))"
timeout 15m /tmp/mc ready myminio || fail
[ ${first_time} -eq 0 ] && upload_objects $start_port [ ${first_time} -eq 0 ] && upload_objects
[ ${first_time} -ne 0 ] && sleep 120
if ! ps -p $pid1 1>&2 >/dev/null; then if ! ps -p $pid1 1>&2 >/dev/null; then
echo "server1 log:" echo "minio server 1 is not running" && fail
cat "${WORK_DIR}/dist-minio-server1.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
if ! ps -p $pid2 1>&2 >/dev/null; then if ! ps -p $pid2 1>&2 >/dev/null; then
echo "server2 log:" echo "minio server 2 is not running" && fail
cat "${WORK_DIR}/dist-minio-server2.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
if ! ps -p $pid3 1>&2 >/dev/null; then if ! ps -p $pid3 1>&2 >/dev/null; then
echo "server3 log:" echo "minio server 3 is not running" && fail
cat "${WORK_DIR}/dist-minio-server3.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
if ! pkill minio; then if ! pkill minio; then
for i in $(seq 1 3); do fail
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
sleep 1 sleep 1
@@ -112,6 +100,17 @@ function purge() {
rm -rf "$1" rm -rf "$1"
} }
function fail() {
for i in $(seq 1 3); do
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
pkill -9 minio
echo "FAILED"
purge "$WORK_DIR"
exit 1
}
function __init__() { function __init__() {
echo "Initializing environment" echo "Initializing environment"
mkdir -p "$WORK_DIR" mkdir -p "$WORK_DIR"
@@ -127,10 +126,6 @@ function __init__() {
} }
function upload_objects() { function upload_objects() {
start_port=$1
/tmp/mc alias set myminio http://127.0.0.1:$((start_port + 1)) minio minio123 --api=s3v4
/tmp/mc ready myminio
/tmp/mc mb myminio/testbucket/ /tmp/mc mb myminio/testbucket/
for ((i = 0; i < 20; i++)); do for ((i = 0; i < 20; i++)); do
echo "my content" | /tmp/mc pipe myminio/testbucket/file-$i echo "my content" | /tmp/mc pipe myminio/testbucket/file-$i
@@ -140,7 +135,7 @@ function upload_objects() {
function perform_test() { function perform_test() {
start_port=$2 start_port=$2
start_minio_3_node 120 $start_port start_minio_3_node $start_port
echo "Testing Distributed Erasure setup healing of drives" echo "Testing Distributed Erasure setup healing of drives"
echo "Remove the contents of the disks belonging to '${1}' node" echo "Remove the contents of the disks belonging to '${1}' node"
@@ -148,19 +143,12 @@ function perform_test() {
rm -rf ${WORK_DIR}/${1}/*/ rm -rf ${WORK_DIR}/${1}/*/
set -x set -x
start_minio_3_node 120 $start_port start_minio_3_node $start_port
check_heal ${1} check_heal ${1}
rv=$? rv=$?
if [ "$rv" == "1" ]; then if [ "$rv" == "1" ]; then
for i in $(seq 1 3); do fail
echo "server$i log:"
cat "${WORK_DIR}/dist-minio-server$i.log"
done
pkill -9 minio
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi fi
} }
+1 -1
View File
@@ -25,7 +25,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// Data types used for returning dummy access control // Data types used for returning dummy access control
+8 -4
View File
@@ -40,7 +40,7 @@ import (
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
const ( const (
@@ -783,7 +783,7 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
} }
switch fileName { switch fileName {
case bucketNotificationConfig: case bucketNotificationConfig:
config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region, globalEventNotifier.targetList) config, err := event.ParseConfig(io.LimitReader(reader, sz), globalSite.Region(), globalEventNotifier.targetList)
if err != nil { if err != nil {
rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err)) rpt.SetStatus(bucket, fileName, fmt.Errorf("%s (%s)", errorCodes[ErrMalformedXML].Description, err))
continue continue
@@ -837,9 +837,13 @@ func (a adminAPIHandlers) ImportBucketMetadataHandler(w http.ResponseWriter, r *
rpt.SetStatus(bucket, fileName, err) rpt.SetStatus(bucket, fileName, err)
continue continue
} }
rcfg, err := globalBucketObjectLockSys.Get(bucket)
if err != nil {
rpt.SetStatus(bucket, fileName, err)
continue
}
// Validate the received bucket policy document // Validate the received bucket policy document
if err = bucketLifecycle.Validate(); err != nil { if err = bucketLifecycle.Validate(rcfg); err != nil {
rpt.SetStatus(bucket, fileName, err) rpt.SetStatus(bucket, fileName, err)
continue continue
} }
+7 -1
View File
@@ -27,7 +27,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// validateAdminReq will validate request against and return whether it is allowed. // validateAdminReq will validate request against and return whether it is allowed.
@@ -216,6 +216,12 @@ func toAdminAPIErr(ctx context.Context, err error) APIError {
Description: err.Error(), Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
} }
case errors.Is(err, errTierInvalidConfig):
apiErr = APIError{
Code: "XMinioAdminTierInvalidConfig",
Description: err.Error(),
HTTPStatusCode: http.StatusBadRequest,
}
default: default:
apiErr = errorCodes.ToAPIErrWithErr(toAdminAPIErrCode(ctx, err), err) apiErr = errorCodes.ToAPIErrWithErr(toAdminAPIErrCode(ctx, err), err)
} }
+1 -1
View File
@@ -37,7 +37,7 @@ import (
"github.com/minio/minio/internal/config/subnet" "github.com/minio/minio/internal/config/subnet"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv // DelConfigKVHandler - DELETE /minio/admin/v3/del-config-kv
+2 -2
View File
@@ -32,8 +32,8 @@ import (
cfgldap "github.com/minio/minio/internal/config/identity/ldap" cfgldap "github.com/minio/minio/internal/config/identity/ldap"
"github.com/minio/minio/internal/config/identity/openid" "github.com/minio/minio/internal/config/identity/openid"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/ldap" "github.com/minio/pkg/v3/ldap"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) { func addOrUpdateIDPHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, isUpdate bool) {
+205 -6
View File
@@ -20,6 +20,7 @@ package cmd
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
@@ -27,7 +28,8 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" xldap "github.com/minio/pkg/v3/ldap"
"github.com/minio/pkg/v3/policy"
) )
// ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies. // ListLDAPPolicyMappingEntities lists users/groups mapped to given/all policies.
@@ -236,12 +238,12 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
targetGroups = requestorGroups targetGroups = requestorGroups
// Deny if the target user is not LDAP // Deny if the target user is not LDAP
foundLDAPDN, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(targetUser) foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(targetUser)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
if foundLDAPDN == "" { if foundResult == nil {
err := errors.New("Specified user does not exist on LDAP server") err := errors.New("Specified user does not exist on LDAP server")
APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err) APIErr := errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err)
writeErrorResponseJSON(ctx, w, APIErr, r.URL) writeErrorResponseJSON(ctx, w, APIErr, r.URL)
@@ -264,7 +266,8 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
isDN := globalIAMSys.LDAPConfig.ParsesAsDN(targetUser) isDN := globalIAMSys.LDAPConfig.ParsesAsDN(targetUser)
opts.claims[ldapUserN] = targetUser // simple username opts.claims[ldapUserN] = targetUser // simple username
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser) var lookupResult *xldap.DNSearchResult
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
if err != nil { if err != nil {
// if not found, check if DN // if not found, check if DN
if strings.Contains(err.Error(), "User DN not found for:") { if strings.Contains(err.Error(), "User DN not found for:") {
@@ -278,7 +281,26 @@ func (a adminAPIHandlers) AddServiceAccountLDAP(w http.ResponseWriter, r *http.R
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
targetUser = lookupResult.NormDN
opts.claims[ldapUser] = targetUser // DN opts.claims[ldapUser] = targetUser // DN
opts.claims[ldapActualUser] = lookupResult.ActualDN
// Check if this user or their groups have a policy applied.
ldapPolicies, err := globalIAMSys.PolicyDBGet(targetUser, targetGroups...)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if len(ldapPolicies) == 0 {
err = fmt.Errorf("No policy set for user `%s` or any of their groups: `%s`", opts.claims[ldapActualUser], strings.Join(targetGroups, "`,`"))
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrAdminNoSuchUser, err), r.URL)
return
}
// Add LDAP attributes that were looked up into the claims.
for attribKey, attribValue := range lookupResult.Attributes {
opts.claims[ldapAttribPrefix+attribKey] = attribValue
}
} }
newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts) newCred, updatedAt, err := globalIAMSys.NewServiceAccount(ctx, targetUser, targetGroups, opts)
@@ -385,15 +407,16 @@ func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Requ
} }
} }
targetAccount, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN) dnResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
if targetAccount == "" { if dnResult == nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errNoSuchUser), r.URL)
return return
} }
targetAccount := dnResult.NormDN
listType := r.Form.Get("listType") listType := r.Form.Get("listType")
if listType != "sts-only" && listType != "svcacc-only" && listType != "" { if listType != "sts-only" && listType != "svcacc-only" && listType != "" {
@@ -456,3 +479,179 @@ func (a adminAPIHandlers) ListAccessKeysLDAP(w http.ResponseWriter, r *http.Requ
writeSuccessResponseJSON(w, encryptedData) writeSuccessResponseJSON(w, encryptedData)
} }
// ListAccessKeysLDAPBulk - GET /minio/admin/v3/idp/ldap/list-access-keys-bulk
func (a adminAPIHandlers) ListAccessKeysLDAPBulk(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Get current object layer instance.
objectAPI := newObjectLayerFn()
if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return
}
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
dnList := r.Form["userDNs"]
isAll := r.Form.Get("all") == "true"
onlySelf := !isAll && len(dnList) == 0
if isAll && len(dnList) > 0 {
// This should be checked on client side, so return generic error
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
return
}
// Empty DN list and not self, list access keys for all users
if isAll {
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.ListUsersAdminAction,
ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner,
Claims: cred.Claims,
}) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
return
}
} else if len(dnList) == 1 {
var dn string
foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(dnList[0])
if err == nil {
dn = foundResult.NormDN
}
if dn == cred.ParentUser || dnList[0] == cred.ParentUser {
onlySelf = true
}
}
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.ListServiceAccountsAdminAction,
ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner,
Claims: cred.Claims,
DenyOnly: onlySelf,
}) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAccessDenied), r.URL)
return
}
if onlySelf && len(dnList) == 0 {
selfDN := cred.AccessKey
if cred.ParentUser != "" {
selfDN = cred.ParentUser
}
dnList = append(dnList, selfDN)
}
accessKeyMap := make(map[string]madmin.ListAccessKeysLDAPResp)
if isAll {
ldapUsers, err := globalIAMSys.ListLDAPUsers(ctx)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
for user := range ldapUsers {
accessKeyMap[user] = madmin.ListAccessKeysLDAPResp{}
}
} else {
for _, userDN := range dnList {
// Validate the userDN
foundResult, err := globalIAMSys.LDAPConfig.GetValidatedDNForUsername(userDN)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if foundResult == nil {
continue
}
accessKeyMap[foundResult.NormDN] = madmin.ListAccessKeysLDAPResp{}
}
}
listType := r.Form.Get("listType")
var listSTSKeys, listServiceAccounts bool
switch listType {
case madmin.AccessKeyListUsersOnly:
listSTSKeys = false
listServiceAccounts = false
case madmin.AccessKeyListSTSOnly:
listSTSKeys = true
listServiceAccounts = false
case madmin.AccessKeyListSvcaccOnly:
listSTSKeys = false
listServiceAccounts = true
case madmin.AccessKeyListAll:
listSTSKeys = true
listServiceAccounts = true
default:
err := errors.New("invalid list type")
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErrWithErr(ErrInvalidRequest, err), r.URL)
return
}
for dn, accessKeys := range accessKeyMap {
if listSTSKeys {
stsKeys, err := globalIAMSys.ListSTSAccounts(ctx, dn)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
for _, sts := range stsKeys {
expiryTime := sts.Expiration
accessKeys.STSKeys = append(accessKeys.STSKeys, madmin.ServiceAccountInfo{
AccessKey: sts.AccessKey,
Expiration: &expiryTime,
})
}
// 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)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
for _, svc := range serviceAccounts {
expiryTime := svc.Expiration
accessKeys.ServiceAccounts = append(accessKeys.ServiceAccounts, madmin.ServiceAccountInfo{
AccessKey: svc.AccessKey,
Expiration: &expiryTime,
})
}
// if only service accounts, skip if user has no service accounts
if !listSTSKeys && len(serviceAccounts) == 0 {
delete(accessKeyMap, dn)
continue
}
}
accessKeyMap[dn] = accessKeys
}
data, err := json.Marshal(accessKeyMap)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
encryptedData, err := madmin.EncryptData(cred.SecretKey, data)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
writeSuccessResponseJSON(w, encryptedData)
}
+3 -2
View File
@@ -27,8 +27,8 @@ import (
"strings" "strings"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
var ( var (
@@ -374,6 +374,7 @@ func (a adminAPIHandlers) RebalanceStop(w http.ResponseWriter, r *http.Request)
globalNotificationSys.StopRebalance(r.Context()) globalNotificationSys.StopRebalance(r.Context())
writeSuccessResponseHeadersOnly(w) writeSuccessResponseHeadersOnly(w)
adminLogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt)) adminLogIf(ctx, pools.saveRebalanceStats(GlobalContext, 0, rebalSaveStoppedAt))
globalNotificationSys.LoadRebalanceMeta(ctx, false)
} }
func proxyDecommissionRequest(ctx context.Context, defaultEndPoint Endpoint, w http.ResponseWriter, r *http.Request) (proxy bool) { func proxyDecommissionRequest(ctx context.Context, defaultEndPoint Endpoint, w http.ResponseWriter, r *http.Request) (proxy bool) {
+1 -1
View File
@@ -33,7 +33,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add // SiteReplicationAdd - PUT /minio/admin/v3/site-replication/add
+7 -4
View File
@@ -32,7 +32,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
minio "github.com/minio/minio-go/v7" minio "github.com/minio/minio-go/v7"
"github.com/minio/pkg/v2/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
) )
func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) { func runAllIAMConcurrencyTests(suite *TestSuiteIAM, c *check) {
@@ -120,9 +120,12 @@ func (s *TestSuiteIAM) TestDeleteUserRace(c *check) {
c.Fatalf("Unable to set user: %v", err) c.Fatalf("Unable to set user: %v", err)
} }
err = s.adm.SetPolicy(ctx, policy, accessKey, false) userReq := madmin.PolicyAssociationReq{
if err != nil { Policies: []string{policy},
c.Fatalf("Unable to set policy: %v", err) User: accessKey,
}
if _, err := s.adm.AttachPolicy(ctx, userReq); err != nil {
c.Fatalf("Unable to attach policy: %v", err)
} }
accessKeys[i] = accessKey accessKeys[i] = accessKey
+41 -14
View File
@@ -29,6 +29,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"time" "time"
"unicode/utf8"
"github.com/klauspost/compress/zip" "github.com/klauspost/compress/zip"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
@@ -36,7 +37,8 @@ import (
"github.com/minio/minio/internal/cachevalue" "github.com/minio/minio/internal/cachevalue"
"github.com/minio/minio/internal/config/dns" "github.com/minio/minio/internal/config/dns"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" xldap "github.com/minio/pkg/v3/ldap"
"github.com/minio/pkg/v3/policy"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )
@@ -473,6 +475,11 @@ func (a adminAPIHandlers) AddUser(w http.ResponseWriter, r *http.Request) {
return return
} }
if !utf8.ValidString(accessKey) {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAddUserValidUTF), r.URL)
return
}
checkDenyOnly := false checkDenyOnly := false
if accessKey == cred.AccessKey { if accessKey == cred.AccessKey {
// Check that there is no explicit deny - otherwise it's allowed // Check that there is no explicit deny - otherwise it's allowed
@@ -700,12 +707,20 @@ func (a adminAPIHandlers) AddServiceAccount(w http.ResponseWriter, r *http.Reque
// In case of LDAP we need to resolve the targetUser to a DN and // In case of LDAP we need to resolve the targetUser to a DN and
// query their groups: // query their groups:
opts.claims[ldapUserN] = targetUser // simple username opts.claims[ldapUserN] = targetUser // simple username
targetUser, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser) var lookupResult *xldap.DNSearchResult
lookupResult, targetGroups, err = globalIAMSys.LDAPConfig.LookupUserDN(targetUser)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
targetUser = lookupResult.NormDN
opts.claims[ldapUser] = targetUser // username DN opts.claims[ldapUser] = targetUser // username DN
opts.claims[ldapActualUser] = lookupResult.ActualDN
// Add LDAP attributes that were looked up into the claims.
for attribKey, attribValue := range lookupResult.Attributes {
opts.claims[ldapAttribPrefix+attribKey] = attribValue
}
// NOTE: if not using LDAP, then internal IDP or open ID is // NOTE: if not using LDAP, then internal IDP or open ID is
// being used - in the former, group info is enforced when // being used - in the former, group info is enforced when
@@ -815,7 +830,11 @@ func (a adminAPIHandlers) UpdateServiceAccount(w http.ResponseWriter, r *http.Re
} }
condValues := getConditionValues(r, "", cred) condValues := getConditionValues(r, "", cred)
addExpirationToCondValues(updateReq.NewExpiration, condValues) err = addExpirationToCondValues(updateReq.NewExpiration, condValues)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
// Permission checks: // Permission checks:
// //
@@ -1636,22 +1655,22 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
// form of the entityName (which will be an LDAP DN). // form of the entityName (which will be an LDAP DN).
var err error var err error
if isGroup { if isGroup {
var foundGroupDN string var foundGroupDN *xldap.DNSearchResult
var underBaseDN bool var underBaseDN bool
if foundGroupDN, underBaseDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil { if foundGroupDN, underBaseDN, err = globalIAMSys.LDAPConfig.GetValidatedGroupDN(nil, entityName); err != nil {
iamLogIf(ctx, err) iamLogIf(ctx, err)
} else if foundGroupDN == "" || !underBaseDN { } else if foundGroupDN == nil || !underBaseDN {
err = errNoSuchGroup err = errNoSuchGroup
} }
entityName = foundGroupDN entityName = foundGroupDN.NormDN
} else { } else {
var foundUserDN string var foundUserDN *xldap.DNSearchResult
if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil { if foundUserDN, err = globalIAMSys.LDAPConfig.GetValidatedDNForUsername(entityName); err != nil {
iamLogIf(ctx, err) iamLogIf(ctx, err)
} else if foundUserDN == "" { } else if foundUserDN == nil {
err = errNoSuchUser err = errNoSuchUser
} }
entityName = foundUserDN entityName = foundUserDN.NormDN
} }
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
@@ -2444,11 +2463,16 @@ func (a adminAPIHandlers) ImportIAM(w http.ResponseWriter, r *http.Request) {
} }
} }
func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) { func addExpirationToCondValues(exp *time.Time, condValues map[string][]string) error {
if exp == nil { if exp == nil || exp.IsZero() || exp.Equal(timeSentinel) {
return return nil
} }
condValues["DurationSeconds"] = []string{strconv.FormatInt(int64(exp.Sub(time.Now()).Seconds()), 10)} dur := exp.Sub(time.Now())
if dur <= 0 {
return errors.New("unsupported expiration time")
}
condValues["DurationSeconds"] = []string{strconv.FormatInt(int64(dur.Seconds()), 10)}
return nil
} }
func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) { func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials, newServiceAccountOpts, madmin.AddServiceAccountReq, string, APIError) {
@@ -2513,7 +2537,10 @@ func commonAddServiceAccount(r *http.Request) (context.Context, auth.Credentials
} }
condValues := getConditionValues(r, "", cred) condValues := getConditionValues(r, "", cred)
addExpirationToCondValues(createReq.Expiration, condValues) err = addExpirationToCondValues(createReq.Expiration, condValues)
if err != nil {
return ctx, auth.Credentials{}, newServiceAccountOpts{}, madmin.AddServiceAccountReq{}, "", toAdminAPIErr(ctx, err)
}
// Check if action is allowed if creating access key for another user // Check if action is allowed if creating access key for another user
// Check if action is explicitly denied if for self // Check if action is explicitly denied if for self
+56 -20
View File
@@ -39,7 +39,7 @@ import (
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio-go/v7/pkg/signer" "github.com/minio/minio-go/v7/pkg/signer"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
) )
const ( const (
@@ -239,9 +239,12 @@ func (s *TestSuiteIAM) TestUserCreate(c *check) {
c.Assert(v.Status, madmin.AccountEnabled) c.Assert(v.Status, madmin.AccountEnabled)
// 3. Associate policy and check that user can access // 3. Associate policy and check that user can access
err = s.adm.SetPolicy(ctx, "readwrite", accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{"readwrite"},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
client := s.getUserClient(c, accessKey, secretKey, "") client := s.getUserClient(c, accessKey, secretKey, "")
@@ -348,9 +351,12 @@ func (s *TestSuiteIAM) TestUserPolicyEscalationBug(c *check) {
if err != nil { if err != nil {
c.Fatalf("policy add error: %v", err) c.Fatalf("policy add error: %v", err)
} }
err = s.adm.SetPolicy(ctx, policy, accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
// 2.3 check user has access to bucket // 2.3 check user has access to bucket
c.mustListObjects(ctx, uClient, bucket) c.mustListObjects(ctx, uClient, bucket)
@@ -470,9 +476,12 @@ func (s *TestSuiteIAM) TestAddServiceAccountPerms(c *check) {
c.mustNotListObjects(ctx, uClient, "testbucket") c.mustNotListObjects(ctx, uClient, "testbucket")
// 3.2 associate policy to user // 3.2 associate policy to user
err = s.adm.SetPolicy(ctx, policy1, accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy1},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
admClnt := s.getAdminClient(c, accessKey, secretKey, "") admClnt := s.getAdminClient(c, accessKey, secretKey, "")
@@ -490,10 +499,22 @@ func (s *TestSuiteIAM) TestAddServiceAccountPerms(c *check) {
c.Fatalf("policy was missing!") c.Fatalf("policy was missing!")
} }
// 3.2 associate policy to user // Detach policy1 to set up for policy2
err = s.adm.SetPolicy(ctx, policy2, accessKey, false) _, err = s.adm.DetachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy1},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to detach policy: %v", err)
}
// 3.2 associate policy to user
_, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy2},
User: accessKey,
})
if err != nil {
c.Fatalf("unable to attach policy: %v", err)
} }
// 3.3 check user can create service account implicitly. // 3.3 check user can create service account implicitly.
@@ -571,9 +592,12 @@ func (s *TestSuiteIAM) TestPolicyCreate(c *check) {
c.mustNotListObjects(ctx, uClient, bucket) c.mustNotListObjects(ctx, uClient, bucket)
// 3.2 associate policy to user // 3.2 associate policy to user
err = s.adm.SetPolicy(ctx, policy, accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
// 3.3 check user has access to bucket // 3.3 check user has access to bucket
c.mustListObjects(ctx, uClient, bucket) c.mustListObjects(ctx, uClient, bucket)
@@ -726,9 +750,12 @@ func (s *TestSuiteIAM) TestGroupAddRemove(c *check) {
c.mustNotListObjects(ctx, uClient, bucket) c.mustNotListObjects(ctx, uClient, bucket)
// 3. Associate policy to group and check user got access. // 3. Associate policy to group and check user got access.
err = s.adm.SetPolicy(ctx, policy, group, true) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy},
Group: group,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
// 3.1 check user has access to bucket // 3.1 check user has access to bucket
c.mustListObjects(ctx, uClient, bucket) c.mustListObjects(ctx, uClient, bucket)
@@ -871,9 +898,12 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByUser(c *check) {
c.Fatalf("Unable to set user: %v", err) c.Fatalf("Unable to set user: %v", err)
} }
err = s.adm.SetPolicy(ctx, policy, accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
// Create an madmin client with user creds // Create an madmin client with user creds
@@ -952,9 +982,12 @@ func (s *TestSuiteIAM) TestServiceAccountDurationSecondsCondition(c *check) {
c.Fatalf("Unable to set user: %v", err) c.Fatalf("Unable to set user: %v", err)
} }
err = s.adm.SetPolicy(ctx, policy, accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
// Create an madmin client with user creds // Create an madmin client with user creds
@@ -1031,9 +1064,12 @@ func (s *TestSuiteIAM) TestServiceAccountOpsByAdmin(c *check) {
c.Fatalf("Unable to set user: %v", err) c.Fatalf("Unable to set user: %v", err)
} }
err = s.adm.SetPolicy(ctx, policy, accessKey, false) _, err = s.adm.AttachPolicy(ctx, madmin.PolicyAssociationReq{
Policies: []string{policy},
User: accessKey,
})
if err != nil { if err != nil {
c.Fatalf("Unable to set policy: %v", err) c.Fatalf("unable to attach policy: %v", err)
} }
// 1. Create a service account for the user // 1. Create a service account for the user
+13 -9
View File
@@ -59,9 +59,9 @@ import (
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/logger/message/log" "github.com/minio/pkg/v3/logger/message/log"
xnet "github.com/minio/pkg/v2/net" xnet "github.com/minio/pkg/v3/net"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
"github.com/secure-io/sio-go" "github.com/secure-io/sio-go"
"github.com/zeebo/xxh3" "github.com/zeebo/xxh3"
) )
@@ -237,6 +237,7 @@ func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.R
if globalIsDistErasure { if globalIsDistErasure {
// Notify all other MinIO peers signal service. // Notify all other MinIO peers signal service.
startTime := time.Now().Add(restartUpdateDelay)
ng := WithNPeers(len(globalNotificationSys.peerClients)) ng := WithNPeers(len(globalNotificationSys.peerClients))
for idx, client := range globalNotificationSys.peerClients { for idx, client := range globalNotificationSys.peerClients {
_, ok := failedClients[idx] _, ok := failedClients[idx]
@@ -247,7 +248,7 @@ func (a adminAPIHandlers) ServerUpdateV2Handler(w http.ResponseWriter, r *http.R
ng.Go(ctx, func() error { ng.Go(ctx, func() error {
prs, ok := peerResults[client.String()] prs, ok := peerResults[client.String()]
if ok && prs.CurrentVersion != prs.UpdatedVersion && prs.UpdatedVersion != "" { if ok && prs.CurrentVersion != prs.UpdatedVersion && prs.UpdatedVersion != "" {
return client.SignalService(serviceRestart, "", dryRun) return client.SignalService(serviceRestart, "", dryRun, &startTime)
} }
return nil return nil
}, idx, *client.host) }, idx, *client.host)
@@ -542,9 +543,12 @@ func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Reques
} }
var objectAPI ObjectLayer var objectAPI ObjectLayer
var execAt *time.Time
switch serviceSig { switch serviceSig {
case serviceRestart: case serviceRestart:
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceRestartAdminAction)
t := time.Now().Add(restartUpdateDelay)
execAt = &t
case serviceStop: case serviceStop:
objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction) objectAPI, _ = validateAdminReq(ctx, w, r, policy.ServiceStopAdminAction)
case serviceFreeze, serviceUnFreeze: case serviceFreeze, serviceUnFreeze:
@@ -571,7 +575,7 @@ func (a adminAPIHandlers) ServiceV2Handler(w http.ResponseWriter, r *http.Reques
} }
if globalIsDistErasure { if globalIsDistErasure {
for _, nerr := range globalNotificationSys.SignalServiceV2(serviceSig, dryRun) { for _, nerr := range globalNotificationSys.SignalServiceV2(serviceSig, dryRun, execAt) {
if nerr.Err != nil && process { if nerr.Err != nil && process {
waitingDrives := map[string]madmin.DiskMetrics{} waitingDrives := map[string]madmin.DiskMetrics{}
jerr := json.Unmarshal([]byte(nerr.Err.Error()), &waitingDrives) jerr := json.Unmarshal([]byte(nerr.Err.Error()), &waitingDrives)
@@ -1431,7 +1435,7 @@ func getAggregatedBackgroundHealState(ctx context.Context, o ObjectLayer) (madmi
if globalIsDistErasure { if globalIsDistErasure {
// Get heal status from other peers // Get heal status from other peers
peersHealStates, nerrs := globalNotificationSys.BackgroundHealStatus() peersHealStates, nerrs := globalNotificationSys.BackgroundHealStatus(ctx)
var errCount int var errCount int
for _, nerr := range nerrs { for _, nerr := range nerrs {
if nerr.Err != nil { if nerr.Err != nil {
@@ -2347,7 +2351,7 @@ func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) ma
notifyTarget := fetchLambdaInfo() notifyTarget := fetchLambdaInfo()
local := getLocalServerProperty(globalEndpoints, r, metrics) local := getLocalServerProperty(globalEndpoints, r, metrics)
servers := globalNotificationSys.ServerInfo(metrics) servers := globalNotificationSys.ServerInfo(ctx, metrics)
servers = append(servers, local) servers = append(servers, local)
var poolsInfo map[int]map[int]madmin.ErasureSetInfo var poolsInfo map[int]map[int]madmin.ErasureSetInfo
@@ -2416,8 +2420,8 @@ func getServerInfo(ctx context.Context, pools, metrics bool, r *http.Request) ma
return madmin.InfoMessage{ return madmin.InfoMessage{
Mode: string(mode), Mode: string(mode),
Domain: domain, Domain: domain,
Region: globalSite.Region, Region: globalSite.Region(),
SQSARN: globalEventNotifier.GetARNList(false), SQSARN: globalEventNotifier.GetARNList(),
DeploymentID: globalDeploymentID(), DeploymentID: globalDeploymentID(),
Buckets: buckets, Buckets: buckets,
Objects: objects, Objects: objects,
+6 -2
View File
@@ -301,8 +301,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
// LDAP specific service accounts ops // LDAP specific service accounts ops
adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp/ldap/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccountLDAP)) adminRouter.Methods(http.MethodPut).Path(adminVersion + "/idp/ldap/add-service-account").HandlerFunc(adminMiddleware(adminAPI.AddServiceAccountLDAP))
adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys"). adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys").
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)). HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAP)).Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}")
Queries("userDN", "{userDN:.*}", "listType", "{listType:.*}") adminRouter.Methods(http.MethodGet).Path(adminVersion+"/idp/ldap/list-access-keys-bulk").
HandlerFunc(adminMiddleware(adminAPI.ListAccessKeysLDAPBulk)).Queries("listType", "{listType:.*}")
// LDAP IAM operations // LDAP IAM operations
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities)) adminRouter.Methods(http.MethodGet).Path(adminVersion + "/idp/ldap/policy-entities").HandlerFunc(adminMiddleware(adminAPI.ListLDAPPolicyMappingEntities))
@@ -340,6 +341,9 @@ func registerAdminRouter(router *mux.Router, enableConfigOps bool) {
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc( adminRouter.Methods(http.MethodGet).Path(adminVersion + "/list-jobs").HandlerFunc(
adminMiddleware(adminAPI.ListBatchJobs)) adminMiddleware(adminAPI.ListBatchJobs))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/status-job").HandlerFunc(
adminMiddleware(adminAPI.BatchJobStatus))
adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc( adminRouter.Methods(http.MethodGet).Path(adminVersion + "/describe-job").HandlerFunc(
adminMiddleware(adminAPI.DescribeBatchJob)) adminMiddleware(adminAPI.DescribeBatchJob))
adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/cancel-job").HandlerFunc( adminRouter.Methods(http.MethodDelete).Path(adminVersion + "/cancel-job").HandlerFunc(
+6 -4
View File
@@ -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 // This file is part of MinIO Object Storage stack
// //
@@ -18,7 +18,6 @@
package cmd package cmd
import ( import (
"context"
"math" "math"
"net/http" "net/http"
"os" "os"
@@ -31,6 +30,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
xnet "github.com/minio/pkg/v3/net"
) )
// getLocalServerProperty - returns madmin.ServerProperties for only the // getLocalServerProperty - returns madmin.ServerProperties for only the
@@ -64,9 +64,11 @@ func getLocalServerProperty(endpointServerPools EndpointServerPools, r *http.Req
if err := isServerResolvable(endpoint, 5*time.Second); err == nil { if err := isServerResolvable(endpoint, 5*time.Second); err == nil {
network[nodeName] = string(madmin.ItemOnline) network[nodeName] = string(madmin.ItemOnline)
} else { } else {
if xnet.IsNetworkOrHostDown(err, false) {
network[nodeName] = string(madmin.ItemOffline) network[nodeName] = string(madmin.ItemOffline)
// log once the error } else if xnet.IsNetworkOrHostDown(err, true) {
peersLogOnceIf(context.Background(), err, nodeName) network[nodeName] = "connection attempt timedout"
}
} }
} }
} }
+1 -1
View File
@@ -67,7 +67,7 @@ type ObjectToDelete struct {
ReplicateDecisionStr string `xml:"-"` ReplicateDecisionStr string `xml:"-"`
} }
// createBucketConfiguration container for bucket configuration request from client. // createBucketLocationConfiguration container for bucket configuration request from client.
// Used for parsing the location from the request body for Makebucket. // Used for parsing the location from the request body for Makebucket.
type createBucketLocationConfiguration struct { type createBucketLocationConfiguration struct {
XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"` XMLName xml.Name `xml:"CreateBucketConfiguration" json:"-"`
+28 -5
View File
@@ -48,7 +48,7 @@ import (
levent "github.com/minio/minio/internal/config/lambda/event" levent "github.com/minio/minio/internal/config/lambda/event"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// APIError structure // APIError structure
@@ -287,6 +287,7 @@ const (
ErrAdminNoSuchGroup ErrAdminNoSuchGroup
ErrAdminGroupNotEmpty ErrAdminGroupNotEmpty
ErrAdminGroupDisabled ErrAdminGroupDisabled
ErrAdminInvalidGroupName
ErrAdminNoSuchJob ErrAdminNoSuchJob
ErrAdminNoSuchPolicy ErrAdminNoSuchPolicy
ErrAdminPolicyChangeAlreadyApplied ErrAdminPolicyChangeAlreadyApplied
@@ -425,6 +426,7 @@ const (
ErrAdminProfilerNotEnabled ErrAdminProfilerNotEnabled
ErrInvalidDecompressedSize ErrInvalidDecompressedSize
ErrAddUserInvalidArgument ErrAddUserInvalidArgument
ErrAddUserValidUTF
ErrAdminResourceInvalidArgument ErrAdminResourceInvalidArgument
ErrAdminAccountNotEligible ErrAdminAccountNotEligible
ErrAccountNotEligible ErrAccountNotEligible
@@ -443,6 +445,8 @@ const (
ErrAdminNoAccessKey ErrAdminNoAccessKey
ErrAdminNoSecretKey ErrAdminNoSecretKey
ErrIAMNotInitialized
apiErrCodeEnd // This is used only for the testing code apiErrCodeEnd // This is used only for the testing code
) )
@@ -456,9 +460,9 @@ func (e errorCodeMap) ToAPIErrWithErr(errCode APIErrorCode, err error) APIError
if err != nil { if err != nil {
apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err) apiErr.Description = fmt.Sprintf("%s (%s)", apiErr.Description, err)
} }
if globalSite.Region != "" { if region := globalSite.Region(); region != "" {
if errCode == ErrAuthorizationHeaderMalformed { if errCode == ErrAuthorizationHeaderMalformed {
apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region) apiErr.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", region)
return apiErr return apiErr
} }
} }
@@ -965,7 +969,7 @@ var errorCodes = errorCodeMap{
ErrReplicationRemoteConnectionError: { ErrReplicationRemoteConnectionError: {
Code: "XMinioAdminReplicationRemoteConnectionError", Code: "XMinioAdminReplicationRemoteConnectionError",
Description: "Remote service connection error", Description: "Remote service connection error",
HTTPStatusCode: http.StatusNotFound, HTTPStatusCode: http.StatusServiceUnavailable,
}, },
ErrReplicationBandwidthLimitError: { ErrReplicationBandwidthLimitError: {
Code: "XMinioAdminReplicationBandwidthLimitError", Code: "XMinioAdminReplicationBandwidthLimitError",
@@ -1303,6 +1307,11 @@ var errorCodes = errorCodeMap{
Description: "Server not initialized yet, please try again.", Description: "Server not initialized yet, please try again.",
HTTPStatusCode: http.StatusServiceUnavailable, HTTPStatusCode: http.StatusServiceUnavailable,
}, },
ErrIAMNotInitialized: {
Code: "XMinioIAMNotInitialized",
Description: "IAM sub-system not initialized yet, please try again.",
HTTPStatusCode: http.StatusServiceUnavailable,
},
ErrBucketMetadataNotInitialized: { ErrBucketMetadataNotInitialized: {
Code: "XMinioBucketMetadataNotInitialized", Code: "XMinioBucketMetadataNotInitialized",
Description: "Bucket metadata not initialized yet, please try again.", Description: "Bucket metadata not initialized yet, please try again.",
@@ -2101,6 +2110,16 @@ var errorCodes = errorCodeMap{
Description: "Expected LDAP short username but was given full DN.", Description: "Expected LDAP short username but was given full DN.",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrAdminInvalidGroupName: {
Code: "XMinioInvalidGroupName",
Description: "The group name is invalid.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrAddUserValidUTF: {
Code: "XMinioInvalidUTF",
Description: "Invalid UTF-8 character detected.",
HTTPStatusCode: http.StatusBadRequest,
},
} }
// toAPIErrorCode - Converts embedded errors. Convenience // toAPIErrorCode - Converts embedded errors. Convenience
@@ -2140,6 +2159,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrAdminNoSuchGroup apiErr = ErrAdminNoSuchGroup
case errGroupNotEmpty: case errGroupNotEmpty:
apiErr = ErrAdminGroupNotEmpty apiErr = ErrAdminGroupNotEmpty
case errGroupNameContainsReservedChars:
apiErr = ErrAdminInvalidGroupName
case errNoSuchJob: case errNoSuchJob:
apiErr = ErrAdminNoSuchJob apiErr = ErrAdminNoSuchJob
case errNoPolicyToAttachOrDetach: case errNoPolicyToAttachOrDetach:
@@ -2154,6 +2175,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
apiErr = ErrEntityTooSmall apiErr = ErrEntityTooSmall
case errAuthentication: case errAuthentication:
apiErr = ErrAccessDenied apiErr = ErrAccessDenied
case auth.ErrContainsReservedChars:
apiErr = ErrAdminInvalidAccessKey
case auth.ErrInvalidAccessKeyLength: case auth.ErrInvalidAccessKeyLength:
apiErr = ErrAdminInvalidAccessKey apiErr = ErrAdminInvalidAccessKey
case auth.ErrInvalidSecretKeyLength: case auth.ErrInvalidSecretKeyLength:
@@ -2579,7 +2602,7 @@ func getAPIErrorResponse(ctx context.Context, err APIError, resource, requestID,
BucketName: reqInfo.BucketName, BucketName: reqInfo.BucketName,
Key: reqInfo.ObjectName, Key: reqInfo.ObjectName,
Resource: resource, Resource: resource,
Region: globalSite.Region, Region: globalSite.Region(),
RequestID: requestID, RequestID: requestID,
HostID: hostID, HostID: hostID,
ActualObjectSize: err.ObjectSize, ActualObjectSize: err.ObjectSize,
+5 -4
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"encoding/xml" "encoding/xml"
"fmt" "fmt"
@@ -53,7 +54,7 @@ func setCommonHeaders(w http.ResponseWriter) {
// Set `x-amz-bucket-region` only if region is set on the server // Set `x-amz-bucket-region` only if region is set on the server
// by default minio uses an empty region. // by default minio uses an empty region.
if region := globalSite.Region; region != "" { if region := globalSite.Region(); region != "" {
w.Header().Set(xhttp.AmzBucketRegion, region) w.Header().Set(xhttp.AmzBucketRegion, region)
} }
w.Header().Set(xhttp.AcceptRanges, "bytes") w.Header().Set(xhttp.AcceptRanges, "bytes")
@@ -107,7 +108,7 @@ func setPartsCountHeaders(w http.ResponseWriter, objInfo ObjectInfo) {
} }
// Write object header // Write object header
func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) { func setObjectHeaders(ctx context.Context, w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSpec, opts ObjectOptions) (err error) {
// set common headers // set common headers
setCommonHeaders(w) setCommonHeaders(w)
@@ -135,7 +136,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
// Set tag count if object has tags // Set tag count if object has tags
if len(objInfo.UserTags) > 0 { if len(objInfo.UserTags) > 0 {
tags, _ := tags.ParseObjectTags(objInfo.UserTags) tags, _ := tags.ParseObjectTags(objInfo.UserTags)
if tags.Count() > 0 { if tags != nil && tags.Count() > 0 {
w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())} w.Header()[xhttp.AmzTagCount] = []string{strconv.Itoa(tags.Count())}
if opts.Tagging { if opts.Tagging {
// This is MinIO only extension to return back tags along with the count. // This is MinIO only extension to return back tags along with the count.
@@ -212,7 +213,7 @@ func setObjectHeaders(w http.ResponseWriter, objInfo ObjectInfo, rs *HTTPRangeSp
if objInfo.IsRemote() { if objInfo.IsRemote() {
// Check if object is being restored. For more information on x-amz-restore header see // Check if object is being restored. For more information on x-amz-restore header see
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax // https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html#API_HeadObject_ResponseSyntax
w.Header()[xhttp.AmzStorageClass] = []string{objInfo.TransitionedObject.Tier} w.Header()[xhttp.AmzStorageClass] = []string{filterStorageClass(ctx, objInfo.TransitionedObject.Tier)}
} }
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil { if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil {
+24 -14
View File
@@ -35,7 +35,7 @@ import (
"github.com/minio/minio/internal/hash" "github.com/minio/minio/internal/hash"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
xxml "github.com/minio/xxml" xxml "github.com/minio/xxml"
) )
@@ -544,7 +544,7 @@ func cleanReservedKeys(metadata map[string]string) map[string]string {
} }
// generates an ListBucketVersions response for the said bucket with other enumerated options. // generates an ListBucketVersions response for the said bucket with other enumerated options.
func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse { func generateListVersionsResponse(ctx context.Context, bucket, prefix, marker, versionIDMarker, delimiter, encodingType string, maxKeys int, resp ListObjectVersionsInfo, metadata metaCheckFn) ListVersionsResponse {
versions := make([]ObjectVersion, 0, len(resp.Objects)) versions := make([]ObjectVersion, 0, len(resp.Objects))
owner := &Owner{ owner := &Owner{
@@ -573,7 +573,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
} }
content.Size = object.Size content.Size = object.Size
if object.StorageClass != "" { if object.StorageClass != "" {
content.StorageClass = object.StorageClass content.StorageClass = filterStorageClass(ctx, object.StorageClass)
} else { } else {
content.StorageClass = globalMinioDefaultStorageClass content.StorageClass = globalMinioDefaultStorageClass
} }
@@ -634,7 +634,7 @@ func generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delim
} }
// generates an ListObjectsV1 response for the said bucket with other enumerated options. // generates an ListObjectsV1 response for the said bucket with other enumerated options.
func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse { func generateListObjectsV1Response(ctx context.Context, bucket, prefix, marker, delimiter, encodingType string, maxKeys int, resp ListObjectsInfo) ListObjectsResponse {
contents := make([]Object, 0, len(resp.Objects)) contents := make([]Object, 0, len(resp.Objects))
owner := &Owner{ owner := &Owner{
ID: globalMinioDefaultOwnerID, ID: globalMinioDefaultOwnerID,
@@ -654,7 +654,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
} }
content.Size = object.Size content.Size = object.Size
if object.StorageClass != "" { if object.StorageClass != "" {
content.StorageClass = object.StorageClass content.StorageClass = filterStorageClass(ctx, object.StorageClass)
} else { } else {
content.StorageClass = globalMinioDefaultStorageClass content.StorageClass = globalMinioDefaultStorageClass
} }
@@ -683,7 +683,7 @@ func generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingTy
} }
// generates an ListObjectsV2 response for the said bucket with other enumerated options. // generates an ListObjectsV2 response for the said bucket with other enumerated options.
func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response { func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, nextToken, startAfter, delimiter, encodingType string, fetchOwner, isTruncated bool, maxKeys int, objects []ObjectInfo, prefixes []string, metadata metaCheckFn) ListObjectsV2Response {
contents := make([]Object, 0, len(objects)) contents := make([]Object, 0, len(objects))
var owner *Owner var owner *Owner
if fetchOwner { if fetchOwner {
@@ -707,7 +707,7 @@ func generateListObjectsV2Response(bucket, prefix, token, nextToken, startAfter,
} }
content.Size = object.Size content.Size = object.Size
if object.StorageClass != "" { if object.StorageClass != "" {
content.StorageClass = object.StorageClass content.StorageClass = filterStorageClass(ctx, object.StorageClass)
} else { } else {
content.StorageClass = globalMinioDefaultStorageClass content.StorageClass = globalMinioDefaultStorageClass
} }
@@ -789,8 +789,8 @@ func generateInitiateMultipartUploadResponse(bucket, key, uploadID string) Initi
} }
// generates CompleteMultipartUploadResponse for given bucket, key, location and ETag. // generates CompleteMultipartUploadResponse for given bucket, key, location and ETag.
func generateCompleteMultpartUploadResponse(bucket, key, location string, oi ObjectInfo) CompleteMultipartUploadResponse { func generateCompleteMultipartUploadResponse(bucket, key, location string, oi ObjectInfo, h http.Header) CompleteMultipartUploadResponse {
cs := oi.decryptChecksums(0) cs := oi.decryptChecksums(0, h)
c := CompleteMultipartUploadResponse{ c := CompleteMultipartUploadResponse{
Location: location, Location: location,
Bucket: bucket, Bucket: bucket,
@@ -946,17 +946,27 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
// writeErrorResponse writes error headers // writeErrorResponse writes error headers
func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) { func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
if err.HTTPStatusCode == http.StatusServiceUnavailable { switch err.HTTPStatusCode {
// Set retry-after header to indicate user-agents to retry request after 120secs. case http.StatusServiceUnavailable:
// Set retry-after header to indicate user-agents to retry request after 60 seconds.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
w.Header().Set(xhttp.RetryAfter, "120") w.Header().Set(xhttp.RetryAfter, "60")
case http.StatusTooManyRequests:
_, deadline := globalAPIConfig.getRequestsPool()
if deadline <= 0 {
// Set retry-after header to indicate user-agents to retry request after 10 seconds.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
w.Header().Set(xhttp.RetryAfter, "10")
} else {
w.Header().Set(xhttp.RetryAfter, strconv.Itoa(int(deadline.Seconds())))
}
} }
switch err.Code { switch err.Code {
case "InvalidRegion": case "InvalidRegion":
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region) err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region())
case "AuthorizationHeaderMalformed": case "AuthorizationHeaderMalformed":
err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region) err.Description = fmt.Sprintf("The authorization header is malformed; the region is wrong; expecting '%s'.", globalSite.Region())
} }
// Similar check to http.checkWriteHeaderCode // Similar check to http.checkWriteHeaderCode
+4 -4
View File
@@ -24,7 +24,7 @@ import (
consoleapi "github.com/minio/console/api" consoleapi "github.com/minio/console/api"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/wildcard" "github.com/minio/pkg/v3/wildcard"
"github.com/rs/cors" "github.com/rs/cors"
) )
@@ -64,7 +64,7 @@ func setObjectLayer(o ObjectLayer) {
globalObjLayerMutex.Unlock() globalObjLayerMutex.Unlock()
} }
// objectAPIHandler implements and provides http handlers for S3 API. // objectAPIHandlers implements and provides http handlers for S3 API.
type objectAPIHandlers struct { type objectAPIHandlers struct {
ObjectAPI func() ObjectLayer ObjectAPI func() ObjectLayer
} }
@@ -436,7 +436,7 @@ func registerAPIRouter(router *mux.Router) {
Queries("notification", "") Queries("notification", "")
// ListenNotification // ListenNotification
router.Methods(http.MethodGet). router.Methods(http.MethodGet).
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag)). HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag, traceHdrsS3HFlag)).
Queries("events", "{events:.*}") Queries("events", "{events:.*}")
// ResetBucketReplicationStatus - MinIO extension API // ResetBucketReplicationStatus - MinIO extension API
router.Methods(http.MethodGet). router.Methods(http.MethodGet).
@@ -615,7 +615,7 @@ func registerAPIRouter(router *mux.Router) {
// ListenNotification // ListenNotification
apiRouter.Methods(http.MethodGet).Path(SlashSeparator). apiRouter.Methods(http.MethodGet).Path(SlashSeparator).
HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag)). HandlerFunc(s3APIMiddleware(api.ListenNotificationHandler, noThrottleS3HFlag, traceHdrsS3HFlag)).
Queries("events", "{events:.*}") Queries("events", "{events:.*}")
// ListBuckets // ListBuckets
+145 -142
View File
File diff suppressed because one or more lines are too long
+6 -6
View File
@@ -41,7 +41,7 @@ import (
xjwt "github.com/minio/minio/internal/jwt" xjwt "github.com/minio/minio/internal/jwt"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mcontext" "github.com/minio/minio/internal/mcontext"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// Verify if request has JWT. // Verify if request has JWT.
@@ -178,7 +178,7 @@ func validateAdminSignature(ctx context.Context, r *http.Request, region string)
logger.GetReqInfo(ctx).Cred = cred logger.GetReqInfo(ctx).Cred = cred
logger.GetReqInfo(ctx).Owner = owner logger.GetReqInfo(ctx).Owner = owner
logger.GetReqInfo(ctx).Region = globalSite.Region logger.GetReqInfo(ctx).Region = globalSite.Region()
return cred, owner, ErrNone return cred, owner, ErrNone
} }
@@ -368,7 +368,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
} }
cred, owner, s3Err = getReqAccessKeyV2(r) cred, owner, s3Err = getReqAccessKeyV2(r)
case authTypeSigned, authTypePresigned: case authTypeSigned, authTypePresigned:
region := globalSite.Region region := globalSite.Region()
switch action { switch action {
case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction: case policy.GetBucketLocationAction, policy.ListAllMyBucketsAction:
region = "" region = ""
@@ -384,7 +384,7 @@ func authenticateRequest(ctx context.Context, r *http.Request, action policy.Act
logger.GetReqInfo(ctx).Cred = cred logger.GetReqInfo(ctx).Cred = cred
logger.GetReqInfo(ctx).Owner = owner logger.GetReqInfo(ctx).Owner = owner
logger.GetReqInfo(ctx).Region = globalSite.Region logger.GetReqInfo(ctx).Region = globalSite.Region()
// region is valid only for CreateBucketAction. // region is valid only for CreateBucketAction.
var region string var region string
@@ -684,7 +684,7 @@ func validateSignature(atype authType, r *http.Request) (auth.Credentials, bool,
} }
cred, owner, s3Err = getReqAccessKeyV2(r) cred, owner, s3Err = getReqAccessKeyV2(r)
case authTypePresigned, authTypeSigned: case authTypePresigned, authTypeSigned:
region := globalSite.Region region := globalSite.Region()
if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone { if s3Err = isReqAuthenticated(GlobalContext, r, region, serviceS3); s3Err != ErrNone {
return cred, owner, s3Err return cred, owner, s3Err
} }
@@ -745,7 +745,7 @@ func isPutRetentionAllowed(bucketName, objectName string, retDays int, retDate t
func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) { func isPutActionAllowed(ctx context.Context, atype authType, bucketName, objectName string, r *http.Request, action policy.Action) (s3Err APIErrorCode) {
var cred auth.Credentials var cred auth.Credentials
var owner bool var owner bool
region := globalSite.Region region := globalSite.Region()
switch atype { switch atype {
case authTypeUnknown: case authTypeUnknown:
return ErrSignatureVersionNotSupported return ErrSignatureVersionNotSupported
+3 -3
View File
@@ -28,7 +28,7 @@ import (
"time" "time"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
type nullReader struct{} type nullReader struct{}
@@ -403,7 +403,7 @@ func TestIsReqAuthenticated(t *testing.T) {
// Validates all testcases. // Validates all testcases.
for i, testCase := range testCases { for i, testCase := range testCases {
s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region, serviceS3) s3Error := isReqAuthenticated(ctx, testCase.req, globalSite.Region(), serviceS3)
if s3Error != testCase.s3Error { if s3Error != testCase.s3Error {
if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error { if _, err := io.ReadAll(testCase.req.Body); toAPIErrorCode(ctx, err) != testCase.s3Error {
t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code) t.Fatalf("Test %d: Unexpected S3 error: want %d - got %d (got after reading request %s)", i, testCase.s3Error, s3Error, toAPIError(ctx, err).Code)
@@ -443,7 +443,7 @@ func TestCheckAdminRequestAuthType(t *testing.T) {
{Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied}, {Request: mustNewPresignedRequest(http.MethodGet, "http://127.0.0.1:9000", 0, nil, t), ErrCode: ErrAccessDenied},
} }
for i, testCase := range testCases { for i, testCase := range testCases {
if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region); s3Error != testCase.ErrCode { if _, s3Error := checkAdminRequestAuth(ctx, testCase.Request, policy.AllAdminActions, globalSite.Region()); s3Error != testCase.ErrCode {
t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error) t.Errorf("Test %d: Unexpected s3error returned wanted %d, got %d", i, testCase.ErrCode, s3Error)
} }
} }
+1 -1
View File
@@ -25,7 +25,7 @@ import (
"time" "time"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
) )
// healTask represents what to heal along with options // healTask represents what to heal along with options
+39 -9
View File
@@ -33,7 +33,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
) )
const ( const (
@@ -88,6 +88,8 @@ type healingTracker struct {
ItemsSkipped uint64 ItemsSkipped uint64
BytesSkipped uint64 BytesSkipped uint64
RetryAttempts uint64
// Add future tracking capabilities // Add future tracking capabilities
// Be sure that they are included in toHealingDisk // Be sure that they are included in toHealingDisk
} }
@@ -141,14 +143,14 @@ func initHealingTracker(disk StorageAPI, healID string) *healingTracker {
return h return h
} }
func (h healingTracker) getLastUpdate() time.Time { func (h *healingTracker) getLastUpdate() time.Time {
h.mu.RLock() h.mu.RLock()
defer h.mu.RUnlock() defer h.mu.RUnlock()
return h.LastUpdate return h.LastUpdate
} }
func (h healingTracker) getBucket() string { func (h *healingTracker) getBucket() string {
h.mu.RLock() h.mu.RLock()
defer h.mu.RUnlock() defer h.mu.RUnlock()
@@ -162,7 +164,7 @@ func (h *healingTracker) setBucket(bucket string) {
h.Bucket = bucket h.Bucket = bucket
} }
func (h healingTracker) getObject() string { func (h *healingTracker) getObject() string {
h.mu.RLock() h.mu.RLock()
defer h.mu.RUnlock() defer h.mu.RUnlock()
@@ -363,7 +365,7 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
localDrives := cloneDrives(globalLocalDrives) localDrives := cloneDrives(globalLocalDrives)
globalLocalDrivesMu.RUnlock() globalLocalDrivesMu.RUnlock()
for _, disk := range localDrives { for _, disk := range localDrives {
_, err := disk.GetDiskID() _, err := disk.DiskInfo(context.Background(), DiskInfoOptions{})
if errors.Is(err, errUnformattedDisk) { if errors.Is(err, errUnformattedDisk) {
disksToHeal = append(disksToHeal, disk.Endpoint()) disksToHeal = append(disksToHeal, disk.Endpoint())
continue continue
@@ -382,6 +384,8 @@ func getLocalDisksToHeal() (disksToHeal Endpoints) {
var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second) var newDiskHealingTimeout = newDynamicTimeout(30*time.Second, 10*time.Second)
var errRetryHealing = errors.New("some items failed to heal, we will retry healing this drive again")
func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error { func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint) error {
poolIdx, setIdx := endpoint.PoolIdx, endpoint.SetIdx poolIdx, setIdx := endpoint.PoolIdx, endpoint.SetIdx
disk := getStorageViaEndpoint(endpoint) disk := getStorageViaEndpoint(endpoint)
@@ -389,6 +393,17 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
return fmt.Errorf("Unexpected error disk must be initialized by now after formatting: %s", endpoint) return fmt.Errorf("Unexpected error disk must be initialized by now after formatting: %s", endpoint)
} }
_, err := disk.DiskInfo(ctx, DiskInfoOptions{})
if err != nil {
if errors.Is(err, errDriveIsRoot) {
// This is a root drive, ignore and move on
return nil
}
if !errors.Is(err, errUnformattedDisk) {
return err
}
}
// Prevent parallel erasure set healing // Prevent parallel erasure set healing
locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx)) locker := z.NewNSLock(minioMetaBucket, fmt.Sprintf("new-drive-healing/%d/%d", poolIdx, setIdx))
lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout) lkctx, err := locker.GetLock(ctx, newDiskHealingTimeout)
@@ -451,12 +466,27 @@ func healFreshDisk(ctx context.Context, z *erasureServerPools, endpoint Endpoint
return err return err
} }
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d, failed: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed) // if objects have failed healing, we attempt a retry to heal the drive upto 3 times before giving up.
if tracker.ItemsFailed > 0 && tracker.RetryAttempts < 4 {
tracker.RetryAttempts++
bugLogIf(ctx, tracker.update(ctx))
if len(tracker.QueuedBuckets) > 0 { healingLogEvent(ctx, "Healing of drive '%s' is incomplete, retrying %s time (healed: %d, skipped: %d, failed: %d).", disk,
return fmt.Errorf("not all buckets were healed: %v", tracker.QueuedBuckets) humanize.Ordinal(int(tracker.RetryAttempts)), tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
return errRetryHealing
} }
if tracker.ItemsFailed > 0 {
healingLogEvent(ctx, "Healing of drive '%s' is incomplete, retried %d times (healed: %d, skipped: %d, failed: %d).", disk,
tracker.RetryAttempts-1, tracker.ItemsHealed, tracker.ItemsSkipped, tracker.ItemsFailed)
} else {
if tracker.RetryAttempts > 0 {
healingLogEvent(ctx, "Healing of drive '%s' is complete, retried %d times (healed: %d, skipped: %d).", disk,
tracker.RetryAttempts-1, tracker.ItemsHealed, tracker.ItemsSkipped)
} else {
healingLogEvent(ctx, "Healing of drive '%s' is finished (healed: %d, skipped: %d).", disk, tracker.ItemsHealed, tracker.ItemsSkipped)
}
}
if serverDebugLog { if serverDebugLog {
tracker.printTo(os.Stdout) tracker.printTo(os.Stdout)
fmt.Printf("\n") fmt.Printf("\n")
@@ -528,7 +558,7 @@ func monitorLocalDisksAndHeal(ctx context.Context, z *erasureServerPools) {
if err := healFreshDisk(ctx, z, disk); err != nil { if err := healFreshDisk(ctx, z, disk); err != nil {
globalBackgroundHealState.setDiskHealingStatus(disk, false) globalBackgroundHealState.setDiskHealingStatus(disk, false)
timedout := OperationTimedOut{} timedout := OperationTimedOut{}
if !errors.Is(err, context.Canceled) && !errors.As(err, &timedout) { if !errors.Is(err, context.Canceled) && !errors.As(err, &timedout) && !errors.Is(err, errRetryHealing) {
printEndpointError(disk, err, false) printEndpointError(disk, err, false)
} }
return return
+30 -5
View File
@@ -200,6 +200,12 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "BytesSkipped") err = msgp.WrapError(err, "BytesSkipped")
return return
} }
case "RetryAttempts":
z.RetryAttempts, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "RetryAttempts")
return
}
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -213,9 +219,9 @@ func (z *healingTracker) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) { func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 25 // map header, size 26
// write "ID" // write "ID"
err = en.Append(0xde, 0x0, 0x19, 0xa2, 0x49, 0x44) err = en.Append(0xde, 0x0, 0x1a, 0xa2, 0x49, 0x44)
if err != nil { if err != nil {
return return
} }
@@ -478,15 +484,25 @@ func (z *healingTracker) EncodeMsg(en *msgp.Writer) (err error) {
err = msgp.WrapError(err, "BytesSkipped") err = msgp.WrapError(err, "BytesSkipped")
return return
} }
// write "RetryAttempts"
err = en.Append(0xad, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.RetryAttempts)
if err != nil {
err = msgp.WrapError(err, "RetryAttempts")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) { func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 25 // map header, size 26
// string "ID" // string "ID"
o = append(o, 0xde, 0x0, 0x19, 0xa2, 0x49, 0x44) o = append(o, 0xde, 0x0, 0x1a, 0xa2, 0x49, 0x44)
o = msgp.AppendString(o, z.ID) o = msgp.AppendString(o, z.ID)
// string "PoolIndex" // string "PoolIndex"
o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78) o = append(o, 0xa9, 0x50, 0x6f, 0x6f, 0x6c, 0x49, 0x6e, 0x64, 0x65, 0x78)
@@ -566,6 +582,9 @@ func (z *healingTracker) MarshalMsg(b []byte) (o []byte, err error) {
// string "BytesSkipped" // string "BytesSkipped"
o = append(o, 0xac, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64) o = append(o, 0xac, 0x42, 0x79, 0x74, 0x65, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x70, 0x65, 0x64)
o = msgp.AppendUint64(o, z.BytesSkipped) o = msgp.AppendUint64(o, z.BytesSkipped)
// string "RetryAttempts"
o = append(o, 0xad, 0x52, 0x65, 0x74, 0x72, 0x79, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x73)
o = msgp.AppendUint64(o, z.RetryAttempts)
return return
} }
@@ -763,6 +782,12 @@ func (z *healingTracker) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "BytesSkipped") err = msgp.WrapError(err, "BytesSkipped")
return return
} }
case "RetryAttempts":
z.RetryAttempts, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "RetryAttempts")
return
}
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -785,6 +810,6 @@ func (z *healingTracker) Msgsize() (s int) {
for za0002 := range z.HealedBuckets { for za0002 := range z.HealedBuckets {
s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002]) s += msgp.StringPrefixSize + len(z.HealedBuckets[za0002])
} }
s += 7 + msgp.StringPrefixSize + len(z.HealID) + 13 + msgp.Uint64Size + 13 + msgp.Uint64Size s += 7 + msgp.StringPrefixSize + len(z.HealID) + 13 + msgp.Uint64Size + 13 + msgp.Uint64Size + 14 + msgp.Uint64Size
return return
} }
+8 -8
View File
@@ -33,9 +33,9 @@ import (
"github.com/minio/minio/internal/bucket/versioning" "github.com/minio/minio/internal/bucket/versioning"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v2/wildcard" "github.com/minio/pkg/v3/wildcard"
"github.com/minio/pkg/v2/workers" "github.com/minio/pkg/v3/workers"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@@ -432,7 +432,7 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
}) })
if err != nil { if err != nil {
stopFn(exp, err) stopFn(exp, err)
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", toExpire[i].Bucket, toExpire[i].Name, toExpire[i].VersionID, err, attempts)) batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s due to %v (attempts=%d)", exp.Bucket, exp.Name, err, attempts))
} else { } else {
stopFn(exp, err) stopFn(exp, err)
success = true success = true
@@ -464,13 +464,13 @@ func batchObjsForDelete(ctx context.Context, r *BatchJobExpire, ri *batchJobInfo
copy(toDelCopy, toDel) copy(toDelCopy, toDel)
var failed int var failed int
errs := r.Expire(ctx, api, vc, toDel) errs := r.Expire(ctx, api, vc, toDel)
// reslice toDel in preparation for next retry // reslice toDel in preparation for next retry attempt
// attempt
toDel = toDel[:0] toDel = toDel[:0]
for i, err := range errs { for i, err := range errs {
if err != nil { if err != nil {
stopFn(toDelCopy[i], err) stopFn(toDelCopy[i], err)
batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID, err, attempts)) batchLogIf(ctx, fmt.Errorf("Failed to expire %s/%s versionID=%s due to %v (attempts=%d)", ri.Bucket, toDelCopy[i].ObjectName, toDelCopy[i].VersionID,
err, attempts))
failed++ failed++
if oi, ok := oiCache.Get(toDelCopy[i]); ok { if oi, ok := oiCache.Get(toDelCopy[i]); ok {
ri.trackCurrentBucketObject(r.Bucket, *oi, false, attempts) ri.trackCurrentBucketObject(r.Bucket, *oi, false, attempts)
@@ -514,7 +514,7 @@ func (r *BatchJobExpire) Start(ctx context.Context, api ObjectLayer, job BatchJo
JobType: string(job.Type()), JobType: string(job.Type()),
StartTime: job.Started, StartTime: job.Started,
} }
if err := ri.load(ctx, api, job); err != nil { if err := ri.loadOrInit(ctx, api, job); err != nil {
return err return err
} }
+194 -49
View File
@@ -28,6 +28,7 @@ import (
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
@@ -48,15 +49,20 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/ioutil" "github.com/minio/minio/internal/ioutil"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/pkg/v2/console" "github.com/minio/pkg/v3/console"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
"github.com/minio/pkg/v2/workers" "github.com/minio/pkg/v3/workers"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var globalBatchConfig batch.Config var globalBatchConfig batch.Config
const (
// Keep the completed/failed job stats 3 days before removing it
oldJobsExpiration = 3 * 24 * time.Hour
)
// BatchJobRequest this is an internal data structure not for external consumption. // BatchJobRequest this is an internal data structure not for external consumption.
type BatchJobRequest struct { type BatchJobRequest struct {
ID string `yaml:"-" json:"name"` ID string `yaml:"-" json:"name"`
@@ -262,7 +268,7 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
JobType: string(job.Type()), JobType: string(job.Type()),
StartTime: job.Started, StartTime: job.Started,
} }
if err := ri.load(ctx, api, job); err != nil { if err := ri.loadOrInit(ctx, api, job); err != nil {
return err return err
} }
if ri.Complete { if ri.Complete {
@@ -722,54 +728,43 @@ const (
batchReplJobDefaultRetryDelay = 250 * time.Millisecond batchReplJobDefaultRetryDelay = 250 * time.Millisecond
) )
func getJobReportPath(job BatchJobRequest) string {
var fileName string
switch {
case job.Replicate != nil:
fileName = batchReplName
case job.KeyRotate != nil:
fileName = batchKeyRotationName
case job.Expire != nil:
fileName = batchExpireName
}
return pathJoin(batchJobReportsPrefix, job.ID, fileName)
}
func getJobPath(job BatchJobRequest) string { func getJobPath(job BatchJobRequest) string {
return pathJoin(batchJobPrefix, job.ID) return pathJoin(batchJobPrefix, job.ID)
} }
func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobRequest) error { func (ri *batchJobInfo) getJobReportPath() (string, error) {
var format, version uint16 var fileName string
switch { switch madmin.BatchJobType(ri.JobType) {
case job.Replicate != nil: case madmin.BatchJobReplicate:
version = batchReplVersionV1 fileName = batchReplName
format = batchReplFormat case madmin.BatchJobKeyRotate:
case job.KeyRotate != nil: fileName = batchKeyRotationName
version = batchKeyRotateVersionV1 case madmin.BatchJobExpire:
format = batchKeyRotationFormat fileName = batchExpireName
case job.Expire != nil:
version = batchExpireVersionV1
format = batchExpireFormat
default: default:
return errors.New("no supported batch job request specified") return "", fmt.Errorf("unknown job type: %v", ri.JobType)
} }
data, err := readConfig(ctx, api, getJobReportPath(job)) return pathJoin(batchJobReportsPrefix, ri.JobID, fileName), nil
if err != nil { }
if errors.Is(err, errConfigNotFound) || isErrObjectNotFound(err) {
ri.Version = int(version) func (ri *batchJobInfo) loadOrInit(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
err := ri.load(ctx, api, job)
if errors.Is(err, errNoSuchJob) {
switch { switch {
case job.Replicate != nil: case job.Replicate != nil:
ri.Version = batchReplVersionV1
ri.RetryAttempts = batchReplJobDefaultRetries ri.RetryAttempts = batchReplJobDefaultRetries
if job.Replicate.Flags.Retry.Attempts > 0 { if job.Replicate.Flags.Retry.Attempts > 0 {
ri.RetryAttempts = job.Replicate.Flags.Retry.Attempts ri.RetryAttempts = job.Replicate.Flags.Retry.Attempts
} }
case job.KeyRotate != nil: case job.KeyRotate != nil:
ri.Version = batchKeyRotateVersionV1
ri.RetryAttempts = batchKeyRotateJobDefaultRetries ri.RetryAttempts = batchKeyRotateJobDefaultRetries
if job.KeyRotate.Flags.Retry.Attempts > 0 { if job.KeyRotate.Flags.Retry.Attempts > 0 {
ri.RetryAttempts = job.KeyRotate.Flags.Retry.Attempts ri.RetryAttempts = job.KeyRotate.Flags.Retry.Attempts
} }
case job.Expire != nil: case job.Expire != nil:
ri.Version = batchExpireVersionV1
ri.RetryAttempts = batchExpireJobDefaultRetries ri.RetryAttempts = batchExpireJobDefaultRetries
if job.Expire.Retry.Attempts > 0 { if job.Expire.Retry.Attempts > 0 {
ri.RetryAttempts = job.Expire.Retry.Attempts ri.RetryAttempts = job.Expire.Retry.Attempts
@@ -778,6 +773,39 @@ func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobR
return nil return nil
} }
return err return err
}
func (ri *batchJobInfo) load(ctx context.Context, api ObjectLayer, job BatchJobRequest) error {
path, err := job.getJobReportPath()
if err != nil {
batchLogIf(ctx, err)
return err
}
return ri.loadByPath(ctx, api, path)
}
func (ri *batchJobInfo) loadByPath(ctx context.Context, api ObjectLayer, path string) error {
var format, version uint16
switch filepath.Base(path) {
case batchReplName:
version = batchReplVersionV1
format = batchReplFormat
case batchKeyRotationName:
version = batchKeyRotateVersionV1
format = batchKeyRotationFormat
case batchExpireName:
version = batchExpireVersionV1
format = batchExpireFormat
default:
return errors.New("no supported batch job request specified")
}
data, err := readConfig(ctx, api, path)
if err != nil {
if errors.Is(err, errConfigNotFound) || isErrObjectNotFound(err) {
return errNoSuchJob
}
return err
} }
if len(data) == 0 { if len(data) == 0 {
// Seems to be empty create a new batchRepl object. // Seems to be empty create a new batchRepl object.
@@ -919,7 +947,12 @@ func (ri *batchJobInfo) updateAfter(ctx context.Context, api ObjectLayer, durati
if err != nil { if err != nil {
return err return err
} }
return saveConfig(ctx, api, getJobReportPath(job), buf) path, err := ri.getJobReportPath()
if err != nil {
batchLogIf(ctx, err)
return err
}
return saveConfig(ctx, api, path, buf)
} }
ri.mu.Unlock() ri.mu.Unlock()
return nil return nil
@@ -944,8 +977,10 @@ func (ri *batchJobInfo) trackCurrentBucketObject(bucket string, info ObjectInfo,
ri.mu.Lock() ri.mu.Lock()
defer ri.mu.Unlock() defer ri.mu.Unlock()
if success {
ri.Bucket = bucket ri.Bucket = bucket
ri.Object = info.Name ri.Object = info.Name
}
ri.countItem(info.Size, info.DeleteMarker, success, attempt) ri.countItem(info.Size, info.DeleteMarker, success, attempt)
} }
@@ -971,7 +1006,7 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
JobType: string(job.Type()), JobType: string(job.Type()),
StartTime: job.Started, StartTime: job.Started,
} }
if err := ri.load(ctx, api, job); err != nil { if err := ri.loadOrInit(ctx, api, job); err != nil {
return err return err
} }
if ri.Complete { if ri.Complete {
@@ -1071,6 +1106,10 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID) c.SetAppInfo("minio-"+batchJobPrefix, r.APIVersion+" "+job.ID)
retryAttempts := ri.RetryAttempts
retry := false
for attempts := 1; attempts <= retryAttempts; attempts++ {
attempts := attempts
var ( var (
walkCh = make(chan itemOrErr[ObjectInfo], 100) walkCh = make(chan itemOrErr[ObjectInfo], 100)
slowCh = make(chan itemOrErr[ObjectInfo], 100) slowCh = make(chan itemOrErr[ObjectInfo], 100)
@@ -1145,12 +1184,6 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
if walkQuorum == "" { if walkQuorum == "" {
walkQuorum = "strict" walkQuorum = "strict"
} }
retryAttempts := ri.RetryAttempts
retry := false
for attempts := 1; attempts <= retryAttempts; attempts++ {
attempts := attempts
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
// one of source/target is s3, skip delete marker and all versions under the same object name. // one of source/target is s3, skip delete marker and all versions under the same object name.
s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3 s3Type := r.Target.Type == BatchJobReplicateResourceS3 || r.Source.Type == BatchJobReplicateResourceS3
@@ -1436,10 +1469,24 @@ func (j BatchJobRequest) Validate(ctx context.Context, o ObjectLayer) error {
} }
func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) { func (j BatchJobRequest) delete(ctx context.Context, api ObjectLayer) {
deleteConfig(ctx, api, getJobReportPath(j))
deleteConfig(ctx, api, getJobPath(j)) deleteConfig(ctx, api, getJobPath(j))
} }
func (j BatchJobRequest) getJobReportPath() (string, error) {
var fileName string
switch {
case j.Replicate != nil:
fileName = batchReplName
case j.KeyRotate != nil:
fileName = batchKeyRotationName
case j.Expire != nil:
fileName = batchExpireName
default:
return "", errors.New("unknown job type")
}
return pathJoin(batchJobReportsPrefix, j.ID, fileName), nil
}
func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error { func (j *BatchJobRequest) save(ctx context.Context, api ObjectLayer) error {
if j.Replicate == nil && j.KeyRotate == nil && j.Expire == nil { if j.Replicate == nil && j.KeyRotate == nil && j.Expire == nil {
return errInvalidArgument return errInvalidArgument
@@ -1476,7 +1523,7 @@ func (j *BatchJobRequest) load(ctx context.Context, api ObjectLayer, name string
func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) { func batchReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts miniogo.PutObjectOptions, err error) {
// TODO: support custom storage class for remote replication // TODO: support custom storage class for remote replication
putOpts, err = putReplicationOpts(ctx, "", objInfo) putOpts, err = putReplicationOpts(ctx, "", objInfo, 0)
if err != nil { if err != nil {
return putOpts, err return putOpts, err
} }
@@ -1542,6 +1589,55 @@ func (a adminAPIHandlers) ListBatchJobs(w http.ResponseWriter, r *http.Request)
batchLogIf(ctx, json.NewEncoder(w).Encode(&listResult)) batchLogIf(ctx, json.NewEncoder(w).Encode(&listResult))
} }
// BatchJobStatus - returns the status of a batch job saved in the disk
func (a adminAPIHandlers) BatchJobStatus(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ListBatchJobsAction)
if objectAPI == nil {
return
}
jobID := r.Form.Get("jobId")
if jobID == "" {
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errInvalidArgument), r.URL)
return
}
req := BatchJobRequest{ID: jobID}
if i := strings.Index(jobID, "-"); i > 0 {
switch madmin.BatchJobType(jobID[:i]) {
case madmin.BatchJobReplicate:
req.Replicate = &BatchJobReplicateV1{}
case madmin.BatchJobKeyRotate:
req.KeyRotate = &BatchJobKeyRotateV1{}
case madmin.BatchJobExpire:
req.Expire = &BatchJobExpire{}
default:
writeErrorResponseJSON(ctx, w, toAPIError(ctx, errors.New("job ID format unrecognized")), r.URL)
return
}
}
ri := &batchJobInfo{}
if err := ri.load(ctx, objectAPI, req); err != nil {
if !errors.Is(err, errNoSuchJob) {
batchLogIf(ctx, err)
}
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
buf, err := json.Marshal(madmin.BatchJobStatus{LastMetric: ri.metric()})
if err != nil {
batchLogIf(ctx, err)
writeErrorResponseJSON(ctx, w, toAPIError(ctx, err), r.URL)
return
}
w.Write(buf)
}
var errNoSuchJob = errors.New("no such job") var errNoSuchJob = errors.New("no such job")
// DescribeBatchJob returns the currently active batch job definition // DescribeBatchJob returns the currently active batch job definition
@@ -1633,7 +1729,7 @@ func (a adminAPIHandlers) StartBatchJob(w http.ResponseWriter, r *http.Request)
return return
} }
job.ID = fmt.Sprintf("%s:%d", shortuuid.New(), GetProxyEndpointLocalIndex(globalProxyEndpoints)) job.ID = fmt.Sprintf("%s-%s%s%d", job.Type(), shortuuid.New(), getKeySeparator(), GetProxyEndpointLocalIndex(globalProxyEndpoints))
job.User = user job.User = user
job.Started = time.Now() job.Started = time.Now()
@@ -1721,11 +1817,60 @@ func newBatchJobPool(ctx context.Context, o ObjectLayer, workers int) *BatchJobP
jobCancelers: make(map[string]context.CancelFunc), jobCancelers: make(map[string]context.CancelFunc),
} }
jpool.ResizeWorkers(workers) jpool.ResizeWorkers(workers)
jpool.resume()
randomWait := func() time.Duration {
// randomWait depends on the number of nodes to avoid triggering resume and cleanups at the same time.
return time.Duration(rand.Float64() * float64(time.Duration(globalEndpoints.NEndpoints())*time.Hour))
}
go func() {
jpool.resume(randomWait)
jpool.cleanupReports(randomWait)
}()
return jpool return jpool
} }
func (j *BatchJobPool) resume() { func (j *BatchJobPool) cleanupReports(randomWait func() time.Duration) {
t := time.NewTimer(randomWait())
defer t.Stop()
for {
select {
case <-GlobalContext.Done():
return
case <-t.C:
results := make(chan itemOrErr[ObjectInfo], 100)
ctx, cancel := context.WithCancel(j.ctx)
defer cancel()
if err := j.objLayer.Walk(ctx, minioMetaBucket, batchJobReportsPrefix, results, WalkOptions{}); err != nil {
batchLogIf(j.ctx, err)
t.Reset(randomWait())
continue
}
for result := range results {
if result.Err != nil {
batchLogIf(j.ctx, result.Err)
continue
}
ri := &batchJobInfo{}
if err := ri.loadByPath(ctx, j.objLayer, result.Item.Name); err != nil {
batchLogIf(ctx, err)
continue
}
if (ri.Complete || ri.Failed) && time.Since(ri.LastUpdate) > oldJobsExpiration {
deleteConfig(ctx, j.objLayer, result.Item.Name)
}
}
t.Reset(randomWait())
}
}
}
func (j *BatchJobPool) resume(randomWait func() time.Duration) {
time.Sleep(randomWait())
results := make(chan itemOrErr[ObjectInfo], 100) results := make(chan itemOrErr[ObjectInfo], 100)
ctx, cancel := context.WithCancel(j.ctx) ctx, cancel := context.WithCancel(j.ctx)
defer cancel() defer cancel()
@@ -1988,7 +2133,7 @@ func (m *batchJobMetrics) purgeJobMetrics() {
var toDeleteJobMetrics []string var toDeleteJobMetrics []string
m.RLock() m.RLock()
for id, metrics := range m.metrics { for id, metrics := range m.metrics {
if time.Since(metrics.LastUpdate) > 24*time.Hour && (metrics.Complete || metrics.Failed) { if time.Since(metrics.LastUpdate) > oldJobsExpiration && (metrics.Complete || metrics.Failed) {
toDeleteJobMetrics = append(toDeleteJobMetrics, id) toDeleteJobMetrics = append(toDeleteJobMetrics, id)
} }
} }
+1 -1
View File
@@ -23,7 +23,7 @@ import (
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/minio/pkg/v2/wildcard" "github.com/minio/pkg/v3/wildcard"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
+3 -3
View File
@@ -33,8 +33,8 @@ import (
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v2/workers" "github.com/minio/pkg/v3/workers"
) )
// keyrotate: // keyrotate:
@@ -257,7 +257,7 @@ func (r *BatchJobKeyRotateV1) Start(ctx context.Context, api ObjectLayer, job Ba
JobType: string(job.Type()), JobType: string(job.Type()),
StartTime: job.Started, StartTime: job.Started,
} }
if err := ri.load(ctx, api, job); err != nil { if err := ri.loadOrInit(ctx, api, job); err != nil {
return err return err
} }
if ri.Complete { if ri.Complete {
+19 -9
View File
@@ -26,15 +26,17 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/ioutil" "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/ringbuffer"
) )
// Calculates bitrot in chunks and writes the hash into the stream. // Calculates bitrot in chunks and writes the hash into the stream.
type streamingBitrotWriter struct { type streamingBitrotWriter struct {
iow io.WriteCloser iow io.WriteCloser
closeWithErr func(err error) error closeWithErr func(err error)
h hash.Hash h hash.Hash
shardSize int64 shardSize int64
canClose *sync.WaitGroup canClose *sync.WaitGroup
byteBuf []byte
} }
func (b *streamingBitrotWriter) Write(p []byte) (int, error) { func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
@@ -62,7 +64,10 @@ func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
} }
func (b *streamingBitrotWriter) Close() error { func (b *streamingBitrotWriter) Close() error {
// Close the underlying writer.
// This will also flush the ring buffer if used.
err := b.iow.Close() err := b.iow.Close()
// Wait for all data to be written before returning else it causes race conditions. // Wait for all data to be written before returning else it causes race conditions.
// Race condition is because of io.PipeWriter implementation. i.e consider the following // Race condition is because of io.PipeWriter implementation. i.e consider the following
// sequent of operations: // sequent of operations:
@@ -73,29 +78,34 @@ func (b *streamingBitrotWriter) Close() error {
if b.canClose != nil { if b.canClose != nil {
b.canClose.Wait() b.canClose.Wait()
} }
// Recycle the buffer.
if b.byteBuf != nil {
globalBytePoolCap.Load().Put(b.byteBuf)
b.byteBuf = nil
}
return err return err
} }
// newStreamingBitrotWriterBuffer returns streaming bitrot writer implementation. // newStreamingBitrotWriterBuffer returns streaming bitrot writer implementation.
// The output is written to the supplied writer w. // The output is written to the supplied writer w.
func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize int64) io.Writer { func newStreamingBitrotWriterBuffer(w io.Writer, algo BitrotAlgorithm, shardSize int64) io.Writer {
return &streamingBitrotWriter{iow: ioutil.NopCloser(w), h: algo.New(), shardSize: shardSize, canClose: nil, closeWithErr: func(err error) error { return &streamingBitrotWriter{iow: ioutil.NopCloser(w), h: algo.New(), shardSize: shardSize, canClose: nil, closeWithErr: func(err error) {}}
// Similar to CloseWithError on pipes we always return nil.
return nil
}}
} }
// Returns streaming bitrot writer implementation. // Returns streaming bitrot writer implementation.
func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer { func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath string, length int64, algo BitrotAlgorithm, shardSize int64) io.Writer {
r, w := io.Pipe()
h := algo.New() h := algo.New()
buf := globalBytePoolCap.Load().Get()
rb := ringbuffer.NewBuffer(buf[:cap(buf)]).SetBlocking(true)
bw := &streamingBitrotWriter{ bw := &streamingBitrotWriter{
iow: ioutil.NewDeadlineWriter(w, globalDriveConfig.GetMaxTimeout()), iow: ioutil.NewDeadlineWriter(rb.WriteCloser(), globalDriveConfig.GetMaxTimeout()),
closeWithErr: w.CloseWithError, closeWithErr: rb.CloseWithError,
h: h, h: h,
shardSize: shardSize, shardSize: shardSize,
canClose: &sync.WaitGroup{}, canClose: &sync.WaitGroup{},
byteBuf: buf,
} }
bw.canClose.Add(1) bw.canClose.Add(1)
go func() { go func() {
@@ -106,7 +116,7 @@ func newStreamingBitrotWriter(disk StorageAPI, origvolume, volume, filePath stri
bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums. bitrotSumsTotalSize := ceilFrac(length, shardSize) * int64(h.Size()) // Size used for storing bitrot checksums.
totalFileSize = bitrotSumsTotalSize + length totalFileSize = bitrotSumsTotalSize + length
} }
r.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, r)) rb.CloseWithError(disk.CreateFile(context.TODO(), origvolume, volume, filePath, totalFileSize, rb))
}() }()
return bw return bw
} }
+28 -4
View File
@@ -19,9 +19,13 @@ package cmd
import ( import (
"context" "context"
"crypto/md5"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io"
"math/rand" "math/rand"
"os"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
@@ -30,7 +34,7 @@ import (
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/grid" "github.com/minio/minio/internal/grid"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
) )
// To abstract a node over network. // To abstract a node over network.
@@ -43,10 +47,15 @@ type ServerSystemConfig struct {
NEndpoints int NEndpoints int
CmdLines []string CmdLines []string
MinioEnv map[string]string MinioEnv map[string]string
Checksum string
} }
// Diff - returns error on first difference found in two configs. // Diff - returns error on first difference found in two configs.
func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error { func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
if s1.Checksum != s2.Checksum {
return fmt.Errorf("Expected MinIO binary checksum: %s, seen: %s", s1.Checksum, s2.Checksum)
}
ns1 := s1.NEndpoints ns1 := s1.NEndpoints
ns2 := s2.NEndpoints ns2 := s2.NEndpoints
if ns1 != ns2 { if ns1 != ns2 {
@@ -82,7 +91,7 @@ func (s1 *ServerSystemConfig) Diff(s2 *ServerSystemConfig) error {
extra = append(extra, k) extra = append(extra, k)
} }
} }
msg := "Expected same MINIO_ environment variables and values across all servers: " msg := "Expected MINIO_* environment name and values across all servers to be same: "
if len(missing) > 0 { if len(missing) > 0 {
msg += fmt.Sprintf(`Missing environment values: %v. `, missing) msg += fmt.Sprintf(`Missing environment values: %v. `, missing)
} }
@@ -105,6 +114,9 @@ var skipEnvs = map[string]struct{}{
"MINIO_ROOT_PASSWORD": {}, "MINIO_ROOT_PASSWORD": {},
"MINIO_ACCESS_KEY": {}, "MINIO_ACCESS_KEY": {},
"MINIO_SECRET_KEY": {}, "MINIO_SECRET_KEY": {},
"MINIO_OPERATOR_VERSION": {},
"MINIO_VSPHERE_PLUGIN_VERSION": {},
"MINIO_CI_CD": {},
} }
func getServerSystemCfg() *ServerSystemConfig { func getServerSystemCfg() *ServerSystemConfig {
@@ -120,7 +132,7 @@ func getServerSystemCfg() *ServerSystemConfig {
} }
envValues[envK] = logger.HashString(env.Get(envK, "")) envValues[envK] = logger.HashString(env.Get(envK, ""))
} }
scfg := &ServerSystemConfig{NEndpoints: globalEndpoints.NEndpoints(), MinioEnv: envValues} scfg := &ServerSystemConfig{NEndpoints: globalEndpoints.NEndpoints(), MinioEnv: envValues, Checksum: binaryChecksum}
var cmdLines []string var cmdLines []string
for _, ep := range globalEndpoints { for _, ep := range globalEndpoints {
cmdLines = append(cmdLines, ep.CmdLine) cmdLines = append(cmdLines, ep.CmdLine)
@@ -167,6 +179,18 @@ func (client *bootstrapRESTClient) String() string {
return client.gridConn.String() return client.gridConn.String()
} }
var binaryChecksum = getBinaryChecksum()
func getBinaryChecksum() string {
mw := md5.New()
b, err := os.Open(os.Args[0])
if err == nil {
defer b.Close()
io.Copy(mw, b)
}
return hex.EncodeToString(mw.Sum(nil))
}
func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools, gm *grid.Manager) error { func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointServerPools, gm *grid.Manager) error {
srcCfg := getServerSystemCfg() srcCfg := getServerSystemCfg()
clnts := newBootstrapRESTClients(endpointServerPools, gm) clnts := newBootstrapRESTClients(endpointServerPools, gm)
@@ -196,7 +220,7 @@ func verifyServerSystemConfig(ctx context.Context, endpointServerPools EndpointS
err := clnt.Verify(ctx, srcCfg) err := clnt.Verify(ctx, srcCfg)
mu.Lock() mu.Lock()
if err != nil { if err != nil {
bootstrapTraceMsg(fmt.Sprintf("clnt.Verify: %v, endpoint: %s", err, clnt)) bootstrapTraceMsg(fmt.Sprintf("bootstrapVerify: %v, endpoint: %s", err, clnt))
if !isNetworkError(err) { if !isNetworkError(err) {
bootLogOnceIf(context.Background(), fmt.Errorf("%s has incorrect configuration: %w", clnt, err), "incorrect_"+clnt.String()) bootLogOnceIf(context.Background(), fmt.Errorf("%s has incorrect configuration: %w", clnt, err), "incorrect_"+clnt.String())
incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt, err)) incorrectConfigs = append(incorrectConfigs, fmt.Errorf("%s has incorrect configuration: %w", clnt, err))
+30 -4
View File
@@ -79,6 +79,12 @@ func (z *ServerSystemConfig) DecodeMsg(dc *msgp.Reader) (err error) {
} }
z.MinioEnv[za0002] = za0003 z.MinioEnv[za0002] = za0003
} }
case "Checksum":
z.Checksum, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "Checksum")
return
}
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -92,9 +98,9 @@ func (z *ServerSystemConfig) DecodeMsg(dc *msgp.Reader) (err error) {
// EncodeMsg implements msgp.Encodable // EncodeMsg implements msgp.Encodable
func (z *ServerSystemConfig) EncodeMsg(en *msgp.Writer) (err error) { func (z *ServerSystemConfig) EncodeMsg(en *msgp.Writer) (err error) {
// map header, size 3 // map header, size 4
// write "NEndpoints" // write "NEndpoints"
err = en.Append(0x83, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73) err = en.Append(0x84, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
if err != nil { if err != nil {
return return
} }
@@ -142,15 +148,25 @@ func (z *ServerSystemConfig) EncodeMsg(en *msgp.Writer) (err error) {
return return
} }
} }
// write "Checksum"
err = en.Append(0xa8, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d)
if err != nil {
return
}
err = en.WriteString(z.Checksum)
if err != nil {
err = msgp.WrapError(err, "Checksum")
return
}
return return
} }
// MarshalMsg implements msgp.Marshaler // MarshalMsg implements msgp.Marshaler
func (z *ServerSystemConfig) MarshalMsg(b []byte) (o []byte, err error) { func (z *ServerSystemConfig) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.Require(b, z.Msgsize()) o = msgp.Require(b, z.Msgsize())
// map header, size 3 // map header, size 4
// string "NEndpoints" // string "NEndpoints"
o = append(o, 0x83, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73) o = append(o, 0x84, 0xaa, 0x4e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73)
o = msgp.AppendInt(o, z.NEndpoints) o = msgp.AppendInt(o, z.NEndpoints)
// string "CmdLines" // string "CmdLines"
o = append(o, 0xa8, 0x43, 0x6d, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x73) o = append(o, 0xa8, 0x43, 0x6d, 0x64, 0x4c, 0x69, 0x6e, 0x65, 0x73)
@@ -165,6 +181,9 @@ func (z *ServerSystemConfig) MarshalMsg(b []byte) (o []byte, err error) {
o = msgp.AppendString(o, za0002) o = msgp.AppendString(o, za0002)
o = msgp.AppendString(o, za0003) o = msgp.AppendString(o, za0003)
} }
// string "Checksum"
o = append(o, 0xa8, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d)
o = msgp.AppendString(o, z.Checksum)
return return
} }
@@ -241,6 +260,12 @@ func (z *ServerSystemConfig) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
z.MinioEnv[za0002] = za0003 z.MinioEnv[za0002] = za0003
} }
case "Checksum":
z.Checksum, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "Checksum")
return
}
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -266,5 +291,6 @@ func (z *ServerSystemConfig) Msgsize() (s int) {
s += msgp.StringPrefixSize + len(za0002) + msgp.StringPrefixSize + len(za0003) s += msgp.StringPrefixSize + len(za0002) + msgp.StringPrefixSize + len(za0003)
} }
} }
s += 9 + msgp.StringPrefixSize + len(z.Checksum)
return return
} }
+1 -1
View File
@@ -30,7 +30,7 @@ import (
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
const ( const (
+24 -42
View File
@@ -51,7 +51,6 @@ import (
sse "github.com/minio/minio/internal/bucket/encryption" sse "github.com/minio/minio/internal/bucket/encryption"
objectlock "github.com/minio/minio/internal/bucket/object/lock" objectlock "github.com/minio/minio/internal/bucket/object/lock"
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
"github.com/minio/minio/internal/config/cache"
"github.com/minio/minio/internal/config/dns" "github.com/minio/minio/internal/config/dns"
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
@@ -61,8 +60,8 @@ import (
"github.com/minio/minio/internal/ioutil" "github.com/minio/minio/internal/ioutil"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
"github.com/minio/pkg/v2/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
) )
const ( const (
@@ -72,6 +71,8 @@ const (
xMinIOErrCodeHeader = "x-minio-error-code" xMinIOErrCodeHeader = "x-minio-error-code"
xMinIOErrDescHeader = "x-minio-error-desc" xMinIOErrDescHeader = "x-minio-error-desc"
postPolicyBucketTagging = "tagging"
) )
// Check if there are buckets on server without corresponding entry in etcd backend and // Check if there are buckets on server without corresponding entry in etcd backend and
@@ -90,7 +91,7 @@ const (
// -- If IP of the entry doesn't match, this means entry is // -- If IP of the entry doesn't match, this means entry is
// //
// for another instance. Log an error to console. // for another instance. Log an error to console.
func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) { func initFederatorBackend(buckets []string, objLayer ObjectLayer) {
if len(buckets) == 0 { if len(buckets) == 0 {
return return
} }
@@ -111,10 +112,10 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
domainMissing := err == dns.ErrDomainMissing domainMissing := err == dns.ErrDomainMissing
if dnsBuckets != nil { if dnsBuckets != nil {
for _, bucket := range buckets { for _, bucket := range buckets {
bucketsSet.Add(bucket.Name) bucketsSet.Add(bucket)
r, ok := dnsBuckets[bucket.Name] r, ok := dnsBuckets[bucket]
if !ok { if !ok {
bucketsToBeUpdated.Add(bucket.Name) bucketsToBeUpdated.Add(bucket)
continue continue
} }
if !globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() { if !globalDomainIPs.Intersection(set.CreateStringSet(getHostsSlice(r)...)).IsEmpty() {
@@ -133,7 +134,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
// but if we do see a difference with local domain IPs with // but if we do see a difference with local domain IPs with
// hostSlice from etcd then we should update with newer // hostSlice from etcd then we should update with newer
// domainIPs, we proceed to do that here. // domainIPs, we proceed to do that here.
bucketsToBeUpdated.Add(bucket.Name) bucketsToBeUpdated.Add(bucket)
continue continue
} }
@@ -142,7 +143,7 @@ func initFederatorBackend(buckets []BucketInfo, objLayer ObjectLayer) {
// bucket names are globally unique in federation at a given // bucket names are globally unique in federation at a given
// path prefix, name collision is not allowed. We simply log // path prefix, name collision is not allowed. We simply log
// an error and continue. // an error and continue.
bucketsInConflict.Add(bucket.Name) bucketsInConflict.Add(bucket)
} }
} }
@@ -227,7 +228,7 @@ func (api objectAPIHandlers) GetBucketLocationHandler(w http.ResponseWriter, r *
// Generate response. // Generate response.
encodedSuccessResponse := encodeResponse(LocationResponse{}) encodedSuccessResponse := encodeResponse(LocationResponse{})
// Get current region. // Get current region.
region := globalSite.Region region := globalSite.Region()
if region != globalMinioDefaultRegion { if region != globalMinioDefaultRegion {
encodedSuccessResponse = encodeResponse(LocationResponse{ encodedSuccessResponse = encodeResponse(LocationResponse{
Location: region, Location: region,
@@ -1340,22 +1341,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
continue continue
} }
asize, err := objInfo.GetActualSize()
if err != nil {
asize = objInfo.Size
}
globalCacheConfig.Set(&cache.ObjectInfo{
Key: objInfo.Name,
Bucket: objInfo.Bucket,
ETag: getDecryptedETag(formValues, objInfo, false),
ModTime: objInfo.ModTime,
Expires: objInfo.Expires.UTC().Format(http.TimeFormat),
CacheControl: objInfo.CacheControl,
Metadata: cleanReservedKeys(objInfo.UserDefined),
Size: asize,
})
fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{ fanOutResp = append(fanOutResp, minio.PutObjectFanOutResponse{
Key: objInfo.Name, Key: objInfo.Name,
ETag: getDecryptedETag(formValues, objInfo, false), ETag: getDecryptedETag(formValues, objInfo, false),
@@ -1415,6 +1400,19 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
if formValues.Get(postPolicyBucketTagging) != "" {
tags, err := tags.ParseObjectXML(strings.NewReader(formValues.Get(postPolicyBucketTagging)))
if err != nil {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrMalformedPOSTRequest), r.URL)
return
}
tagsStr := tags.String()
opts.UserDefined[xhttp.AmzObjectTagging] = tagsStr
} else {
// avoid user set an invalid tag using `X-Amz-Tagging`
delete(opts.UserDefined, xhttp.AmzObjectTagging)
}
objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts) objInfo, err := objectAPI.PutObject(ctx, bucket, object, pReader, opts)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
@@ -1437,22 +1435,6 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
w.Header().Set(xhttp.Location, obj) w.Header().Set(xhttp.Location, obj)
} }
asize, err := objInfo.GetActualSize()
if err != nil {
asize = objInfo.Size
}
defer globalCacheConfig.Set(&cache.ObjectInfo{
Key: objInfo.Name,
Bucket: objInfo.Bucket,
ETag: etag,
ModTime: objInfo.ModTime,
Expires: objInfo.ExpiresStr(),
CacheControl: objInfo.CacheControl,
Metadata: cleanReservedKeys(objInfo.UserDefined),
Size: asize,
})
// Notify object created event. // Notify object created event.
defer sendEvent(eventArgs{ defer sendEvent(eventArgs{
EventName: event.ObjectCreatedPost, EventName: event.ObjectCreatedPost,
+6 -6
View File
@@ -32,7 +32,7 @@ import (
// Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling RemoveBucket HTTP handler tests for both Erasure multiple disks and single node setup.
func TestRemoveBucketHandler(t *testing.T) { func TestRemoveBucketHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testRemoveBucketHandler, []string{"RemoveBucket"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testRemoveBucketHandler, endpoints: []string{"RemoveBucket"}})
} }
func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
@@ -78,7 +78,7 @@ func testRemoveBucketHandler(obj ObjectLayer, instanceType, bucketName string, a
// Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling GetBucketPolicy HTTP handler tests for both Erasure multiple disks and single node setup.
func TestGetBucketLocationHandler(t *testing.T) { func TestGetBucketLocationHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testGetBucketLocationHandler, []string{"GetBucketLocation"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testGetBucketLocationHandler, endpoints: []string{"GetBucketLocation"}})
} }
func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
@@ -220,7 +220,7 @@ func testGetBucketLocationHandler(obj ObjectLayer, instanceType, bucketName stri
// Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling HeadBucket HTTP handler tests for both Erasure multiple disks and single node setup.
func TestHeadBucketHandler(t *testing.T) { func TestHeadBucketHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testHeadBucketHandler, []string{"HeadBucket"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testHeadBucketHandler, endpoints: []string{"HeadBucket"}})
} }
func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
@@ -322,7 +322,7 @@ func testHeadBucketHandler(obj ObjectLayer, instanceType, bucketName string, api
// Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup. // Wrapper for calling TestListMultipartUploadsHandler tests for both Erasure multiple disks and single node setup.
func TestListMultipartUploadsHandler(t *testing.T) { func TestListMultipartUploadsHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testListMultipartUploadsHandler, []string{"ListMultipartUploads"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testListMultipartUploadsHandler, endpoints: []string{"ListMultipartUploads"}})
} }
// testListMultipartUploadsHandler - Tests validate listing of multipart uploads. // testListMultipartUploadsHandler - Tests validate listing of multipart uploads.
@@ -558,7 +558,7 @@ func testListMultipartUploadsHandler(obj ObjectLayer, instanceType, bucketName s
// Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup. // Wrapper for calling TestListBucketsHandler tests for both Erasure multiple disks and single node setup.
func TestListBucketsHandler(t *testing.T) { func TestListBucketsHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testListBucketsHandler, []string{"ListBuckets"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testListBucketsHandler, endpoints: []string{"ListBuckets"}})
} }
// testListBucketsHandler - Tests validate listing of buckets. // testListBucketsHandler - Tests validate listing of buckets.
@@ -649,7 +649,7 @@ func testListBucketsHandler(obj ObjectLayer, instanceType, bucketName string, ap
// Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling DeleteMultipleObjects HTTP handler tests for both Erasure multiple disks and single node setup.
func TestAPIDeleteMultipleObjectsHandler(t *testing.T) { func TestAPIDeleteMultipleObjectsHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testAPIDeleteMultipleObjectsHandler, []string{"DeleteMultipleObjects", "PutBucketPolicy"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testAPIDeleteMultipleObjectsHandler, endpoints: []string{"DeleteMultipleObjects", "PutBucketPolicy"}})
} }
func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler, func testAPIDeleteMultipleObjectsHandler(obj ObjectLayer, instanceType, bucketName string, apiRouter http.Handler,
+4 -3
View File
@@ -28,7 +28,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
const ( const (
@@ -64,7 +64,8 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
} }
// Check if bucket exists. // Check if bucket exists.
if _, err := objAPI.GetBucketInfo(ctx, bucket, BucketOptions{}); err != nil { rcfg, err := globalBucketObjectLockSys.Get(bucket)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
@@ -76,7 +77,7 @@ func (api objectAPIHandlers) PutBucketLifecycleHandler(w http.ResponseWriter, r
} }
// Validate the received bucket policy document // Validate the received bucket policy document
if err = bucketLifecycle.Validate(); err != nil { if err = bucketLifecycle.Validate(rcfg); err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
+2 -2
View File
@@ -29,7 +29,7 @@ import (
// Test S3 Bucket lifecycle APIs with wrong credentials // Test S3 Bucket lifecycle APIs with wrong credentials
func TestBucketLifecycleWrongCredentials(t *testing.T) { func TestBucketLifecycleWrongCredentials(t *testing.T) {
ExecObjectLayerAPITest(t, testBucketLifecycleHandlersWrongCredentials, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testBucketLifecycleHandlersWrongCredentials, endpoints: []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}})
} }
// Test for authentication // Test for authentication
@@ -145,7 +145,7 @@ func testBucketLifecycleHandlersWrongCredentials(obj ObjectLayer, instanceType,
// Test S3 Bucket lifecycle APIs // Test S3 Bucket lifecycle APIs
func TestBucketLifecycle(t *testing.T) { func TestBucketLifecycle(t *testing.T) {
ExecObjectLayerAPITest(t, testBucketLifecycleHandlers, []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testBucketLifecycleHandlers, endpoints: []string{"GetBucketLifecycle", "PutBucketLifecycle", "DeleteBucketLifecycle"}})
} }
// Simple tests of bucket lifecycle: PUT, GET, DELETE. // Simple tests of bucket lifecycle: PUT, GET, DELETE.
+18 -3
View File
@@ -40,7 +40,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/s3select" "github.com/minio/minio/internal/s3select"
xnet "github.com/minio/pkg/v2/net" xnet "github.com/minio/pkg/v3/net"
"github.com/zeebo/xxh3" "github.com/zeebo/xxh3"
) )
@@ -72,6 +72,7 @@ func NewLifecycleSys() *LifecycleSys {
} }
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string) madmin.TraceInfo { func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string) madmin.TraceInfo {
sz, _ := oi.GetActualSize()
return madmin.TraceInfo{ return madmin.TraceInfo{
TraceType: madmin.TraceILM, TraceType: madmin.TraceILM,
Time: startTime, Time: startTime,
@@ -79,6 +80,7 @@ func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event
FuncName: event, FuncName: event,
Duration: duration, Duration: duration,
Path: pathJoin(oi.Bucket, oi.Name), Path: pathJoin(oi.Bucket, oi.Name),
Bytes: sz,
Error: "", Error: "",
Message: getSource(4), Message: getSource(4),
Custom: map[string]string{"version-id": oi.VersionID}, Custom: map[string]string{"version-id": oi.VersionID},
@@ -277,6 +279,10 @@ func (es *expiryState) getWorkerCh(h uint64) chan<- expiryOp {
} }
func (es *expiryState) ResizeWorkers(n int) { func (es *expiryState) ResizeWorkers(n int) {
if n == 0 {
n = 100
}
// Lock to avoid multiple resizes to happen at the same time. // Lock to avoid multiple resizes to happen at the same time.
es.mu.Lock() es.mu.Lock()
defer es.mu.Unlock() defer es.mu.Unlock()
@@ -538,6 +544,10 @@ func (t *transitionState) UpdateWorkers(n int) {
} }
func (t *transitionState) updateWorkers(n int) { func (t *transitionState) updateWorkers(n int) {
if n == 0 {
n = 100
}
for t.numWorkers < n { for t.numWorkers < n {
go t.worker(t.objAPI) go t.worker(t.objAPI)
t.numWorkers++ t.numWorkers++
@@ -573,6 +583,10 @@ func enqueueTransitionImmediate(obj ObjectInfo, src lcEventSrc) {
if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil { if lc, err := globalLifecycleSys.Get(obj.Bucket); err == nil {
switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action { switch event := lc.Eval(obj.ToLifecycleOpts()); event.Action {
case lifecycle.TransitionAction, lifecycle.TransitionVersionAction: case lifecycle.TransitionAction, lifecycle.TransitionVersionAction:
if obj.DeleteMarker || obj.IsDir {
// nothing to transition
return
}
globalTransitionState.queueTransitionTask(obj, event, src) globalTransitionState.queueTransitionTask(obj, event, src)
} }
} }
@@ -663,11 +677,12 @@ func genTransitionObjName(bucket string) (string, error) {
// is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved // is moved to the transition tier. Note that in the case of encrypted objects, entire encrypted stream is moved
// to the transition tier without decrypting or re-encrypting. // to the transition tier without decrypting or re-encrypting.
func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) (err error) { func transitionObject(ctx context.Context, objectAPI ObjectLayer, oi ObjectInfo, lae lcAuditEvent) (err error) {
timeILM := globalScannerMetrics.timeILM(lae.Action)
defer func() { defer func() {
if err != nil { if err != nil {
return return
} }
globalScannerMetrics.timeILM(lae.Action)(1) timeILM(1)
}() }()
opts := ObjectOptions{ opts := ObjectOptions{
@@ -726,7 +741,7 @@ func getTransitionedObjectReader(ctx context.Context, bucket, object string, rs
return nil, fmt.Errorf("transition storage class not configured: %w", err) return nil, fmt.Errorf("transition storage class not configured: %w", err)
} }
fn, off, length, err := NewGetObjectReader(rs, oi, opts) fn, off, length, err := NewGetObjectReader(rs, oi, opts, h)
if err != nil { if err != nil {
return nil, ErrorRespToObjectError(err, bucket, object) return nil, ErrorRespToObjectError(err, bucket, object)
} }
+6 -6
View File
@@ -26,7 +26,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// Validate all the ListObjects query arguments, returns an APIErrorCode // Validate all the ListObjects query arguments, returns an APIErrorCode
@@ -124,7 +124,7 @@ func (api objectAPIHandlers) listObjectVersionsHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
response := generateListVersionsResponse(bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta) response := generateListVersionsResponse(ctx, bucket, prefix, marker, versionIDMarker, delimiter, encodingType, maxkeys, listObjectVersionsInfo, checkObjMeta)
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodeResponseList(response)) writeSuccessResponseXML(w, encodeResponseList(response))
@@ -202,7 +202,7 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
if r.Header.Get(xMinIOExtract) == "true" && strings.Contains(prefix, archivePattern) { if r.Header.Get(xMinIOExtract) == "true" && strings.Contains(prefix, archivePattern) {
// Initiate a list objects operation inside a zip file based in the input params // Initiate a list objects operation inside a zip file based in the input params
listObjectsV2Info, err = listObjectsV2InArchive(ctx, objectAPI, bucket, prefix, token, delimiter, maxKeys, fetchOwner, startAfter) listObjectsV2Info, err = listObjectsV2InArchive(ctx, objectAPI, bucket, prefix, token, delimiter, maxKeys, startAfter, r.Header)
} else { } else {
// Initiate a list objects operation based on the input params. // Initiate a list objects operation based on the input params.
// On success would return back ListObjectsInfo object to be // On success would return back ListObjectsInfo object to be
@@ -219,7 +219,7 @@ func (api objectAPIHandlers) listObjectsV2Handler(ctx context.Context, w http.Re
return return
} }
response := generateListObjectsV2Response(bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter, response := generateListObjectsV2Response(ctx, bucket, prefix, token, listObjectsV2Info.NextContinuationToken, startAfter,
delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated, delimiter, encodingType, fetchOwner, listObjectsV2Info.IsTruncated,
maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta) maxKeys, listObjectsV2Info.Objects, listObjectsV2Info.Prefixes, checkObjMeta)
@@ -231,7 +231,7 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
if token == "" { if token == "" {
return token, -1 return token, -1
} }
i := strings.Index(token, ":") i := strings.Index(token, getKeySeparator())
if i < 0 { if i < 0 {
return token, -1 return token, -1
} }
@@ -318,7 +318,7 @@ func (api objectAPIHandlers) ListObjectsV1Handler(w http.ResponseWriter, r *http
return return
} }
response := generateListObjectsV1Response(bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo) response := generateListObjectsV1Response(ctx, bucket, prefix, marker, delimiter, encodingType, maxKeys, listObjectsInfo)
// Write success response. // Write success response.
writeSuccessResponseXML(w, encodeResponseList(response)) writeSuccessResponseXML(w, encodeResponseList(response))
+16 -20
View File
@@ -37,8 +37,8 @@ import (
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
"github.com/minio/pkg/v2/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
) )
// BucketMetadataSys captures all bucket metadata for a given cluster. // BucketMetadataSys captures all bucket metadata for a given cluster.
@@ -392,9 +392,7 @@ func (sys *BucketMetadataSys) GetReplicationConfig(ctx context.Context, bucket s
return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket} return nil, time.Time{}, BucketReplicationConfigNotFound{Bucket: bucket}
} }
if reloaded { if reloaded {
globalBucketTargetSys.set(BucketInfo{ globalBucketTargetSys.set(bucket, meta)
Name: bucket,
}, meta)
} }
return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil return meta.replicationConfig, meta.ReplicationConfigUpdatedAt, nil
} }
@@ -413,9 +411,7 @@ func (sys *BucketMetadataSys) GetBucketTargetsConfig(bucket string) (*madmin.Buc
return nil, BucketRemoteTargetNotFound{Bucket: bucket} return nil, BucketRemoteTargetNotFound{Bucket: bucket}
} }
if reloaded { if reloaded {
globalBucketTargetSys.set(BucketInfo{ globalBucketTargetSys.set(bucket, meta)
Name: bucket,
}, meta)
} }
return meta.bucketTargetConfig, nil return meta.bucketTargetConfig, nil
} }
@@ -471,7 +467,7 @@ func (sys *BucketMetadataSys) GetConfig(ctx context.Context, bucket string) (met
} }
// Init - initializes bucket metadata system for all buckets. // Init - initializes bucket metadata system for all buckets.
func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error { func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []string, objAPI ObjectLayer) error {
if objAPI == nil { if objAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@@ -484,7 +480,7 @@ func (sys *BucketMetadataSys) Init(ctx context.Context, buckets []BucketInfo, ob
} }
// concurrently load bucket metadata to speed up loading bucket metadata. // concurrently load bucket metadata to speed up loading bucket metadata.
func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []BucketInfo, failedBuckets map[string]struct{}) { func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []string, failedBuckets map[string]struct{}) {
g := errgroup.WithNErrs(len(buckets)) g := errgroup.WithNErrs(len(buckets))
bucketMetas := make([]BucketMetadata, len(buckets)) bucketMetas := make([]BucketMetadata, len(buckets))
for index := range buckets { for index := range buckets {
@@ -494,8 +490,8 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
// herd upon start up sequence. // herd upon start up sequence.
time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond)))) time.Sleep(25*time.Millisecond + time.Duration(rand.Int63n(int64(100*time.Millisecond))))
_, _ = sys.objAPI.HealBucket(ctx, buckets[index].Name, madmin.HealOpts{Recreate: true}) _, _ = sys.objAPI.HealBucket(ctx, buckets[index], madmin.HealOpts{Recreate: true})
meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index].Name) meta, err := loadBucketMetadata(ctx, sys.objAPI, buckets[index])
if err != nil { if err != nil {
return err return err
} }
@@ -508,7 +504,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
for index, err := range errs { for index, err := range errs {
if err != nil { if err != nil {
internalLogOnceIf(ctx, fmt.Errorf("Unable to load bucket metadata, will be retried: %w", err), internalLogOnceIf(ctx, fmt.Errorf("Unable to load bucket metadata, will be retried: %w", err),
"load-bucket-metadata-"+buckets[index].Name, logger.WarningKind) "load-bucket-metadata-"+buckets[index], logger.WarningKind)
} }
} }
@@ -519,7 +515,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
if errs[i] != nil { if errs[i] != nil {
continue continue
} }
sys.metadataMap[buckets[i].Name] = meta sys.metadataMap[buckets[i]] = meta
} }
sys.Unlock() sys.Unlock()
@@ -528,7 +524,7 @@ func (sys *BucketMetadataSys) concurrentLoad(ctx context.Context, buckets []Buck
if failedBuckets == nil { if failedBuckets == nil {
failedBuckets = make(map[string]struct{}) failedBuckets = make(map[string]struct{})
} }
failedBuckets[buckets[i].Name] = struct{}{} failedBuckets[buckets[i]] = struct{}{}
continue continue
} }
globalEventNotifier.set(buckets[i], meta) // set notification targets globalEventNotifier.set(buckets[i], meta) // set notification targets
@@ -548,7 +544,7 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
case <-ctx.Done(): case <-ctx.Done():
return return
case <-t.C: case <-t.C:
buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{}) buckets, err := sys.objAPI.ListBuckets(ctx, BucketOptions{NoMetadata: true})
if err != nil { if err != nil {
internalLogIf(ctx, err, logger.WarningKind) internalLogIf(ctx, err, logger.WarningKind)
break break
@@ -579,8 +575,8 @@ func (sys *BucketMetadataSys) refreshBucketsMetadataLoop(ctx context.Context, fa
// Initialize the failed buckets // Initialize the failed buckets
if _, ok := failedBuckets[buckets[i].Name]; ok { if _, ok := failedBuckets[buckets[i].Name]; ok {
globalEventNotifier.set(buckets[i], meta) globalEventNotifier.set(buckets[i].Name, meta)
globalBucketTargetSys.set(buckets[i], meta) globalBucketTargetSys.set(buckets[i].Name, meta)
delete(failedBuckets, buckets[i].Name) delete(failedBuckets, buckets[i].Name)
} }
@@ -600,8 +596,8 @@ func (sys *BucketMetadataSys) Initialized() bool {
} }
// Loads bucket metadata for all buckets into BucketMetadataSys. // Loads bucket metadata for all buckets into BucketMetadataSys.
func (sys *BucketMetadataSys) init(ctx context.Context, buckets []BucketInfo) { func (sys *BucketMetadataSys) init(ctx context.Context, buckets []string) {
count := 100 // load 100 bucket metadata at a time. count := globalEndpoints.ESCount() * 10
failedBuckets := make(map[string]struct{}) failedBuckets := make(map[string]struct{})
for { for {
if len(buckets) < count { if len(buckets) < count {
+1 -1
View File
@@ -41,7 +41,7 @@ import (
"github.com/minio/minio/internal/fips" "github.com/minio/minio/internal/fips"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
"github.com/minio/sio" "github.com/minio/sio"
) )
+5 -4
View File
@@ -26,7 +26,7 @@ import (
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
const ( const (
@@ -66,8 +66,9 @@ func (api objectAPIHandlers) GetBucketNotificationHandler(w http.ResponseWriter,
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
config.SetRegion(globalSite.Region) region := globalSite.Region()
if err = config.Validate(globalSite.Region, globalEventNotifier.targetList); err != nil { config.SetRegion(region)
if err = config.Validate(region, globalEventNotifier.targetList); err != nil {
arnErr, ok := err.(*event.ErrARNNotFound) arnErr, ok := err.(*event.ErrARNNotFound)
if ok { if ok {
for i, queue := range config.QueueList { for i, queue := range config.QueueList {
@@ -134,7 +135,7 @@ func (api objectAPIHandlers) PutBucketNotificationHandler(w http.ResponseWriter,
return return
} }
config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region, globalEventNotifier.targetList) config, err := event.ParseConfig(io.LimitReader(r.Body, r.ContentLength), globalSite.Region(), globalEventNotifier.targetList)
if err != nil { if err != nil {
apiErr := errorCodes.ToAPIErr(ErrMalformedXML) apiErr := errorCodes.ToAPIErr(ErrMalformedXML)
if event.IsEventError(err) { if event.IsEventError(err) {
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/minio/minio/internal/bucket/replication" "github.com/minio/minio/internal/bucket/replication"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// BucketObjectLockSys - map of bucket and retention configuration. // BucketObjectLockSys - map of bucket and retention configuration.
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
const ( const (
+6 -6
View File
@@ -29,8 +29,8 @@ import (
"testing" "testing"
"github.com/minio/minio/internal/auth" "github.com/minio/minio/internal/auth"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
"github.com/minio/pkg/v2/policy/condition" "github.com/minio/pkg/v3/policy/condition"
) )
func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy { func getAnonReadOnlyBucketPolicy(bucketName string) *policy.BucketPolicy {
@@ -107,7 +107,7 @@ func getAnonWriteOnlyObjectPolicy(bucketName, prefix string) *policy.BucketPolic
// Wrapper for calling Create Bucket and ensure we get one and only one success. // Wrapper for calling Create Bucket and ensure we get one and only one success.
func TestCreateBucket(t *testing.T) { func TestCreateBucket(t *testing.T) {
ExecObjectLayerAPITest(t, testCreateBucket, []string{"MakeBucket"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testCreateBucket, endpoints: []string{"MakeBucket"}})
} }
// testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success. // testCreateBucket - Test for calling Create Bucket and ensure we get one and only one success.
@@ -154,7 +154,7 @@ func testCreateBucket(obj ObjectLayer, instanceType, bucketName string, apiRoute
// Wrapper for calling Put Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling Put Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
func TestPutBucketPolicyHandler(t *testing.T) { func TestPutBucketPolicyHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testPutBucketPolicyHandler, []string{"PutBucketPolicy"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testPutBucketPolicyHandler, endpoints: []string{"PutBucketPolicy"}})
} }
// testPutBucketPolicyHandler - Test for Bucket policy end point. // testPutBucketPolicyHandler - Test for Bucket policy end point.
@@ -373,7 +373,7 @@ func testPutBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
// Wrapper for calling Get Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling Get Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
func TestGetBucketPolicyHandler(t *testing.T) { func TestGetBucketPolicyHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testGetBucketPolicyHandler, []string{"PutBucketPolicy", "GetBucketPolicy"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testGetBucketPolicyHandler, endpoints: []string{"PutBucketPolicy", "GetBucketPolicy"}})
} }
// testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket. // testGetBucketPolicyHandler - Test for end point which fetches the access policy json of the given bucket.
@@ -577,7 +577,7 @@ func testGetBucketPolicyHandler(obj ObjectLayer, instanceType, bucketName string
// Wrapper for calling Delete Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup. // Wrapper for calling Delete Bucket Policy HTTP handler tests for both Erasure multiple disks and single node setup.
func TestDeleteBucketPolicyHandler(t *testing.T) { func TestDeleteBucketPolicyHandler(t *testing.T) {
ExecObjectLayerAPITest(t, testDeleteBucketPolicyHandler, []string{"PutBucketPolicy", "DeleteBucketPolicy"}) ExecObjectLayerAPITest(ExecObjectLayerAPITestArgs{t: t, objAPITest: testDeleteBucketPolicyHandler, endpoints: []string{"PutBucketPolicy", "DeleteBucketPolicy"}})
} }
// testDeleteBucketPolicyHandler - Test for Delete bucket policy end point. // testDeleteBucketPolicyHandler - Test for Delete bucket policy end point.
+1 -1
View File
@@ -32,7 +32,7 @@ import (
"github.com/minio/minio/internal/handlers" "github.com/minio/minio/internal/handlers"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// PolicySys - policy subsystem. // PolicySys - policy subsystem.
+1 -1
View File
@@ -34,7 +34,7 @@ import (
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// PutBucketReplicationConfigHandler - PUT Bucket replication configuration. // PutBucketReplicationConfigHandler - PUT Bucket replication configuration.
+5 -1
View File
@@ -534,7 +534,7 @@ func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) Replicate
rstate.ReplicateDecisionStr = dsc.String() rstate.ReplicateDecisionStr = dsc.String()
asz, _ := oi.GetActualSize() asz, _ := oi.GetActualSize()
return ReplicateObjectInfo{ r := ReplicateObjectInfo{
Name: oi.Name, Name: oi.Name,
Size: oi.Size, Size: oi.Size,
ActualSize: asz, ActualSize: asz,
@@ -558,6 +558,10 @@ func getHealReplicateObjectInfo(oi ObjectInfo, rcfg replicationConfig) Replicate
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined), SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
UserTags: oi.UserTags, UserTags: oi.UserTags,
} }
if r.SSEC {
r.Checksum = oi.Checksum
}
return r
} }
// ReplicationState - returns replication state using other internal replication metadata in ObjectInfo // ReplicationState - returns replication state using other internal replication metadata in ObjectInfo
+102 -27
View File
@@ -19,6 +19,7 @@ package cmd
import ( import (
"context" "context"
"encoding/base64"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
@@ -74,8 +75,9 @@ const (
ObjectLockRetentionTimestamp = "objectlock-retention-timestamp" ObjectLockRetentionTimestamp = "objectlock-retention-timestamp"
// ObjectLockLegalHoldTimestamp - the last time a legal hold metadata modification happened on this cluster for this object version // ObjectLockLegalHoldTimestamp - the last time a legal hold metadata modification happened on this cluster for this object version
ObjectLockLegalHoldTimestamp = "objectlock-legalhold-timestamp" ObjectLockLegalHoldTimestamp = "objectlock-legalhold-timestamp"
// ReplicationWorkerMultiplier is suggested worker multiplier if traffic exceeds replication worker capacity
ReplicationWorkerMultiplier = 1.5 // ReplicationSsecChecksumHeader - the encrypted checksum of the SSE-C encrypted object.
ReplicationSsecChecksumHeader = "X-Minio-Replication-Ssec-Crc"
) )
// gets replication config associated to a given bucket name. // gets replication config associated to a given bucket name.
@@ -763,12 +765,34 @@ func (m caseInsensitiveMap) Lookup(key string) (string, bool) {
return "", false return "", false
} }
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (putOpts minio.PutObjectOptions, err error) { func getCRCMeta(oi ObjectInfo, partNum int, h http.Header) map[string]string {
meta := make(map[string]string) meta := make(map[string]string)
cs := oi.decryptChecksums(partNum, h)
for k, v := range cs {
cksum := hash.NewChecksumString(k, v)
if cksum == nil {
continue
}
if cksum.Valid() {
meta[cksum.Type.Key()] = v
}
}
return meta
}
func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, partNum int) (putOpts minio.PutObjectOptions, err error) {
meta := make(map[string]string)
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
for k, v := range objInfo.UserDefined { for k, v := range objInfo.UserDefined {
// In case of SSE-C objects copy the allowed internal headers as well // In case of SSE-C objects copy the allowed internal headers as well
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) { if !isSSEC || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) { if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
if strings.EqualFold(k, ReservedMetadataPrefixLower+"crc") {
for k, v := range getCRCMeta(objInfo, partNum, nil) {
meta[k] = v
}
}
continue continue
} }
if isStandardHeader(k) { if isStandardHeader(k) {
@@ -782,6 +806,17 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo) (put
} }
} }
if len(objInfo.Checksum) > 0 {
// Add encrypted CRC to metadata for SSE-C objects.
if isSSEC {
meta[ReplicationSsecChecksumHeader] = base64.StdEncoding.EncodeToString(objInfo.Checksum)
} else {
for k, v := range getCRCMeta(objInfo, 0, nil) {
meta[k] = v
}
}
}
if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) { if sc == "" && (objInfo.StorageClass == storageclass.STANDARD || objInfo.StorageClass == storageclass.RRS) {
sc = objInfo.StorageClass sc = objInfo.StorageClass
} }
@@ -1201,7 +1236,12 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
// make sure we have the latest metadata for metrics calculation // make sure we have the latest metadata for metrics calculation
rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN) rinfo.PrevReplicationStatus = objInfo.TargetReplicationStatus(tgt.ARN)
size, err := objInfo.GetActualSize() // Set the encrypted size for SSE-C objects
var size int64
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) {
size = objInfo.Size
} else {
size, err = objInfo.GetActualSize()
if err != nil { if err != nil {
replLogIf(ctx, err) replLogIf(ctx, err)
sendEvent(eventArgs{ sendEvent(eventArgs{
@@ -1213,6 +1253,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
}) })
return return
} }
}
if tgt.Bucket == "" { if tgt.Bucket == "" {
replLogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty for target %s", objInfo.Name, objInfo.VersionID, tgt.EndpointURL())) replLogIf(ctx, fmt.Errorf("unable to replicate object %s(%s), bucket is empty for target %s", objInfo.Name, objInfo.VersionID, tgt.EndpointURL()))
@@ -1239,7 +1280,7 @@ func (ri ReplicateObjectInfo) replicateObject(ctx context.Context, objectAPI Obj
// use core client to avoid doing multipart on PUT // use core client to avoid doing multipart on PUT
c := &minio.Core{Client: tgt.Client} c := &minio.Core{Client: tgt.Client}
putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo) putOpts, err := putReplicationOpts(ctx, tgt.StorageClass, objInfo, 0)
if err != nil { if err != nil {
replLogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err)) replLogIf(ctx, fmt.Errorf("failure setting options for replication bucket:%s err:%w", bucket, err))
sendEvent(eventArgs{ sendEvent(eventArgs{
@@ -1377,7 +1418,8 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
} }
// Set the encrypted size for SSE-C objects // Set the encrypted size for SSE-C objects
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) { isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
if isSSEC {
size = objInfo.Size size = objInfo.Size
} }
@@ -1437,6 +1479,13 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
return return
} }
} else { } else {
// SSEC objects will refuse HeadObject without the decryption key.
// Ignore the error, since we know the object exists and versioning prevents overwriting existing versions.
if isSSEC && strings.Contains(cerr.Error(), errorCodes[ErrSSEEncryptedObject].Description) {
rinfo.ReplicationStatus = replication.Completed
rinfo.ReplicationAction = replicateNone
goto applyAction
}
// if target returns error other than NoSuchKey, defer replication attempt // if target returns error other than NoSuchKey, defer replication attempt
if minio.IsNetworkOrHostDown(cerr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) { if minio.IsNetworkOrHostDown(cerr, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
globalBucketTargetSys.markOffline(tgt.EndpointURL()) globalBucketTargetSys.markOffline(tgt.EndpointURL())
@@ -1464,6 +1513,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
return return
} }
} }
applyAction:
rinfo.ReplicationStatus = replication.Completed rinfo.ReplicationStatus = replication.Completed
rinfo.Size = size rinfo.Size = size
rinfo.ReplicationAction = rAction rinfo.ReplicationAction = rAction
@@ -1506,7 +1556,7 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
} }
} else { } else {
var putOpts minio.PutObjectOptions var putOpts minio.PutObjectOptions
putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo) putOpts, err = putReplicationOpts(ctx, tgt.StorageClass, objInfo, 0)
if err != nil { if err != nil {
replLogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err)) replLogIf(ctx, fmt.Errorf("failed to set replicate options for object %s/%s(%s) (target %s) err:%w", bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), err))
sendEvent(eventArgs{ sendEvent(eventArgs{
@@ -1542,15 +1592,17 @@ func (ri ReplicateObjectInfo) replicateAll(ctx context.Context, objectAPI Object
} else { } else {
_, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts) _, rinfo.Err = c.PutObject(ctx, tgt.Bucket, object, r, size, "", "", putOpts)
} }
if rinfo.Err != nil && minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" { if rinfo.Err != nil {
if minio.ToErrorResponse(rinfo.Err).Code != "PreconditionFailed" {
rinfo.ReplicationStatus = replication.Failed rinfo.ReplicationStatus = replication.Failed
replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w", replLogIf(ctx, fmt.Errorf("unable to replicate for object %s/%s(%s) to target %s: %w",
bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err)) bucket, objInfo.Name, objInfo.VersionID, tgt.EndpointURL(), rinfo.Err))
} }
if rinfo.Err != nil && minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) { if minio.IsNetworkOrHostDown(rinfo.Err, true) && !globalBucketTargetSys.isOffline(tgt.EndpointURL()) {
globalBucketTargetSys.markOffline(tgt.EndpointURL()) globalBucketTargetSys.markOffline(tgt.EndpointURL())
} }
} }
}
return return
} }
@@ -1568,7 +1620,7 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
break break
} }
if minio.ToErrorResponse(err).Code == "PreconditionFailed" { if minio.ToErrorResponse(err).Code == "PreconditionFailed" {
return err return nil
} }
attempts++ attempts++
time.Sleep(time.Duration(rand.Int63n(int64(time.Second)))) time.Sleep(time.Duration(rand.Int63n(int64(time.Second))))
@@ -1597,11 +1649,12 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
var ( var (
hr *hash.Reader hr *hash.Reader
pInfo minio.ObjectPart pInfo minio.ObjectPart
isSSEC = crypto.SSEC.IsEncrypted(objInfo.UserDefined)
) )
var objectSize int64 var objectSize int64
for _, partInfo := range objInfo.Parts { for _, partInfo := range objInfo.Parts {
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) { if isSSEC {
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize) hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.Size), partInfo.Size, "", "", partInfo.ActualSize)
} else { } else {
hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize) hr, err = hash.NewReader(ctx, io.LimitReader(r, partInfo.ActualSize), partInfo.ActualSize, "", "", partInfo.ActualSize)
@@ -1612,12 +1665,18 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
cHeader := http.Header{} cHeader := http.Header{}
cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true") cHeader.Add(xhttp.MinIOSourceReplicationRequest, "true")
if !isSSEC {
crc := getCRCMeta(objInfo, partInfo.Number, nil) // No SSE-C keys here.
for k, v := range crc {
cHeader.Add(k, v)
}
}
popts := minio.PutObjectPartOptions{ popts := minio.PutObjectPartOptions{
SSE: opts.ServerSideEncryption, SSE: opts.ServerSideEncryption,
CustomHeader: cHeader, CustomHeader: cHeader,
} }
if crypto.SSEC.IsEncrypted(objInfo.UserDefined) { if isSSEC {
objectSize += partInfo.Size objectSize += partInfo.Size
pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts) pInfo, err = c.PutObjectPart(ctx, bucket, object, uploadID, partInfo.Number, hr, partInfo.Size, popts)
} else { } else {
@@ -1627,22 +1686,33 @@ func replicateObjectWithMultipart(ctx context.Context, c *minio.Core, bucket, ob
if err != nil { if err != nil {
return err return err
} }
if !crypto.SSEC.IsEncrypted(objInfo.UserDefined) && pInfo.Size != partInfo.ActualSize { if !isSSEC && pInfo.Size != partInfo.ActualSize {
return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize) return fmt.Errorf("Part size mismatch: got %d, want %d", pInfo.Size, partInfo.ActualSize)
} }
uploadedParts = append(uploadedParts, minio.CompletePart{ uploadedParts = append(uploadedParts, minio.CompletePart{
PartNumber: pInfo.PartNumber, PartNumber: pInfo.PartNumber,
ETag: pInfo.ETag, ETag: pInfo.ETag,
ChecksumCRC32: pInfo.ChecksumCRC32,
ChecksumCRC32C: pInfo.ChecksumCRC32C,
ChecksumSHA1: pInfo.ChecksumSHA1,
ChecksumSHA256: pInfo.ChecksumSHA256,
}) })
} }
userMeta := map[string]string{
validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"],
}
if isSSEC && objInfo.UserDefined[ReplicationSsecChecksumHeader] != "" {
userMeta[ReplicationSsecChecksumHeader] = objInfo.UserDefined[ReplicationSsecChecksumHeader]
}
// really big value but its okay on heavily loaded systems. This is just tail end timeout. // really big value but its okay on heavily loaded systems. This is just tail end timeout.
cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute) cctx, ccancel := context.WithTimeout(ctx, 10*time.Minute)
defer ccancel() defer ccancel()
_, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{ _, err = c.CompleteMultipartUpload(cctx, bucket, object, uploadID, uploadedParts, minio.PutObjectOptions{
UserMetadata: map[string]string{validSSEReplicationHeaders[ReservedMetadataPrefix+"Actual-Object-Size"]: objInfo.UserDefined[ReservedMetadataPrefix+"actual-size"]}, UserMetadata: userMeta,
Internal: minio.AdvancedPutOptions{ Internal: minio.AdvancedPutOptions{
SourceMTime: objInfo.ModTime, SourceMTime: objInfo.ModTime,
SourceETag: objInfo.ETag,
// always set this to distinguish between `mc mirror` replication and serverside // always set this to distinguish between `mc mirror` replication and serverside
ReplicationRequest: true, ReplicationRequest: true,
}, },
@@ -2176,12 +2246,12 @@ type proxyResult struct {
// get Reader from replication target if active-active replication is in place and // get Reader from replication target if active-active replication is in place and
// this node returns a 404 // this node returns a 404
func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, _ http.Header, opts ObjectOptions, proxyTargets *madmin.BucketTargets) (gr *GetObjectReader, proxy proxyResult, err error) { func proxyGetToReplicationTarget(ctx context.Context, bucket, object string, rs *HTTPRangeSpec, h http.Header, opts ObjectOptions, proxyTargets *madmin.BucketTargets) (gr *GetObjectReader, proxy proxyResult, err error) {
tgt, oi, proxy := proxyHeadToRepTarget(ctx, bucket, object, rs, opts, proxyTargets) tgt, oi, proxy := proxyHeadToRepTarget(ctx, bucket, object, rs, opts, proxyTargets)
if !proxy.Proxy { if !proxy.Proxy {
return nil, proxy, nil return nil, proxy, nil
} }
fn, _, _, err := NewGetObjectReader(nil, oi, opts) fn, _, _, err := NewGetObjectReader(nil, oi, opts, h)
if err != nil { if err != nil {
return nil, proxy, err return nil, proxy, err
} }
@@ -2366,7 +2436,9 @@ func scheduleReplication(ctx context.Context, oi ObjectInfo, o ObjectLayer, dsc
SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined), SSEC: crypto.SSEC.IsEncrypted(oi.UserDefined),
UserTags: oi.UserTags, UserTags: oi.UserTags,
} }
if ri.SSEC {
ri.Checksum = oi.Checksum
}
if dsc.Synchronous() { if dsc.Synchronous() {
replicateObject(ctx, ri, o) replicateObject(ctx, ri, o)
} else { } else {
@@ -2846,17 +2918,19 @@ func (s *replicationResyncer) resyncBucket(ctx context.Context, objectAPI Object
ReplicationProxyRequest: "false", ReplicationProxyRequest: "false",
}, },
}) })
sz := roi.Size
if err != nil { if err != nil {
if roi.DeleteMarker && isErrMethodNotAllowed(ErrorRespToObjectError(err, opts.bucket, roi.Name)) { if roi.DeleteMarker && isErrMethodNotAllowed(ErrorRespToObjectError(err, opts.bucket, roi.Name)) {
st.ReplicatedCount++ st.ReplicatedCount++
} else { } else {
st.FailedCount++ st.FailedCount++
} }
sz = 0
} else { } else {
st.ReplicatedCount++ st.ReplicatedCount++
st.ReplicatedSize += roi.Size st.ReplicatedSize += roi.Size
} }
traceFn(err) traceFn(sz, err)
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
@@ -2971,17 +3045,17 @@ func (s *replicationResyncer) start(ctx context.Context, objAPI ObjectLayer, opt
return nil return nil
} }
func (s *replicationResyncer) trace(resyncID string, path string) func(err error) { func (s *replicationResyncer) trace(resyncID string, path string) func(sz int64, err error) {
startTime := time.Now() startTime := time.Now()
return func(err error) { return func(sz int64, err error) {
duration := time.Since(startTime) duration := time.Since(startTime)
if globalTrace.NumSubscribers(madmin.TraceReplicationResync) > 0 { if globalTrace.NumSubscribers(madmin.TraceReplicationResync) > 0 {
globalTrace.Publish(replicationResyncTrace(resyncID, startTime, duration, path, err)) globalTrace.Publish(replicationResyncTrace(resyncID, startTime, duration, path, err, sz))
} }
} }
} }
func replicationResyncTrace(resyncID string, startTime time.Time, duration time.Duration, path string, err error) madmin.TraceInfo { func replicationResyncTrace(resyncID string, startTime time.Time, duration time.Duration, path string, err error, sz int64) madmin.TraceInfo {
var errStr string var errStr string
if err != nil { if err != nil {
errStr = err.Error() errStr = err.Error()
@@ -2995,6 +3069,7 @@ func replicationResyncTrace(resyncID string, startTime time.Time, duration time.
Duration: duration, Duration: duration,
Path: path, Path: path,
Error: errStr, Error: errStr,
Bytes: sz,
} }
} }
@@ -3011,7 +3086,7 @@ func (p *ReplicationPool) deleteResyncMetadata(ctx context.Context, bucket strin
} }
// initResync - initializes bucket replication resync for all buckets. // initResync - initializes bucket replication resync for all buckets.
func (p *ReplicationPool) initResync(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error { func (p *ReplicationPool) initResync(ctx context.Context, buckets []string, objAPI ObjectLayer) error {
if objAPI == nil { if objAPI == nil {
return errServerNotInitialized return errServerNotInitialized
} }
@@ -3020,7 +3095,7 @@ func (p *ReplicationPool) initResync(ctx context.Context, buckets []BucketInfo,
return nil return nil
} }
func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) { func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []string, objAPI ObjectLayer) {
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Run the replication resync in a loop // Run the replication resync in a loop
for { for {
@@ -3038,13 +3113,13 @@ func (p *ReplicationPool) startResyncRoutine(ctx context.Context, buckets []Buck
} }
// Loads bucket replication resync statuses into memory. // Loads bucket replication resync statuses into memory.
func (p *ReplicationPool) loadResync(ctx context.Context, buckets []BucketInfo, objAPI ObjectLayer) error { func (p *ReplicationPool) loadResync(ctx context.Context, buckets []string, objAPI ObjectLayer) error {
// Make sure only one node running resync on the cluster. // Make sure only one node running resync on the cluster.
ctx, cancel := globalLeaderLock.GetLock(ctx) ctx, cancel := globalLeaderLock.GetLock(ctx)
defer cancel() defer cancel()
for index := range buckets { for index := range buckets {
bucket := buckets[index].Name bucket := buckets[index]
meta, err := loadBucketResyncMetadata(ctx, bucket, objAPI) meta, err := loadBucketResyncMetadata(ctx, bucket, objAPI)
if err != nil { if err != nil {
+3 -3
View File
@@ -612,7 +612,7 @@ func (sys *BucketTargetSys) UpdateAllTargets(bucket string, tgts *madmin.BucketT
} }
// create minio-go clients for buckets having remote targets // create minio-go clients for buckets having remote targets
func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) { func (sys *BucketTargetSys) set(bucket string, meta BucketMetadata) {
cfg := meta.bucketTargetConfig cfg := meta.bucketTargetConfig
if cfg == nil || cfg.Empty() { if cfg == nil || cfg.Empty() {
return return
@@ -626,9 +626,9 @@ func (sys *BucketTargetSys) set(bucket BucketInfo, meta BucketMetadata) {
continue continue
} }
sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: tgtClient} sys.arnRemotesMap[tgt.Arn] = arnTarget{Client: tgtClient}
sys.updateBandwidthLimit(bucket.Name, tgt.Arn, tgt.BandwidthLimit) sys.updateBandwidthLimit(bucket, tgt.Arn, tgt.BandwidthLimit)
} }
sys.targetsMap[bucket.Name] = cfg.Targets sys.targetsMap[bucket] = cfg.Targets
} }
// Returns a minio-go Client configured to access remote host described in replication target config. // Returns a minio-go Client configured to access remote host described in replication target config.
+1 -1
View File
@@ -28,7 +28,7 @@ import (
"github.com/minio/minio/internal/bucket/versioning" "github.com/minio/minio/internal/bucket/versioning"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
const ( const (
+6
View File
@@ -80,6 +80,9 @@ func runCallhome(ctx context.Context, objAPI ObjectLayer) bool {
ctx = lkctx.Context() ctx = lkctx.Context()
defer locker.Unlock(lkctx) defer locker.Unlock(lkctx)
// Perform callhome once and then keep running it at regular intervals.
performCallhome(ctx)
callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur()) callhomeTimer := time.NewTimer(globalCallhomeConfig.FrequencyDur())
defer callhomeTimer.Stop() defer callhomeTimer.Stop()
@@ -141,11 +144,14 @@ func performCallhome(ctx context.Context) {
select { select {
case hi, hasMore := <-healthInfoCh: case hi, hasMore := <-healthInfoCh:
if !hasMore { if !hasMore {
auditOptions := AuditLogOptions{Event: "callhome:diagnostics"}
// Received all data. Send to SUBNET and return // Received all data. Send to SUBNET and return
err := sendHealthInfo(ctx, healthInfo) err := sendHealthInfo(ctx, healthInfo)
if err != nil { if err != nil {
internalLogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err)) internalLogIf(ctx, fmt.Errorf("Unable to perform callhome: %w", err))
auditOptions.Error = err.Error()
} }
auditLogInternal(ctx, auditOptions)
return return
} }
healthInfo = hi healthInfo = hi
+40 -15
View File
@@ -55,10 +55,10 @@ import (
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/kms" "github.com/minio/minio/internal/kms"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/certs" "github.com/minio/pkg/v3/certs"
"github.com/minio/pkg/v2/console" "github.com/minio/pkg/v3/console"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
xnet "github.com/minio/pkg/v2/net" xnet "github.com/minio/pkg/v3/net"
"golang.org/x/term" "golang.org/x/term"
) )
@@ -176,7 +176,10 @@ func minioConfigToConsoleFeatures() {
os.Setenv("CONSOLE_STS_DURATION", valueSession) os.Setenv("CONSOLE_STS_DURATION", valueSession)
} }
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region) os.Setenv("CONSOLE_MINIO_SITE_NAME", globalSite.Name())
os.Setenv("CONSOLE_MINIO_SITE_REGION", globalSite.Region())
os.Setenv("CONSOLE_MINIO_REGION", globalSite.Region())
os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", "")) os.Setenv("CONSOLE_CERT_PASSWD", env.Get("MINIO_CERT_PASSWD", ""))
// This section sets Browser (console) stored config // This section sets Browser (console) stored config
@@ -399,22 +402,37 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
ctxt.certsDirSet = true ctxt.certsDirSet = true
} }
memAvailable := availableMemory()
if ctx.IsSet("memlimit") || ctx.GlobalIsSet("memlimit") {
memlimit := ctx.String("memlimit")
if memlimit == "" {
memlimit = ctx.GlobalString("memlimit")
}
mlimit, err := humanize.ParseBytes(memlimit)
if err != nil {
return err
}
if mlimit > memAvailable {
logger.Info("WARNING: maximum memory available (%s) smaller than specified --memlimit=%s, ignoring --memlimit value",
humanize.IBytes(memAvailable), memlimit)
}
ctxt.MemLimit = mlimit
} else {
ctxt.MemLimit = memAvailable
}
if memAvailable < ctxt.MemLimit {
ctxt.MemLimit = memAvailable
}
ctxt.FTP = ctx.StringSlice("ftp") ctxt.FTP = ctx.StringSlice("ftp")
ctxt.SFTP = ctx.StringSlice("sftp") ctxt.SFTP = ctx.StringSlice("sftp")
ctxt.Interface = ctx.String("interface") ctxt.Interface = ctx.String("interface")
ctxt.UserTimeout = ctx.Duration("conn-user-timeout") ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
ctxt.ConnClientReadDeadline = ctx.Duration("conn-client-read-deadline")
ctxt.ConnClientWriteDeadline = ctx.Duration("conn-client-write-deadline")
ctxt.SendBufSize = ctx.Int("send-buf-size") ctxt.SendBufSize = ctx.Int("send-buf-size")
ctxt.RecvBufSize = ctx.Int("recv-buf-size") ctxt.RecvBufSize = ctx.Int("recv-buf-size")
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
ctxt.IdleTimeout = ctx.Duration("idle-timeout") ctxt.IdleTimeout = ctx.Duration("idle-timeout")
ctxt.ReadHeaderTimeout = ctx.Duration("read-header-timeout") ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
ctxt.MaxIdleConnsPerHost = ctx.Int("max-idle-conns-per-host")
if conf := ctx.String("config"); len(conf) > 0 { if conf := ctx.String("config"); len(conf) > 0 {
err = mergeServerCtxtFromConfigFile(conf, ctxt) err = mergeServerCtxtFromConfigFile(conf, ctxt)
@@ -816,7 +834,7 @@ func serverHandleEnvVars() {
} }
} }
globalDisableFreezeOnBoot = env.Get("_MINIO_DISABLE_API_FREEZE_ON_BOOT", "") == "true" || serverDebugLog globalEnableSyncBoot = env.Get("MINIO_SYNC_BOOT", config.EnableOff) == config.EnableOn
} }
func loadRootCredentials() { func loadRootCredentials() {
@@ -825,6 +843,7 @@ func loadRootCredentials() {
// Check both cases and authenticate them if correctly defined // Check both cases and authenticate them if correctly defined
var user, password string var user, password string
var hasCredentials bool var hasCredentials bool
var legacyCredentials bool
//nolint:gocritic //nolint:gocritic
if env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) { if env.IsSet(config.EnvRootUser) && env.IsSet(config.EnvRootPassword) {
user = env.Get(config.EnvRootUser, "") user = env.Get(config.EnvRootUser, "")
@@ -833,6 +852,7 @@ func loadRootCredentials() {
} else if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) { } else if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
user = env.Get(config.EnvAccessKey, "") user = env.Get(config.EnvAccessKey, "")
password = env.Get(config.EnvSecretKey, "") password = env.Get(config.EnvSecretKey, "")
legacyCredentials = true
hasCredentials = true hasCredentials = true
} else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" { } else if globalServerCtxt.RootUser != "" && globalServerCtxt.RootPwd != "" {
user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd user, password = globalServerCtxt.RootUser, globalServerCtxt.RootPwd
@@ -841,8 +861,13 @@ func loadRootCredentials() {
if hasCredentials { if hasCredentials {
cred, err := auth.CreateCredentials(user, password) cred, err := auth.CreateCredentials(user, password)
if err != nil { if err != nil {
if legacyCredentials {
logger.Fatal(config.ErrInvalidCredentials(err), logger.Fatal(config.ErrInvalidCredentials(err),
"Unable to validate credentials inherited from the shell environment") "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) { if env.IsSet(config.EnvAccessKey) && env.IsSet(config.EnvSecretKey) {
msg := fmt.Sprintf("WARNING: %s and %s are deprecated.\n"+ msg := fmt.Sprintf("WARNING: %s and %s are deprecated.\n"+
+11 -4
View File
@@ -56,7 +56,7 @@ import (
"github.com/minio/minio/internal/crypto" "github.com/minio/minio/internal/crypto"
xhttp "github.com/minio/minio/internal/http" xhttp "github.com/minio/minio/internal/http"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
) )
func initHelp() { func initHelp() {
@@ -240,6 +240,11 @@ func initHelp() {
Description: "manage Browser HTTP specific features, such as Security headers, etc.", Description: "manage Browser HTTP specific features, such as Security headers, etc.",
Optional: true, Optional: true,
}, },
config.HelpKV{
Key: config.ILMSubSys,
Description: "manage ILM settings for expiration and transition workers",
Optional: true,
},
} }
if globalIsErasure { if globalIsErasure {
@@ -288,6 +293,7 @@ func initHelp() {
config.DriveSubSys: drive.HelpDrive, config.DriveSubSys: drive.HelpDrive,
config.CacheSubSys: cache.Help, config.CacheSubSys: cache.Help,
config.BrowserSubSys: browser.Help, config.BrowserSubSys: browser.Help,
config.ILMSubSys: ilm.Help,
} }
config.RegisterHelpSubSys(helpMap) config.RegisterHelpSubSys(helpMap)
@@ -362,7 +368,7 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
} }
case config.IdentityOpenIDSubSys: case config.IdentityOpenIDSubSys:
if _, err := openid.LookupConfig(s, if _, err := openid.LookupConfig(s,
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil { NewHTTPTransport(), xhttp.DrainBody, globalSite.Region()); err != nil {
return err return err
} }
case config.IdentityLDAPSubSys: case config.IdentityLDAPSubSys:
@@ -383,7 +389,7 @@ func validateSubSysConfig(ctx context.Context, s config.Config, subSys string, o
} }
case config.IdentityPluginSubSys: case config.IdentityPluginSubSys:
if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default], if _, err := idplugin.LookupConfig(s[config.IdentityPluginSubSys][config.Default],
NewHTTPTransport(), xhttp.DrainBody, globalSite.Region); err != nil { NewHTTPTransport(), xhttp.DrainBody, globalSite.Region()); err != nil {
return err return err
} }
case config.SubnetSubSys: case config.SubnetSubSys:
@@ -530,10 +536,11 @@ func lookupConfigs(s config.Config, objAPI ObjectLayer) {
// but not federation. // but not federation.
globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled globalBucketFederation = etcdCfg.PathPrefix == "" && etcdCfg.Enabled
globalSite, err = config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default]) siteCfg, err := config.LookupSite(s[config.SiteSubSys][config.Default], s[config.RegionSubSys][config.Default])
if err != nil { if err != nil {
configLogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err)) configLogIf(ctx, fmt.Errorf("Invalid site configuration: %w", err))
} }
globalSite.Update(siteCfg)
globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled globalAutoEncryption = crypto.LookupAutoEncryption() // Enable auto-encryption if enabled
if globalAutoEncryption && GlobalKMS == nil { if globalAutoEncryption && GlobalKMS == nil {
+4 -4
View File
@@ -39,8 +39,8 @@ func TestServerConfig(t *testing.T) {
t.Fatalf("Init Test config failed") t.Fatalf("Init Test config failed")
} }
if globalSite.Region != globalMinioDefaultRegion { if globalSite.Region() != globalMinioDefaultRegion {
t.Errorf("Expecting region `us-east-1` found %s", globalSite.Region) t.Errorf("Expecting region `us-east-1` found %s", globalSite.Region())
} }
// Set new region and verify. // Set new region and verify.
@@ -52,8 +52,8 @@ func TestServerConfig(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if site.Region != "us-west-1" { if site.Region() != "us-west-1" {
t.Errorf("Expecting region `us-west-1` found %s", globalSite.Region) t.Errorf("Expecting region `us-west-1` found %s", globalSite.Region())
} }
if err := saveServerConfig(context.Background(), objLayer, globalServerConfig); err != nil { if err := saveServerConfig(context.Background(), objLayer, globalServerConfig); err != nil {
+2 -2
View File
@@ -33,8 +33,8 @@ import (
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/event/target" "github.com/minio/minio/internal/event/target"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
xnet "github.com/minio/pkg/v2/net" xnet "github.com/minio/pkg/v3/net"
"github.com/minio/pkg/v2/quick" "github.com/minio/pkg/v3/quick"
) )
// Save config file to corresponding backend // Save config file to corresponding backend
+1 -1
View File
@@ -27,7 +27,7 @@ import (
"github.com/minio/minio/internal/config/policy/opa" "github.com/minio/minio/internal/config/policy/opa"
"github.com/minio/minio/internal/config/storageclass" "github.com/minio/minio/internal/config/storageclass"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v2/quick" "github.com/minio/pkg/v3/quick"
) )
// FileLogger is introduced to workaround the dependency about logrus // FileLogger is introduced to workaround the dependency about logrus
+2 -2
View File
@@ -29,8 +29,8 @@ import (
"github.com/minio/minio/internal/logger/target/console" "github.com/minio/minio/internal/logger/target/console"
"github.com/minio/minio/internal/logger/target/types" "github.com/minio/minio/internal/logger/target/types"
"github.com/minio/minio/internal/pubsub" "github.com/minio/minio/internal/pubsub"
"github.com/minio/pkg/v2/logger/message/log" "github.com/minio/pkg/v3/logger/message/log"
xnet "github.com/minio/pkg/v2/net" xnet "github.com/minio/pkg/v3/net"
) )
// number of log messages to buffer // number of log messages to buffer
+55 -30
View File
@@ -31,6 +31,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
@@ -43,7 +44,7 @@ import (
"github.com/minio/minio/internal/config/heal" "github.com/minio/minio/internal/config/heal"
"github.com/minio/minio/internal/event" "github.com/minio/minio/internal/event"
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
"github.com/minio/pkg/v2/console" "github.com/minio/pkg/v3/console"
uatomic "go.uber.org/atomic" uatomic "go.uber.org/atomic"
) )
@@ -53,7 +54,7 @@ const (
dataScannerCompactLeastObject = 500 // Compact when there is less than this many objects in a branch. dataScannerCompactLeastObject = 500 // Compact when there is less than this many objects in a branch.
dataScannerCompactAtChildren = 10000 // Compact when there are this many children in a branch. dataScannerCompactAtChildren = 10000 // Compact when there are this many children in a branch.
dataScannerCompactAtFolders = dataScannerCompactAtChildren / 4 // Compact when this many subfolders in a single folder. dataScannerCompactAtFolders = dataScannerCompactAtChildren / 4 // Compact when this many subfolders in a single folder.
dataScannerForceCompactAtFolders = 1_000_000 // Compact when this many subfolders in a single folder (even top level). dataScannerForceCompactAtFolders = 250_000 // Compact when this many subfolders in a single folder (even top level).
dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles. dataScannerStartDelay = 1 * time.Minute // Time to wait on startup and between cycles.
healDeleteDangling = true healDeleteDangling = true
@@ -226,7 +227,9 @@ func runDataScanner(ctx context.Context, objAPI ObjectLayer) {
binary.LittleEndian.PutUint64(tmp, cycleInfo.next) binary.LittleEndian.PutUint64(tmp, cycleInfo.next)
tmp, _ = cycleInfo.MarshalMsg(tmp) tmp, _ = cycleInfo.MarshalMsg(tmp)
err = saveConfig(ctx, objAPI, dataUsageBloomNamePath, tmp) err = saveConfig(ctx, objAPI, dataUsageBloomNamePath, tmp)
scannerLogIf(ctx, err, dataUsageBloomNamePath) if err != nil {
scannerLogIf(ctx, fmt.Errorf("%w, Object %s", err, dataUsageBloomNamePath))
}
} }
} }
} }
@@ -250,6 +253,7 @@ type folderScanner struct {
scanMode madmin.HealScanMode scanMode madmin.HealScanMode
weSleep func() bool weSleep func() bool
shouldHeal func() bool
disks []StorageAPI disks []StorageAPI
disksQuorum int disksQuorum int
@@ -304,11 +308,12 @@ type folderScanner struct {
// The returned cache will always be valid, but may not be updated from the existing. // The returned cache will always be valid, but may not be updated from the existing.
// Before each operation sleepDuration is called which can be used to temporarily halt the scanner. // Before each operation sleepDuration is called which can be used to temporarily halt the scanner.
// If the supplied context is canceled the function will return at the first chance. // If the supplied context is canceled the function will return at the first chance.
func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, cache dataUsageCache, getSize getSizeFn, scanMode madmin.HealScanMode, weSleep func() bool) (dataUsageCache, error) { func scanDataFolder(ctx context.Context, disks []StorageAPI, drive *xlStorage, cache dataUsageCache, getSize getSizeFn, scanMode madmin.HealScanMode, weSleep func() bool) (dataUsageCache, error) {
switch cache.Info.Name { switch cache.Info.Name {
case "", dataUsageRoot: case "", dataUsageRoot:
return cache, errors.New("internal error: root scan attempted") return cache, errors.New("internal error: root scan attempted")
} }
basePath := drive.drivePath
updatePath, closeDisk := globalScannerMetrics.currentPathUpdater(basePath, cache.Info.Name) updatePath, closeDisk := globalScannerMetrics.currentPathUpdater(basePath, cache.Info.Name)
defer closeDisk() defer closeDisk()
@@ -328,6 +333,26 @@ func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, ca
disksQuorum: len(disks) / 2, disksQuorum: len(disks) / 2,
} }
var skipHeal atomic.Bool
if globalIsErasure || cache.Info.SkipHealing {
skipHeal.Store(true)
}
// Check if we should do healing at all.
s.shouldHeal = func() bool {
if skipHeal.Load() {
return false
}
if s.healObjectSelect == 0 {
return false
}
if di, _ := drive.DiskInfo(ctx, DiskInfoOptions{}); di.Healing {
skipHeal.Store(true)
return false
}
return true
}
// Enable healing in XL mode. // Enable healing in XL mode.
if globalIsErasure && !cache.Info.SkipHealing { if globalIsErasure && !cache.Info.SkipHealing {
// Do a heal check on an object once every n cycles. Must divide into healFolderInclude // Do a heal check on an object once every n cycles. Must divide into healFolderInclude
@@ -349,6 +374,7 @@ func scanDataFolder(ctx context.Context, disks []StorageAPI, basePath string, ca
// No useful information... // No useful information...
return cache, err return cache, err
} }
s.newCache.forceCompact(dataScannerCompactAtChildren)
s.newCache.Info.LastUpdate = UTCNow() s.newCache.Info.LastUpdate = UTCNow()
s.newCache.Info.NextCycle = cache.Info.NextCycle s.newCache.Info.NextCycle = cache.Info.NextCycle
return s.newCache, nil return s.newCache, nil
@@ -481,14 +507,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
replication: replicationCfg, replication: replicationCfg,
} }
item.heal.enabled = thisHash.modAlt(f.oldCache.Info.NextCycle/folder.objectHealProbDiv, f.healObjectSelect/folder.objectHealProbDiv) && globalIsErasure item.heal.enabled = thisHash.modAlt(f.oldCache.Info.NextCycle/folder.objectHealProbDiv, f.healObjectSelect/folder.objectHealProbDiv) && f.shouldHeal()
item.heal.bitrot = f.scanMode == madmin.HealDeepScan item.heal.bitrot = f.scanMode == madmin.HealDeepScan
// if the drive belongs to an erasure set
// that is already being healed, skip the
// healing attempt on this drive.
item.heal.enabled = item.heal.enabled && f.healObjectSelect > 0
sz, err := f.getSize(item) sz, err := f.getSize(item)
if err != nil && err != errIgnoreFileContrib { if err != nil && err != errIgnoreFileContrib {
wait() // wait to proceed to next entry. wait() // wait to proceed to next entry.
@@ -651,7 +672,7 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
} }
// Scan for healing // Scan for healing
if f.healObjectSelect == 0 || len(abandonedChildren) == 0 { if len(abandonedChildren) == 0 || !f.shouldHeal() {
// If we are not heal scanning, return now. // If we are not heal scanning, return now.
break break
} }
@@ -686,6 +707,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
healObjectsPrefix := color.Green("healObjects:") healObjectsPrefix := color.Green("healObjects:")
for k := range abandonedChildren { for k := range abandonedChildren {
if !f.shouldHeal() {
break
}
bucket, prefix := path2BucketObject(k) bucket, prefix := path2BucketObject(k)
stopFn := globalScannerMetrics.time(scannerMetricCheckMissing) stopFn := globalScannerMetrics.time(scannerMetricCheckMissing)
f.updateCurrentPath(k) f.updateCurrentPath(k)
@@ -719,6 +743,10 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
}, },
// Some disks have data for this. // Some disks have data for this.
partial: func(entries metaCacheEntries, errs []error) { partial: func(entries metaCacheEntries, errs []error) {
if !f.shouldHeal() {
cancel()
return
}
entry, ok := entries.resolve(&resolver) entry, ok := entries.resolve(&resolver)
if !ok { if !ok {
// check if we can get one entry at least // check if we can get one entry at least
@@ -771,7 +799,9 @@ func (f *folderScanner) scanFolder(ctx context.Context, folder cachedFolder, int
}, madmin.HealItemObject) }, madmin.HealItemObject)
stopFn(int(ver.Size)) stopFn(int(ver.Size))
if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) { if !isErrObjectNotFound(err) && !isErrVersionNotFound(err) {
scannerLogIf(ctx, err, fiv.Name) if err != nil {
scannerLogIf(ctx, fmt.Errorf("%w, Object %s/%s/%s", err, bucket, fiv.Name, ver.VersionID))
}
} }
if err == nil { if err == nil {
successVersions++ successVersions++
@@ -1232,36 +1262,29 @@ func evalActionFromLifecycle(ctx context.Context, lc lifecycle.Lifecycle, lr loc
} }
func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) bool { func applyTransitionRule(event lifecycle.Event, src lcEventSrc, obj ObjectInfo) bool {
if obj.DeleteMarker { if obj.DeleteMarker || obj.IsDir {
return false return false
} }
globalTransitionState.queueTransitionTask(obj, event, src) globalTransitionState.queueTransitionTask(obj, event, src)
return true return true
} }
func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool { func applyExpiryOnTransitionedObject(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) (ok bool) {
var err error timeILM := globalScannerMetrics.timeILM(lcEvent.Action)
defer func() { if err := expireTransitionedObject(ctx, objLayer, &obj, lcEvent, src); err != nil {
if err != nil {
return
}
// Note: DeleteAllVersions action is not supported for
// transitioned objects
globalScannerMetrics.timeILM(lcEvent.Action)(1)
}()
if err = expireTransitionedObject(ctx, objLayer, &obj, lcEvent, src); err != nil {
if isErrObjectNotFound(err) || isErrVersionNotFound(err) { if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
return false return false
} }
ilmLogIf(ctx, err) ilmLogIf(ctx, fmt.Errorf("expireTransitionedObject(%s, %s): %w", obj.Bucket, obj.Name, err))
return false return false
} }
timeILM(1)
// Notification already sent in *expireTransitionedObject*, just return 'true' here. // Notification already sent in *expireTransitionedObject*, just return 'true' here.
return true return true
} }
func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) bool { func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLayer, obj ObjectInfo, lcEvent lifecycle.Event, src lcEventSrc) (ok bool) {
traceFn := globalLifecycleSys.trace(obj) traceFn := globalLifecycleSys.trace(obj)
opts := ObjectOptions{ opts := ObjectOptions{
Expiration: ExpirationOptions{Expire: true}, Expiration: ExpirationOptions{Expire: true},
@@ -1283,8 +1306,10 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
dobj ObjectInfo dobj ObjectInfo
err error err error
) )
timeILM := globalScannerMetrics.timeILM(lcEvent.Action)
defer func() { defer func() {
if err != nil { if !ok {
return return
} }
@@ -1293,7 +1318,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
if lcEvent.Action.DeleteAll() { if lcEvent.Action.DeleteAll() {
numVersions = uint64(obj.NumVersions) numVersions = uint64(obj.NumVersions)
} }
globalScannerMetrics.timeILM(lcEvent.Action)(numVersions) timeILM(numVersions)
} }
}() }()
@@ -1303,7 +1328,7 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
return false return false
} }
// Assume it is still there. // Assume it is still there.
ilmLogOnceIf(ctx, err, "non-transition-expiry") ilmLogOnceIf(ctx, fmt.Errorf("DeleteObject(%s, %s): %w", obj.Bucket, obj.Name, err), "non-transition-expiry"+obj.Name)
return false return false
} }
if dobj.Name == "" { if dobj.Name == "" {
+47
View File
@@ -729,6 +729,53 @@ func (d *dataUsageCache) reduceChildrenOf(path dataUsageHash, limit int, compact
} }
} }
// forceCompact will force compact the cache of the top entry.
// If the number of children is more than limit*100, it will compact self.
// When above the limit a cleanup will also be performed to remove any possible abandoned entries.
func (d *dataUsageCache) forceCompact(limit int) {
if d == nil || len(d.Cache) <= limit {
return
}
top := hashPath(d.Info.Name).Key()
topE := d.find(top)
if topE == nil {
scannerLogIf(GlobalContext, errors.New("forceCompact: root not found"))
return
}
// If off by 2 orders of magnitude, compact self and log error.
if len(topE.Children) > dataScannerForceCompactAtFolders {
// If we still have too many children, compact self.
scannerLogOnceIf(GlobalContext, fmt.Errorf("forceCompact: %q has %d children. Force compacting. Expect reduced scanner performance", d.Info.Name, len(topE.Children)), d.Info.Name)
d.reduceChildrenOf(hashPath(d.Info.Name), limit, true)
}
if len(d.Cache) <= limit {
return
}
// Check for abandoned entries.
found := make(map[string]struct{}, len(d.Cache))
// Mark all children recursively
var mark func(entry dataUsageEntry)
mark = func(entry dataUsageEntry) {
for k := range entry.Children {
found[k] = struct{}{}
if ch, ok := d.Cache[k]; ok {
mark(ch)
}
}
}
found[top] = struct{}{}
mark(*topE)
// Delete all entries not found.
for k := range d.Cache {
if _, ok := found[k]; !ok {
delete(d.Cache, k)
}
}
}
// StringAll returns a detailed string representation of all entries in the cache. // StringAll returns a detailed string representation of all entries in the cache.
func (d *dataUsageCache) StringAll() string { func (d *dataUsageCache) StringAll() string {
// Remove bloom filter from print. // Remove bloom filter from print.
+20 -6
View File
@@ -26,6 +26,9 @@ import (
"path" "path"
"path/filepath" "path/filepath"
"testing" "testing"
"time"
"github.com/minio/minio/internal/cachevalue"
) )
type usageTestFile struct { type usageTestFile struct {
@@ -61,10 +64,13 @@ func TestDataUsageUpdate(t *testing.T) {
} }
return return
} }
xls := xlStorage{drivePath: base, diskInfoCache: cachevalue.New[DiskInfo]()}
xls.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{}, func(ctx context.Context) (DiskInfo, error) {
return DiskInfo{Total: 1 << 40, Free: 1 << 40}, nil
})
weSleep := func() bool { return false } weSleep := func() bool { return false }
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep) got, err := scanDataFolder(context.Background(), nil, &xls, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -174,7 +180,7 @@ func TestDataUsageUpdate(t *testing.T) {
} }
// Changed dir must be picked up in this many cycles. // Changed dir must be picked up in this many cycles.
for i := 0; i < dataUsageUpdateDirCycles; i++ { for i := 0; i < dataUsageUpdateDirCycles; i++ {
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0, weSleep) got, err = scanDataFolder(context.Background(), nil, &xls, got, getSize, 0, weSleep)
got.Info.NextCycle++ got.Info.NextCycle++
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -283,8 +289,12 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
} }
weSleep := func() bool { return false } weSleep := func() bool { return false }
xls := xlStorage{drivePath: base, diskInfoCache: cachevalue.New[DiskInfo]()}
xls.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{}, func(ctx context.Context) (DiskInfo, error) {
return DiskInfo{Total: 1 << 40, Free: 1 << 40}, nil
})
got, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize, 0, weSleep) got, err := scanDataFolder(context.Background(), nil, &xls, dataUsageCache{Info: dataUsageCacheInfo{Name: "bucket"}}, getSize, 0, weSleep)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -419,7 +429,7 @@ func TestDataUsageUpdatePrefix(t *testing.T) {
} }
// Changed dir must be picked up in this many cycles. // Changed dir must be picked up in this many cycles.
for i := 0; i < dataUsageUpdateDirCycles; i++ { for i := 0; i < dataUsageUpdateDirCycles; i++ {
got, err = scanDataFolder(context.Background(), nil, base, got, getSize, 0, weSleep) got, err = scanDataFolder(context.Background(), nil, &xls, got, getSize, 0, weSleep)
got.Info.NextCycle++ got.Info.NextCycle++
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -567,8 +577,12 @@ func TestDataUsageCacheSerialize(t *testing.T) {
} }
return return
} }
xls := xlStorage{drivePath: base, diskInfoCache: cachevalue.New[DiskInfo]()}
xls.diskInfoCache.InitOnce(time.Second, cachevalue.Opts{}, func(ctx context.Context) (DiskInfo, error) {
return DiskInfo{Total: 1 << 40, Free: 1 << 40}, nil
})
weSleep := func() bool { return false } weSleep := func() bool { return false }
want, err := scanDataFolder(context.Background(), nil, base, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep) want, err := scanDataFolder(context.Background(), nil, &xls, dataUsageCache{Info: dataUsageCacheInfo{Name: bucket}}, getSize, 0, weSleep)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
+3 -4
View File
@@ -61,10 +61,9 @@ func NewDummyDataGen(totalLength, skipOffset int64) io.ReadSeeker {
} }
skipOffset %= int64(len(alphabets)) skipOffset %= int64(len(alphabets))
as := make([]byte, 2*len(alphabets)) const multiply = 100
copy(as, alphabets) as := bytes.Repeat(alphabets, multiply)
copy(as[len(alphabets):], alphabets) b := as[skipOffset : skipOffset+int64(len(alphabets)*(multiply-1))]
b := as[skipOffset : skipOffset+int64(len(alphabets))]
return &DummyDataGen{ return &DummyDataGen{
length: totalLength, length: totalLength,
b: b, b: b,
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
"github.com/minio/pkg/v2/policy" "github.com/minio/pkg/v3/policy"
) )
// Data types used for returning dummy tagging XML. // Data types used for returning dummy tagging XML.
+21 -9
View File
@@ -134,13 +134,18 @@ func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
SSES3SinglePartObjects := make(map[int]bool) SSES3SinglePartObjects := make(map[int]bool)
for i, object := range batch { for i, object := range batch {
if kind, ok := crypto.IsEncrypted(object.UserDefined); ok && kind == crypto.S3 && !crypto.IsMultiPart(object.UserDefined) { if kind, ok := crypto.IsEncrypted(object.UserDefined); ok && kind == crypto.S3 && !crypto.IsMultiPart(object.UserDefined) {
ETag, err := etag.Parse(object.ETag)
if err != nil {
continue
}
if ETag.IsEncrypted() {
SSES3SinglePartObjects[i] = true SSES3SinglePartObjects[i] = true
metadata = append(metadata, object.UserDefined) metadata = append(metadata, object.UserDefined)
buckets = append(buckets, object.Bucket) buckets = append(buckets, object.Bucket)
names = append(names, object.Name) names = append(names, object.Name)
} }
} }
}
// If there are no SSE-S3 single-part objects // If there are no SSE-S3 single-part objects
// we can skip the decryption process. However, // we can skip the decryption process. However,
@@ -190,7 +195,7 @@ func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
if err != nil { if err != nil {
return err return err
} }
if SSES3SinglePartObjects[i] && ETag.IsEncrypted() { if SSES3SinglePartObjects[i] {
ETag, err = etag.Decrypt(keys[0][:], ETag) ETag, err = etag.Decrypt(keys[0][:], ETag)
if err != nil { if err != nil {
return err return err
@@ -1025,9 +1030,11 @@ func DecryptObjectInfo(info *ObjectInfo, r *http.Request) (encrypted bool, err e
if encrypted { if encrypted {
if crypto.SSEC.IsEncrypted(info.UserDefined) { if crypto.SSEC.IsEncrypted(info.UserDefined) {
if !(crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers)) { if !(crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers)) {
if r.Header.Get(xhttp.MinIOSourceReplicationRequest) != "true" {
return encrypted, errEncryptedObject return encrypted, errEncryptedObject
} }
} }
}
if crypto.S3.IsEncrypted(info.UserDefined) && r.Header.Get(xhttp.AmzCopySource) == "" { if crypto.S3.IsEncrypted(info.UserDefined) && r.Header.Get(xhttp.AmzCopySource) == "" {
if crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers) { if crypto.SSEC.IsRequested(headers) || crypto.SSECopy.IsRequested(headers) {
@@ -1077,13 +1084,16 @@ func metadataEncrypter(key crypto.ObjectKey) objectMetaEncryptFn {
} }
// metadataDecrypter reverses metadataEncrypter. // metadataDecrypter reverses metadataEncrypter.
func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn { func (o *ObjectInfo) metadataDecrypter(h http.Header) objectMetaDecryptFn {
return func(baseKey string, input []byte) ([]byte, error) { return func(baseKey string, input []byte) ([]byte, error) {
if len(input) == 0 { if len(input) == 0 {
return input, nil return input, nil
} }
var key []byte
key, err := decryptObjectMeta(nil, o.Bucket, o.Name, o.UserDefined) if k, err := crypto.SSEC.ParseHTTP(h); err == nil {
key = k[:]
}
key, err := decryptObjectMeta(key, o.Bucket, o.Name, o.UserDefined)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1095,13 +1105,13 @@ func (o *ObjectInfo) metadataDecrypter() objectMetaDecryptFn {
// decryptChecksums will attempt to decode checksums and return it/them if set. // decryptChecksums will attempt to decode checksums and return it/them if set.
// if part > 0, and we have the checksum for the part that will be returned. // if part > 0, and we have the checksum for the part that will be returned.
func (o *ObjectInfo) decryptPartsChecksums() { func (o *ObjectInfo) decryptPartsChecksums(h http.Header) {
data := o.Checksum data := o.Checksum
if len(data) == 0 { if len(data) == 0 {
return return
} }
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted { if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
decrypted, err := o.metadataDecrypter()("object-checksum", data) decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
if err != nil { if err != nil {
encLogIf(GlobalContext, err) encLogIf(GlobalContext, err)
return return
@@ -1157,15 +1167,17 @@ func (o *ObjectInfo) metadataEncryptFn(headers http.Header) (objectMetaEncryptFn
// decryptChecksums will attempt to decode checksums and return it/them if set. // decryptChecksums will attempt to decode checksums and return it/them if set.
// if part > 0, and we have the checksum for the part that will be returned. // if part > 0, and we have the checksum for the part that will be returned.
func (o *ObjectInfo) decryptChecksums(part int) map[string]string { func (o *ObjectInfo) decryptChecksums(part int, h http.Header) map[string]string {
data := o.Checksum data := o.Checksum
if len(data) == 0 { if len(data) == 0 {
return nil return nil
} }
if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted { if _, encrypted := crypto.IsEncrypted(o.UserDefined); encrypted {
decrypted, err := o.metadataDecrypter()("object-checksum", data) decrypted, err := o.metadataDecrypter(h)("object-checksum", data)
if err != nil { if err != nil {
if err != crypto.ErrSecretKeyMismatch {
encLogIf(GlobalContext, err) encLogIf(GlobalContext, err)
}
return nil return nil
} }
data = decrypted data = decrypted
+2 -2
View File
@@ -28,8 +28,8 @@ import (
"github.com/cespare/xxhash/v2" "github.com/cespare/xxhash/v2"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/pkg/v2/ellipses" "github.com/minio/pkg/v3/ellipses"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
) )
// This file implements and supports ellipses pattern for // This file implements and supports ellipses pattern for
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/minio/pkg/v2/ellipses" "github.com/minio/pkg/v3/ellipses"
) )
// Tests create endpoints with ellipses and without. // Tests create endpoints with ellipses and without.
+10 -2
View File
@@ -36,8 +36,8 @@ import (
"github.com/minio/minio/internal/config" "github.com/minio/minio/internal/config"
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/minio/internal/mountinfo" "github.com/minio/minio/internal/mountinfo"
"github.com/minio/pkg/v2/env" "github.com/minio/pkg/v3/env"
xnet "github.com/minio/pkg/v2/net" xnet "github.com/minio/pkg/v3/net"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@@ -253,6 +253,14 @@ type PoolEndpoints struct {
// EndpointServerPools - list of list of endpoints // EndpointServerPools - list of list of endpoints
type EndpointServerPools []PoolEndpoints type EndpointServerPools []PoolEndpoints
// ESCount returns the total number of erasure sets in this cluster
func (l EndpointServerPools) ESCount() (count int) {
for _, p := range l {
count += p.SetCount
}
return
}
// GetNodes returns a sorted list of nodes in this cluster // GetNodes returns a sorted list of nodes in this cluster
func (l EndpointServerPools) GetNodes() (nodes []Node) { func (l EndpointServerPools) GetNodes() (nodes []Node) {
nodesMap := make(map[string]Node) nodesMap := make(map[string]Node)
+1 -1
View File
@@ -25,7 +25,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/minio/pkg/v2/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
) )
func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) { func (er erasureObjects) getOnlineDisks() (newDisks []StorageAPI) {

Some files were not shown because too many files have changed in this diff Show More