Compare commits

..

77 Commits

Author SHA1 Message Date
Harshavardhana 4b6eadbd80 update deps (#20851) 2025-01-17 16:31:37 -08:00
Shubhendu 6f47414b23 Correct bucket metrics name (#20823)
Earlier, cluster and bucket metrics were named 
`minio_usage_last_activity_nano_seconds`.

The bucket level is now named as 
`minio_bucket_usage_last_activity_nano_seconds`

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2025-01-17 13:12:21 -08:00
Harshavardhana 224a27992a update go-deps on debugging tools 2025-01-17 11:41:46 -08:00
Harshavardhana 232544e1d8 update inspect export, which was missed from #20846 2025-01-17 11:38:44 -08:00
Anis Eleuch dbcb71828d s3: Provide enough buffer when the object final size is unknown (#20847)
When compression is enabled, the final object size is not calculated in
that case, we need to make sure that the provided buffer is always
more significant than the shard size, the bitrot will always calculate 
the hash of blocks with shard size, except the last block.
2025-01-17 11:19:30 -08:00
Klaus Post b9196757fd Fix inconsistently written compressed files. (#20846)
Before https://github.com/minio/minio/pull/20575, files could pick up indices 
from unrelated files if no index was added.

This would result in these files not being consistent across a set.

When loading, search for the compression indicators and check if they 
are within the problematic date range, and clean up any parts that have 
an index but shouldn't.

The test validates that the signature matches the one in files stored without an index.

Bumps xlMetaVersion, so this check doesn't have to be made for future versions.
2025-01-17 11:17:18 -08:00
Andreas Auernhammer b4ac53d157 update github.com/minio/kms-go/kes to v0.3.1 (#20843)
Signed-off-by: Andreas Auernhammer <github@aead.dev>
2025-01-16 01:13:28 -08:00
Poorna 4952bdb770 decom: avoid skipping single delete markers for replication (#20836)
It is possible delete marker was received on old pool as decom
move in progress, this PR allows decom retry to ensure these
delete markers are moved to new pool so that decommission can
be completed.

Fixes #20819
2025-01-14 11:53:02 -08:00
Anis Eleuch 00b2ef2932 Bump golang.org/x/net to silence wrong vuln checker (#20814) 2025-01-08 16:39:24 +05:30
Klaus Post 4536ecfaa4 Add cpuio profiling potential crash workaround (#20809)
Add profiling potential crash wourkaround

Using admin traces could potentially crash the server (or handler more likely) due to upstream divide by 0: https://github.com/felixge/fgprof/pull/34

Ensure the profile always runs 100ms before stopping, so sample count isn't 0 (default sample rate ~10ms/sample, but allow for cpu starvation)
2025-01-06 21:21:54 +05:30
Harshavardhana 43a7402968 update helm release v5.4.0 2025-01-02 21:34:47 -08:00
Allan Roger Reid 330dca9a35 Add resiliency tests (#20786) 2024-12-20 20:24:45 -08:00
Klaus Post ddd137d317 ListObjectParts should return actual size (#20782)
Fixes #20781:

```
λ aws --endpoint-url http://127.0.0.1:9001 s3api list-parts --bucket testbucket --key test.testcompress --upload-id "ZDM0YzUwM2YtZWM1Zi00NWI2LTgxMzYtZTIwMGE3Yjc0Y2Y1LjYyMzgyMmFhLWU2N2QtNGUyYS04NDE1LWUzZDFlZmJmMWUyZHgxNzM0NjI1MjgyMDkyNzY4MDAw"
{
    "Parts": [
        {
            "PartNumber": 1,
            "LastModified": "2024-12-19T16:47:04.334000+00:00",
            "ETag": "\"7025f242f56479e06c435c0b500cdbb2\"",
            "Size": 2002
        }
    ],
    "ChecksumAlgorithm": "",
    "Initiator": {
        "ID": "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4",
        "DisplayName": "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
    },
    "Owner": {
        "DisplayName": "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4",
        "ID": "02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4"
    },
    "StorageClass": "STANDARD"
}
```

(for whatever reason the python script generated a 2002 byte file ;)
2024-12-19 23:51:46 +05:30
Minio Trusted 06ddd8770e Update yaml files to latest version RELEASE.2024-12-18T13-15-44Z 2024-12-19 00:24:40 +00:00
Anis Eleuch 16f8cf1c52 heal: Include more use case of not healable but readable objects (#248) (#20776)
If one object has many parts where all parts are readable but some parts
are missing from some drives, this object can be sometimes un-healable,
which is wrong.

This commit will avoid reading from drives that have missing, corrupted or
outdated xl.meta. It will also check if any part is unreadable to avoid
healing in that case.
2024-12-18 05:15:44 -08:00
Mark Theunissen 01e520eb23 s3: Sanitize the source object name in CopyObject handler (#20774) 2024-12-17 07:01:07 -08:00
Harshavardhana 02f770a0c0 update all dependencies and use latest msgp (#20768) 2024-12-16 04:20:12 +05:30
dependabot[bot] 2f4c79bc0f Bump golang.org/x/crypto from 0.29.0 to 0.31.0 (#20767)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.29.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.29.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-14 23:04:23 -08:00
dependabot[bot] 969ee7dfbe Bump golang.org/x/crypto from 0.23.0 to 0.31.0 in /docs/debugging/inspect (#20760)
Bump golang.org/x/crypto in /docs/debugging/inspect

Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.23.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-14 22:04:58 -08:00
Minio Trusted 5f0b086b05 Update yaml files to latest version RELEASE.2024-12-13T22-19-12Z 2024-12-15 03:52:57 +00:00
Artur Melanchyk 68b004a48f fix: specify size in some map allocations (#20764) 2024-12-13 14:19:12 -08:00
Harshavardhana 54ecce66f0 move readLock on uploadId at EOF in PutObjectPart (#258)
we do not need to hold the read locks at the higher
layer instead before reading the body, instead hold
the read locks properly at the time of renamePart()
for protection from racy part overwrites to compete
with concurrent completeMultipart().
2024-12-13 14:06:18 -08:00
Artur Melanchyk 2b008c598b fix: replace mutex with atomic (#20762) 2024-12-13 19:32:46 +05:30
dependabot[bot] 86d02b17cf Bump golang.org/x/crypto from 0.23.0 to 0.31.0 in /docs/debugging/s3-verify (#20757)
Bump golang.org/x/crypto in /docs/debugging/s3-verify

Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.23.0 to 0.31.0.
- [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.31.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-12 05:00:05 -08:00
Anis Eleuch c1a95a70ac heal: Move CheckParts from single handler to streaming RPC (#20755)
CheckParts call can take time to verify 10k parts of a single object in a single drive.
To avoid an internal dealine of one minute in the single handler RPC, this commit will
switch to streaming RPC instead.
2024-12-12 11:20:57 +05:30
Aditya Manthramurthy f246c9053f fix: Privilege escalation in IAM import API (#20756)
This API had missing permissions checking, allowing a user to change
their policy mapping by:

1. Craft iam-info.zip file: Update own user permission in
user_mappings.json
2. Upload it via `mc admin cluster iam import nobody iam-info.zip`

Here `nobody` can be a user with pretty much any kind of permission (but
not anonymous) and this ends up working.

Some more detailed steps - start from a fresh setup:

```
./minio server /tmp/d{1...4} &
mc alias set myminio http://localhost:9000 minioadmin minioadmin
mc admin user add myminio nobody nobody123
mc admin policy attach myminio readwrite nobody nobody123
mc alias set nobody http://localhost:9000 nobody nobody123

mc admin cluster iam export myminio
mkdir /tmp/x && mv myminio-iam-info.zip /tmp/x
cd /tmp/x
unzip myminio-iam-info.zip
echo '{"nobody":{"version":1,"policy":"consoleAdmin","updatedAt":"2024-08-13T19:47:10.1Z"}}' > \
      iam-assets/user_mappings.json
zip -r myminio-iam-info-updated.zip iam-assets/

mc admin cluster iam import nobody ./myminio-iam-info-updated.zip
mc admin service restart nobody
```
2024-12-12 07:39:40 +05:30
Cesar N. 9cdd204ae4 Upgrade Console version to v1.7.5 (#20748) 2024-12-11 16:23:53 +05:30
Harshavardhana 7b3eb9f7f8 fix: groups lookup performance issue with users with lots of groups (#20740)
fixes https://github.com/minio/minio/issues/20717
2024-12-11 16:23:28 +05:30
ebozduman d56ef8dbe1 Adds AIstore documentation link (#20738) 2024-12-11 16:22:17 +05:30
Mark Theunissen a248ed5ff5 Fixes for POST policy checks and the x-ignore implementation (#20674) 2024-12-11 16:21:34 +05:30
Klaus Post 5bb31e4883 Disable mint full object tests (#20743)
Remove expected failures from https://github.com/minio/minio-go/pull/2026
2024-12-09 18:59:22 -08:00
Taran Pelkey aff2a76d80 Return error when attempting to create a policy with commas in name (#20724) 2024-12-04 03:51:26 -08:00
Anis Eleuch eddbe6bca2 heal: Report bucket healing result correctly (#20721) 2024-12-04 04:42:25 +05:30
Anis Eleuch 734d1e320a heal: Single object heal to look for older versions as well (#203) (#20723)
`mc admin heal ALIAS/bucket/object` does not have any flag to heal
object noncurrent versions, this commit will make healing of the object
noncurrent versions implicitly asked.

This also fixes the 'mc admin heal ALIAS/bucket/object' that does not work 
correctly when the bucket is versioned. This has been broken since Apr 2023.
2024-12-04 04:42:04 +05:30
Anis Eleuch b8dab7b1a9 Set http server read/write timeout from --idle-timeout (#228) (#20715)
Golang http.Server will call SetReadDeadline overwriting the previous
deadline configuration set after a new connection Accept in the custom
listener code. Therefore, --idle-timeout was not correctly respected.

Make http.Server read/write timeout similar to --idle-timeout.
2024-12-02 18:51:17 +05:30
Klaus Post abd6bf060d Add 'X-Forwarded-For' to (s)FTP requests (#20709)
Fixes #20707
2024-11-29 18:25:37 +05:30
Alex f0d4ef604c Updated Console to v1.7.4 (#20693)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2024-11-28 13:14:11 +05:30
Ramon de Klein 2712f75762 prevent IAM cleanup errors (#20691) 2024-11-28 13:13:51 +05:30
Krutika Dhananjay 4c46668da8 Add a test case for fix #20684 (#20688)
The test fails without the change.
Also, removed a duplicate test case involving lifecycle config with no rules.
2024-11-28 13:13:31 +05:30
Anis Eleuch 02e93fd6ba heal: Better reporting to mc with dangling/timeout errors (#20690)
The code assigns corrupted state to a drive for any unexpected error,
which is confusing for users. This change will make sure to assign
corrupted state only for corrupted parts or xl.meta. Use unknown state
with a explanation for any unexpected error, like canceled, deadline
errors, drive timeout, ...

Also make sure to return the bucket/object name when the object is not
found or marked not found by the heal dangling code.
2024-11-26 10:45:35 -08:00
Krutika Dhananjay 366876e98b Fix prefix validation in lifecycle rule (#20684) 2024-11-25 12:12:21 -08:00
Mark Theunissen d202fdd022 Add the policy name to the audit logs tags when doing policy-based API calls. Add retention settings to tags (#20638)
* Add the policy name to the audit log tags when doing policy-based API calls

* Audit log the retention settings requested in the API call

* Audit log of retention on PutObjectRetention API path too
2024-11-25 09:17:12 -08:00
Eng Zer Jun c07e5b49d4 refactor: replace experimental maps and slices with stdlib (#20679)
The experimental functions are now available in the standard library in
Go 1.23 [1].

[1]: https://go.dev/doc/go1.23#new-unique-package

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-11-25 09:10:22 -08:00
Aditya Manthramurthy 9a39f8ad4d fix: Remove User should fail for a service account (#20677)
The RemoveUser API only removes internal users, and it reports success
when it didnt find the internal user account for deletion. When provided
with a service account, it should not report success as that is misleading.
2024-11-21 18:24:04 -08:00
Nao Yonashiro 7e0c1c9413 feat: bump github.com/cosnicolaou/pbzip2 from 1.0.3 to 1.0.5 (#20671)
Update github.com/cosnicolaou/pbzip2 to latest version for
significant performance improvements. This update brings a 45%
reduction in processing time.
2024-11-20 18:53:52 -08:00
John Morales 485d833cd7 fix: API label casing and count value for +Inf bucket v2 metrics (#20656) 2024-11-18 11:17:52 -08:00
Klaus Post e8a476ef5a Keep larger merge buffers for RPC (#20654)
Keep larger merge buffers

When sending large messages >1K, the merge buffer would continuously be reallocated.

This could happen on listings, where blocks are typically 4->8K.

Keep merge buffer of up to 256KB.

Benchmark with 4096b messages:

```
benchmark                                          old ns/op     new ns/op     delta
BenchmarkRequests/servers=2/bytes/par=32-32        8271          6360          -23.10%
BenchmarkRequests/servers=2/bytes/par=64-32        7840          4731          -39.66%
BenchmarkRequests/servers=2/bytes/par=128-32       7291          4740          -34.99%
BenchmarkRequests/servers=2/bytes/par=256-32       7095          4580          -35.45%
BenchmarkRequests/servers=2/bytes/par=512-32       6757          4584          -32.16%
BenchmarkRequests/servers=2/bytes/par=1024-32      6429          4453          -30.74%

benchmark                                          old bytes     new bytes     delta
BenchmarkRequests/servers=2/bytes/par=32-32        12090         821           -93.21%
BenchmarkRequests/servers=2/bytes/par=64-32        17423         820           -95.29%
BenchmarkRequests/servers=2/bytes/par=128-32       18493         822           -95.56%
BenchmarkRequests/servers=2/bytes/par=256-32       18892         821           -95.65%
BenchmarkRequests/servers=2/bytes/par=512-32       19064         826           -95.67%
BenchmarkRequests/servers=2/bytes/par=1024-32      19038         842           -95.58%
```
2024-11-16 09:18:48 -08:00
Krutika Dhananjay 267f0ecea2 Fix 0 httpTimeout for logger webhook (#20653)
Previously, not setting http.Config.HTTPTimeout for logger webhook
was resulting in a timeout of 0, and causing "deadline exceeded"
errors in log webhook.

This change introduces a new env variable for configuring log webhook
timeout and more importantly sets it when the config is initialised.
2024-11-16 02:01:36 -08:00
Harshavardhana 4ee3434854 updating all dependencies as per regular cadence (#20646) 2024-11-14 12:33:18 -08:00
Anis Eleuch 0e9854372e heal/batch: Fix missing redirection to the first node (#20642)
Manual heal can return XMinioHealInvalidClientToken if the manual
healing is started in the first node, and the next mc call to get the
heal status is landed on another node. The reason is that redirection
based on the token ID is not able to redirect requests to the first node
due to a typo.

This also affects the batch cancel command if the batch is being done in
the first node, the user will never be able to cancel it due to the same
bug.
2024-11-13 04:07:28 -08:00
Klaus Post b5177993b3 Make DeadlineConn http.Listener compatible (#20635)
HTTP likes to slap an infinite read deadline on a connection and 
do a blocking read while the response is being written.

This effectively means that a reading deadline becomes the 
request-response deadline.

Instead of enforcing our timeout, we pass it through and keep 
"infinite deadline" is sticky on connections.

However, we still "record" when reads are aborted, so we never overwrite that.

The HTTP server should have `ReadTimeout` and `IdleTimeout` set for the deadline to be effective.

Use --idle-timeout for incoming connections.
2024-11-12 12:41:41 -08:00
Klaus Post 55f5c18fd9 Harden internode DeadlineConn (#20631)
Since DeadlineConn would send deadline updates directly upstream,
it would race with Read/Write operations. The stdlib will perform a read, 
but do an async SetReadDeadLine(unix(1)) to cancel the Read in 
`abortPendingRead`. In this case, the Read may override the 
deadline intended to cancel the read.

Stop updating deadlines if a deadline in the past is seen and when Close is called. 
A mutex now protects all upstream deadline calls to avoid races. 

This should fix the short-term buildup of...

```
365 @ 0x44112e 0x4756b9 0x475699 0x483525 0x732286 0x737407 0x73816b 0x479601
#	0x475698	sync.runtime_notifyListWait+0x138		runtime/sema.go:569
#	0x483524	sync.(*Cond).Wait+0x84				sync/cond.go:70
#	0x732285	net/http.(*connReader).abortPendingRead+0xa5	net/http/server.go:729
#	0x737406	net/http.(*response).finishRequest+0x86		net/http/server.go:1676
#	0x73816a	net/http.(*conn).serve+0x62a			net/http/server.go:2050
```

AFAICT Only affects internode calls that create a connection (non-grid).
2024-11-11 09:15:17 -08:00
Harshavardhana 8ce101c174 fix: LDAP service port number in tests 2024-11-11 07:01:29 -08:00
Klaus Post 4972735507 Fix lint issues from v1.62.0 upgrade (#20633)
* Fix lint issues from v1.62.0 upgrade

* Fix xlMetaV2TrimData version checks.
2024-11-11 06:51:43 -08:00
Minio Trusted e6ca6de194 Update yaml files to latest version RELEASE.2024-11-07T00-52-20Z 2024-11-07 23:58:42 +00:00
Harshavardhana cefc43e4da simplify the Get()/GetMultiple() re-use GetRaw() for both (#179)
Remember GetMultiple() must be used if your target is calling
PutMultiple(), without that the multiple events will not be
replayed.
2024-11-06 16:52:20 -08:00
Ramon de Klein 25e34fda5f decompress audit log properly before sending to remote target (#20619) 2024-11-06 13:25:24 -08:00
Erfan 4208d7af5a docs: remove redundant prometheus metric (#20618) 2024-11-06 07:44:21 -08:00
Klaus Post 8d42f37e4b Fix msgUnPath crash (#20614)
These are needed checks for the functions to be un-crashable with any input 
given to `msgUnPath` (tested with fuzzing).

Both conditions would result in a crash, which prevents that. Some 
additional upstream checks are needed.

Fixes #20610
2024-11-05 04:37:59 -08:00
dependabot[bot] 7cb4b5c636 Bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#20611)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-05 02:22:36 -08:00
Harshavardhana 1615920f48 fix typos reported in CI/CD 2024-11-04 11:06:02 -08:00
Cesar N. 7ee42b3ff5 Update console package to v1.7.3 (#20606) 2024-11-04 08:47:08 -08:00
Harshavardhana a6f1e727fb add tests for ILM transition and healing (#166) (#20601)
This PR fixes a regression introduced in https://github.com/minio/minio/pull/19797
by restoring the healing ability of transitioned objects

Bonus: support for transitioned objects to carry original
The object name is for future reverse lookups if necessary.

Also fix parity calculation for tiered objects to n/2 for n/2 == (parity)
2024-10-31 15:10:24 -07:00
Aditya Manthramurthy c1fc7779ca Remove expires field from list objects metadata (#20600)
This field was always 0 regardless of whether the object had an expiry
so we are basically removing dead code.
2024-10-31 12:27:06 -07:00
Allan Roger Reid b3ab7546ee Fix typos in README.md (#20599) 2024-10-31 10:38:53 -07:00
Minio Trusted ad88a81e3d Update yaml files to latest version RELEASE.2024-10-29T16-01-48Z 2024-10-30 21:24:58 +00:00
Aditya Manthramurthy c4239ced22 run IAM purge routines deterministically every hr (#20587)
Existing implementation runs IAM purge routines for expired LDAP and
OIDC accounts with a probability of 0.25 after every IAM refresh. This
change ensures that they are run once in each hour.
2024-10-29 09:01:48 -07:00
Anis Eleuch f85c28e960 heal: large objects fix and avoid .healing.bin corner case premature exit (#20577)
xlStorage.Healing() returns nil if there is an error reading
.healing.bin or if this latter is empty. healing.bin update()
call returns early if .healing.bin is empty; hence, no further update
of .healing.bin is possible.

A .healing.bin can be empty if os.Open() with O_TRUNC is successful
but the next Write returns an error.

To avoid this weird situation, avoid making healingTracker.update()
to return early if .healing.bin is empty, so write again.

This commit also fixes wrong error log printing when an object is 
healed in another drive in the same erasure set but not in the drive 
that is actively healing by fresh drive healing code. Currently, it prints 
<nil> instead of a factual error.

* heal: Scan .minio.sys metadata only during site-wide heal (#137)

mc admin heal always invoke .minio.sys heal, but sometimes, this latter
contains a lot of data, many service accounts, STS accounts etc, which
makes mc admin heal command very slow.

Only invoke .minio.sys healing when no bucket was specified in `mc admin
heal` command.
2024-10-26 02:58:27 -07:00
Anis Eleuch f7e176d4ca heal: Avoid deadline error with very large objects (#140) (#20586)
Healing a large object with a normal scan mode where no parts read 
is involved can still fail after 30 seconds if an object has

There are too many parts when hard disks are being used mainly. 
The reason is there is a general deadline that checks for all parts we 
do a deadline per part.
2024-10-26 02:56:26 -07:00
Aditya Manthramurthy 72a0d14195 fix: avoid useless expires value in listing meta (#20584)
When listing objects with metadata, avoid returning an "expires" time
metadata value when its value is the zero time as this means that no
expires value is set on the object.
2024-10-24 19:13:19 -07:00
Klaus Post 6abe4128d7 Fix ILM expire workers exiting (#20578)
Fix expire workers exiting

Under 2 conditions ILM expire workers would exit, eventually causing all workers to terminate.
2024-10-23 08:35:37 -07:00
Klaus Post ed5ed7e490 Trace ILM errors (#20576)
Some paths would attempt transitions but in case of failures 
no traces would be emitted.

Add traces (with errors) when transition operations fail.
2024-10-22 14:10:34 -07:00
Klaus Post 51410c9023 Clear omitted fields (#20575)
Searched `msg:"[a-zA-Z0-9]*,omitempty` through the codebase.

Uses latest tinylib master.
2024-10-22 08:30:50 -07:00
Shubhendu 96ca402dcd Correct the date filter check for batch replication (#20569)
The condition were incorrect as we were comparing the filter
value against the modification time object.

For example if created after filter date is after modification
time of object, that means object was created before the filter
time and should be skipped while replication because as per the
filter we need only the objects created after the filter date.

Signed-off-by: Shubhendu Ram Tripathi <shubhendu@minio.io>
2024-10-18 08:32:09 -07:00
Anis Eleuch 3da7c9cce3 repl: Fix removal of replicator svc when keycloak is configured (#120)
When Keycloak vendor is set, the code will start to clean up service
accounts that parents do not exist anymore. However, the code will also
look for the parent user of site-replicator-0, MINIO_ROOT_USER, which
obviously does not exist in Keycloak. Therefore, the site-replicator-0
will be removed automatically.

This commit will avoid cleaning up service accounts generated from
the root user.
2024-10-14 09:35:37 -07:00
Harshavardhana a14e19ec54 remove support for s390x 2024-10-13 07:06:17 -07:00
Minio Trusted e091dde041 Update yaml files to latest version RELEASE.2024-10-13T13-34-11Z 2024-10-13 14:05:41 +00:00
151 changed files with 8427 additions and 2458 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
+39
View File
@@ -0,0 +1,39 @@
name: Resiliency Functional Tests
on:
pull_request:
branches:
- master
# This ensures that previous jobs for the PR are canceled when the PR is
# updated.
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
name: Go ${{ matrix.go-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [1.23.x]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
check-latest: true
- name: Build on ${{ matrix.os }}
if: matrix.os == 'ubuntu-latest'
env:
CGO_ENABLED: 0
GO111MODULE: on
run: |
sudo sysctl net.ipv6.conf.all.disable_ipv6=0
sudo sysctl net.ipv6.conf.default.disable_ipv6=0
make test-resiliency
+2 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -39,3 +39,4 @@ jobs:
sudo sysctl net.ipv6.conf.all.disable_ipv6=0 sudo sysctl net.ipv6.conf.all.disable_ipv6=0
sudo sysctl net.ipv6.conf.default.disable_ipv6=0 sudo sysctl net.ipv6.conf.default.disable_ipv6=0
make verify make verify
make test-timeout
+1 -1
View File
@@ -61,7 +61,7 @@ jobs:
# are turned off - i.e. if ldap="", then ldap server is not enabled for # are turned off - i.e. if ldap="", then ldap server is not enabled for
# the tests. # the tests.
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
ldap: ["", "localhost:389"] ldap: ["", "localhost:389"]
etcd: ["", "http://localhost:2379"] etcd: ["", "http://localhost:2379"]
openid: ["", "http://127.0.0.1:5556/dex"] openid: ["", "http://127.0.0.1:5556/dex"]
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
- name: setup-go-step - name: setup-go-step
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: 1.22.x go-version: 1.23.x
- name: github sha short - name: github sha short
id: vars id: vars
+2 -1
View File
@@ -21,7 +21,7 @@ jobs:
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@@ -40,6 +40,7 @@ jobs:
sudo sysctl net.ipv6.conf.all.disable_ipv6=0 sudo sysctl net.ipv6.conf.all.disable_ipv6=0
sudo sysctl net.ipv6.conf.default.disable_ipv6=0 sudo sysctl net.ipv6.conf.default.disable_ipv6=0
make test-ilm make test-ilm
make test-ilm-transition
- name: Test PBAC - name: Test PBAC
run: | run: |
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
+5
View File
@@ -7,6 +7,7 @@ export ACCESS_KEY="$2"
export SECRET_KEY="$3" export SECRET_KEY="$3"
export JOB_NAME="$4" export JOB_NAME="$4"
export MINT_MODE="full" export MINT_MODE="full"
export MINT_NO_FULL_OBJECT="true"
docker system prune -f || true docker system prune -f || true
docker volume prune -f || true docker volume prune -f || true
@@ -15,6 +16,9 @@ docker volume rm $(docker volume ls -f dangling=true) || true
## change working directory ## change working directory
cd .github/workflows/mint cd .github/workflows/mint
## always pull latest
docker pull docker.io/minio/mint:edge
docker-compose -f minio-${MODE}.yaml up -d docker-compose -f minio-${MODE}.yaml up -d
sleep 1m sleep 1m
@@ -35,6 +39,7 @@ docker run --rm --net=mint_default \
-e ACCESS_KEY="${ACCESS_KEY}" \ -e ACCESS_KEY="${ACCESS_KEY}" \
-e SECRET_KEY="${SECRET_KEY}" \ -e SECRET_KEY="${SECRET_KEY}" \
-e ENABLE_HTTPS=0 \ -e ENABLE_HTTPS=0 \
-e MINT_NO_FULL_OBJECT="${MINT_NO_FULL_OBJECT}" \
-e MINT_MODE="${MINT_MODE}" \ -e MINT_MODE="${MINT_MODE}" \
docker.io/minio/mint:edge docker.io/minio/mint:edge
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
go-version: [1.22.x] go-version: [1.23.x]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
+1 -1
View File
@@ -21,7 +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.7 go-version: 1.23.2
- 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
+3346 -62
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.22-alpine as build FROM golang:1.23-alpine as build
ARG TARGETARCH ARG TARGETARCH
ARG RELEASE ARG RELEASE
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.22-alpine AS build FROM golang:1.23-alpine AS build
ARG TARGETARCH ARG TARGETARCH
ARG RELEASE ARG RELEASE
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.22-alpine AS build FROM golang:1.23-alpine AS build
ARG TARGETARCH ARG TARGETARCH
ARG RELEASE ARG RELEASE
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.22-alpine AS build FROM golang:1.23-alpine AS build
ARG TARGETARCH ARG TARGETARCH
ARG RELEASE ARG RELEASE
+13 -1
View File
@@ -24,7 +24,7 @@ help: ## print this help
getdeps: ## fetch necessary dependencies getdeps: ## fetch necessary dependencies
@mkdir -p ${GOPATH}/bin @mkdir -p ${GOPATH}/bin
@echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR) @echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOLANGCI_DIR)
@echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.1.10-0.20240227114326-6d6f813fff1b @echo "Installing msgp" && go install -v github.com/tinylib/msgp@v1.2.5
@echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest @echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
crosscompile: ## cross compile minio crosscompile: ## cross compile minio
@@ -60,6 +60,10 @@ 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-ilm-transition: install-race
@echo "Running ILM tiering tests with healing"
@env bash $(PWD)/docs/bucket/lifecycle/setup_ilm_transition.sh
test-pbac: install-race test-pbac: install-race
@echo "Running bucket policies tests" @echo "Running bucket policies tests"
@env bash $(PWD)/docs/iam/policies/pbac-tests.sh @env bash $(PWD)/docs/iam/policies/pbac-tests.sh
@@ -145,6 +149,10 @@ test-multipart: install-race ## test multipart
@echo "Test multipart behavior when part files are missing" @echo "Test multipart behavior when part files are missing"
@(env bash $(PWD)/buildscripts/multipart-quorum-test.sh) @(env bash $(PWD)/buildscripts/multipart-quorum-test.sh)
test-timeout: install-race ## test multipart
@echo "Test server timeout"
@(env bash $(PWD)/buildscripts/test-timeout.sh)
verify: install-race ## verify minio various setups verify: install-race ## verify minio various setups
@echo "Verifying build with race" @echo "Verifying build with race"
@(env bash $(PWD)/buildscripts/verify-build.sh) @(env bash $(PWD)/buildscripts/verify-build.sh)
@@ -209,6 +217,10 @@ 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
test-resiliency: build
@echo "Running resiliency tests"
@(DOCKER_COMPOSE_FILE=$(PWD)/docs/resiliency/docker-compose.yaml env bash $(PWD)/docs/resiliency/resiliency-tests.sh)
install-race: checks build-debugging ## 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,dev -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
+1 -1
View File
@@ -4,7 +4,7 @@
[![MinIO](https://raw.githubusercontent.com/minio/minio/master/.github/logo.svg?sanitize=true)](https://min.io) [![MinIO](https://raw.githubusercontent.com/minio/minio/master/.github/logo.svg?sanitize=true)](https://min.io)
MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads. MinIO is a High Performance Object Storage released under GNU Affero General Public License v3.0. It is API compatible with Amazon S3 cloud storage service. Use MinIO to build high performance infrastructure for machine learning, analytics and application data workloads. To learn more about what MinIO is doing for AI storage, go to [AI storage documentation](https://min.io/solutions/object-storage-for-ai).
This README provides quickstart instructions on running MinIO on bare metal hardware, including container-based installations. For Kubernetes environments, use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md). This README provides quickstart instructions on running MinIO on bare metal hardware, including container-based installations. For Kubernetes environments, use the [MinIO Kubernetes Operator](https://github.com/minio/operator/blob/master/README.md).
@@ -8,7 +8,7 @@
# #
# This script assumes that LDAP server is at: # This script assumes that LDAP server is at:
# #
# `localhost:1389` # `localhost:389`
# #
# if this is not the case, set the environment variable # if this is not the case, set the environment variable
# `_MINIO_LDAP_TEST_SERVER`. # `_MINIO_LDAP_TEST_SERVER`.
@@ -41,7 +41,7 @@ __init__() {
fi fi
if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then if [ -z "$_MINIO_LDAP_TEST_SERVER" ]; then
export _MINIO_LDAP_TEST_SERVER=localhost:1389 export _MINIO_LDAP_TEST_SERVER=localhost:389
echo "Using default LDAP endpoint: $_MINIO_LDAP_TEST_SERVER" echo "Using default LDAP endpoint: $_MINIO_LDAP_TEST_SERVER"
fi fi
@@ -58,7 +58,7 @@ create_iam_content_in_old_minio() {
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 ready old-minio
mc idp ldap add old-minio \ mc idp ldap add old-minio \
server_addr=localhost:1389 \ server_addr=localhost:389 \
server_insecure=on \ server_insecure=on \
lookup_bind_dn=cn=admin,dc=min,dc=io \ lookup_bind_dn=cn=admin,dc=min,dc=io \
lookup_bind_password=admin \ lookup_bind_password=admin \
+137
View File
@@ -0,0 +1,137 @@
#!/bin/bash
if [ -n "$TEST_DEBUG" ]; then
set -x
fi
WORK_DIR="$PWD/.verify-$RANDOM"
MINIO_CONFIG_DIR="$WORK_DIR/.minio"
MINIO=("$PWD/minio" --config-dir "$MINIO_CONFIG_DIR" server)
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
if [ ! -x "$PWD/minio" ]; then
echo "minio executable binary not found in current directory"
exit 1
fi
trap 'catch $LINENO' ERR
function purge() {
rm -rf "$1"
}
# shellcheck disable=SC2120
catch() {
if [ $# -ne 0 ]; then
echo "error on line $1"
fi
echo "Cleaning up instances of MinIO"
pkill minio || true
pkill -9 minio || true
purge "$WORK_DIR"
if [ $# -ne 0 ]; then
exit $#
fi
}
catch
function gen_put_request() {
hdr_sleep=$1
body_sleep=$2
echo "PUT /testbucket/testobject HTTP/1.1"
sleep $hdr_sleep
echo "Host: foo-header"
echo "User-Agent: curl/8.2.1"
echo "Accept: */*"
echo "Content-Length: 30"
echo ""
sleep $body_sleep
echo "random line 0"
echo "random line 1"
echo ""
echo ""
}
function send_put_object_request() {
hdr_timeout=$1
body_timeout=$2
start=$(date +%s)
timeout 5m bash -c "gen_put_request $hdr_timeout $body_timeout | netcat 127.0.0.1 $start_port | read" || return -1
[ $(($(date +%s) - start)) -gt $((srv_hdr_timeout + srv_idle_timeout + 1)) ] && return -1
return 0
}
function test_minio_with_timeout() {
start_port=$1
export MINIO_ROOT_USER=minio
export MINIO_ROOT_PASSWORD=minio123
export MC_HOST_minio="http://minio:minio123@127.0.0.1:${start_port}/"
export MINIO_CI_CD=1
mkdir ${WORK_DIR}
C_PWD=${PWD}
if [ ! -x "$PWD/mc" ]; then
MC_BUILD_DIR="mc-$RANDOM"
if ! git clone --quiet https://github.com/minio/mc "$MC_BUILD_DIR"; then
echo "failed to download https://github.com/minio/mc"
purge "${MC_BUILD_DIR}"
exit 1
fi
(cd "${MC_BUILD_DIR}" && go build -o "$C_PWD/mc")
# remove mc source.
purge "${MC_BUILD_DIR}"
fi
"${MINIO[@]}" --address ":$start_port" --read-header-timeout ${srv_hdr_timeout}s --idle-timeout ${srv_idle_timeout}s "${WORK_DIR}/disk/" >"${WORK_DIR}/server1.log" 2>&1 &
pid=$!
disown $pid
sleep 1
if ! ps -p ${pid} 1>&2 >/dev/null; then
echo "server1 log:"
cat "${WORK_DIR}/server1.log"
echo "FAILED"
purge "$WORK_DIR"
exit 1
fi
set -e
"${PWD}/mc" mb minio/testbucket
"${PWD}/mc" anonymous set public minio/testbucket
# slow header writing
send_put_object_request 20 0 && exit -1
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
# quick header write and slow bodywrite
send_put_object_request 0 40 && exit -1
"${PWD}/mc" stat minio/testbucket/testobject && exit -1
# quick header and body write
send_put_object_request 1 1 || exit -1
"${PWD}/mc" stat minio/testbucket/testobject || exit -1
}
function main() {
export start_port=$(shuf -i 10000-65000 -n 1)
export srv_hdr_timeout=5
export srv_idle_timeout=5
export -f gen_put_request
test_minio_with_timeout ${start_port}
}
main "$@"
+35 -45
View File
@@ -37,6 +37,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/dns" "github.com/minio/minio/internal/config/dns"
"github.com/minio/minio/internal/logger"
"github.com/minio/mux" "github.com/minio/mux"
xldap "github.com/minio/pkg/v3/ldap" xldap "github.com/minio/pkg/v3/ldap"
"github.com/minio/pkg/v3/policy" "github.com/minio/pkg/v3/policy"
@@ -65,6 +66,17 @@ func (a adminAPIHandlers) RemoveUser(w http.ResponseWriter, r *http.Request) {
return return
} }
// This API only supports removal of internal users not service accounts.
ok, _, err = globalIAMSys.IsServiceAccount(accessKey)
if err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return
}
if ok {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errIAMActionNotAllowed), r.URL)
return
}
// When the user is root credential you are not allowed to // When the user is root credential you are not allowed to
// remove the root user. Also you cannot delete yourself. // remove the root user. Also you cannot delete yourself.
if accessKey == globalActiveCred.AccessKey || accessKey == cred.AccessKey { if accessKey == globalActiveCred.AccessKey || accessKey == cred.AccessKey {
@@ -1568,6 +1580,7 @@ func (a adminAPIHandlers) InfoCannedPolicy(w http.ResponseWriter, r *http.Reques
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTooManyPolicies), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, errTooManyPolicies), r.URL)
return return
} }
setReqInfoPolicyName(ctx, name)
policyDoc, err := globalIAMSys.InfoPolicy(name) policyDoc, err := globalIAMSys.InfoPolicy(name)
if err != nil { if err != nil {
@@ -1671,6 +1684,7 @@ func (a adminAPIHandlers) RemoveCannedPolicy(w http.ResponseWriter, r *http.Requ
vars := mux.Vars(r) vars := mux.Vars(r)
policyName := vars["name"] policyName := vars["name"]
setReqInfoPolicyName(ctx, policyName)
if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil { if err := globalIAMSys.DeletePolicy(ctx, policyName, true); err != nil {
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
@@ -1703,6 +1717,13 @@ func (a adminAPIHandlers) AddCannedPolicy(w http.ResponseWriter, r *http.Request
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
return return
} }
setReqInfoPolicyName(ctx, policyName)
// Reject policy names with commas.
if strings.Contains(policyName, ",") {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrPolicyInvalidName), r.URL)
return
}
// Error out if Content-Length is missing. // Error out if Content-Length is missing.
if r.ContentLength <= 0 { if r.ContentLength <= 0 {
@@ -1768,6 +1789,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
policyName := vars["policyName"] policyName := vars["policyName"]
entityName := vars["userOrGroup"] entityName := vars["userOrGroup"]
isGroup := vars["isGroup"] == "true" isGroup := vars["isGroup"] == "true"
setReqInfoPolicyName(ctx, policyName)
if !isGroup { if !isGroup {
ok, _, err := globalIAMSys.IsTempUser(entityName) ok, _, err := globalIAMSys.IsTempUser(entityName)
@@ -1853,7 +1875,7 @@ func (a adminAPIHandlers) SetPolicyForUserOrGroup(w http.ResponseWriter, r *http
})) }))
} }
// ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/polciy-entities?policy=xxx&user=xxx&group=xxx // ListPolicyMappingEntities - GET /minio/admin/v3/idp/builtin/policy-entities?policy=xxx&user=xxx&group=xxx
func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) { func (a adminAPIHandlers) ListPolicyMappingEntities(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
@@ -1955,6 +1977,7 @@ func (a adminAPIHandlers) AttachDetachPolicyBuiltin(w http.ResponseWriter, r *ht
writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL) writeErrorResponseJSON(ctx, w, toAdminAPIErr(ctx, err), r.URL)
return return
} }
setReqInfoPolicyName(ctx, strings.Join(addedOrRemoved, ","))
respBody := madmin.PolicyAssociationResp{ respBody := madmin.PolicyAssociationResp{
UpdatedAt: updatedAt, UpdatedAt: updatedAt,
@@ -2009,6 +2032,7 @@ func (a adminAPIHandlers) ExportIAM(w http.ResponseWriter, r *http.Request) {
// Get current object layer instance. // Get current object layer instance.
objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction) objectAPI, _ := validateAdminReq(ctx, w, r, policy.ExportIAMAction)
if objectAPI == nil { if objectAPI == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return return
} }
// Initialize a zip writer which will provide a zipped content // Initialize a zip writer which will provide a zipped content
@@ -2231,17 +2255,13 @@ func (a adminAPIHandlers) ImportIAMV2(w http.ResponseWriter, r *http.Request) {
func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiVer string) { func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiVer string) {
ctx := r.Context() ctx := r.Context()
// Get current object layer instance. // Validate signature, permissions and get current object layer instance.
objectAPI := newObjectLayerFn() objectAPI, _ := validateAdminReq(ctx, w, r, policy.ImportIAMAction)
if objectAPI == nil || globalNotificationSys == nil { if objectAPI == nil || globalNotificationSys == nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrServerNotInitialized), r.URL)
return return
} }
cred, owner, s3Err := validateAdminSignature(ctx, r, "")
if s3Err != ErrNone {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(s3Err), r.URL)
return
}
data, err := io.ReadAll(r.Body) data, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrInvalidRequest), r.URL)
@@ -2331,38 +2351,12 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
return return
} }
if (cred.IsTemp() || cred.IsServiceAccount()) && cred.ParentUser == accessKey {
// Incoming access key matches parent user then we should
// reject password change requests.
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAddUserInvalidArgument, err, allUsersFile, accessKey), r.URL)
return
}
// Check if accessKey has beginning and end space characters, this only applies to new users. // Check if accessKey has beginning and end space characters, this only applies to new users.
if !exists && hasSpaceBE(accessKey) { if !exists && hasSpaceBE(accessKey) {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL) writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAdminResourceInvalidArgument, err, allUsersFile, accessKey), r.URL)
return return
} }
checkDenyOnly := false
if accessKey == cred.AccessKey {
// Check that there is no explicit deny - otherwise it's allowed
// to change one's own password.
checkDenyOnly = true
}
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.CreateUserAdminAction,
ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner,
Claims: cred.Claims,
DenyOnly: checkDenyOnly,
}) {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allUsersFile, accessKey), r.URL)
return
}
if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil { if _, err = globalIAMSys.CreateUser(ctx, accessKey, ureq); err != nil {
failed.Users = append(failed.Users, madmin.IAMErrEntity{Name: accessKey, Error: err}) failed.Users = append(failed.Users, madmin.IAMErrEntity{Name: accessKey, Error: err})
} else { } else {
@@ -2462,17 +2456,6 @@ func (a adminAPIHandlers) importIAM(w http.ResponseWriter, r *http.Request, apiV
writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL) writeErrorResponseJSON(ctx, w, errorCodes.ToAPIErr(ErrAdminResourceInvalidArgument), r.URL)
return return
} }
if !globalIAMSys.IsAllowed(policy.Args{
AccountName: cred.AccessKey,
Groups: cred.Groups,
Action: policy.CreateServiceAccountAdminAction,
ConditionValues: getConditionValues(r, "", cred),
IsOwner: owner,
Claims: cred.Claims,
}) {
writeErrorResponseJSON(ctx, w, importErrorWithAPIErr(ctx, ErrAccessDenied, err, allSvcAcctsFile, user), r.URL)
return
}
updateReq := true updateReq := true
_, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey) _, _, err = globalIAMSys.GetServiceAccount(ctx, svcAcctReq.AccessKey)
if err != nil { if err != nil {
@@ -2801,3 +2784,10 @@ func commonAddServiceAccount(r *http.Request, ldap bool) (context.Context, auth.
return ctx, cred, opts, createReq, targetUser, APIError{} return ctx, cred, opts, createReq, targetUser, APIError{}
} }
// setReqInfoPolicyName will set the given policyName as a tag on the context's request info,
// so that it appears in audit logs.
func setReqInfoPolicyName(ctx context.Context, policyName string) {
reqInfo := logger.GetReqInfo(ctx)
reqInfo.SetTags("policyName", policyName)
}
+6
View File
@@ -716,6 +716,12 @@ func (s *TestSuiteIAM) TestCannedPolicies(c *check) {
c.Fatalf("policy info err: %v", err) c.Fatalf("policy info err: %v", err)
} }
// Check that policy with comma is rejected.
err = s.adm.AddCannedPolicy(ctx, "invalid,policy", policyBytes)
if err == nil {
c.Fatalf("invalid policy created successfully")
}
infoStr := string(info) infoStr := string(info)
if !strings.Contains(infoStr, `"s3:PutObject"`) || !strings.Contains(infoStr, ":"+bucket+"/") { if !strings.Contains(infoStr, `"s3:PutObject"`) || !strings.Contains(infoStr, ":"+bucket+"/") {
c.Fatalf("policy contains unexpected content!") c.Fatalf("policy contains unexpected content!")
+11 -19
View File
@@ -806,18 +806,20 @@ func (h *healSequence) healDiskMeta(objAPI ObjectLayer) error {
return h.healMinioSysMeta(objAPI, minioConfigPrefix)() return h.healMinioSysMeta(objAPI, minioConfigPrefix)()
} }
func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error { func (h *healSequence) healItems(objAPI ObjectLayer) error {
if h.clientToken == bgHealingUUID { if h.clientToken == bgHealingUUID {
// For background heal do nothing. // For background heal do nothing.
return nil return nil
} }
if err := h.healDiskMeta(objAPI); err != nil { if h.bucket == "" { // heal internal meta only during a site-wide heal
return err if err := h.healDiskMeta(objAPI); err != nil {
return err
}
} }
// Heal buckets and objects // Heal buckets and objects
return h.healBuckets(objAPI, bucketsOnly) return h.healBuckets(objAPI)
} }
// traverseAndHeal - traverses on-disk data and performs healing // traverseAndHeal - traverses on-disk data and performs healing
@@ -828,8 +830,7 @@ func (h *healSequence) healItems(objAPI ObjectLayer, bucketsOnly bool) error {
// has to wait until a safe point is reached, such as between scanning // has to wait until a safe point is reached, such as between scanning
// two objects. // two objects.
func (h *healSequence) traverseAndHeal(objAPI ObjectLayer) { func (h *healSequence) traverseAndHeal(objAPI ObjectLayer) {
bucketsOnly := false // Heals buckets and objects also. h.traverseAndHealDoneCh <- h.healItems(objAPI)
h.traverseAndHealDoneCh <- h.healItems(objAPI, bucketsOnly)
xioutil.SafeClose(h.traverseAndHealDoneCh) xioutil.SafeClose(h.traverseAndHealDoneCh)
} }
@@ -840,6 +841,7 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
// NOTE: Healing on meta is run regardless // NOTE: Healing on meta is run regardless
// of any bucket being selected, this is to ensure that // of any bucket being selected, this is to ensure that
// meta are always upto date and correct. // meta are always upto date and correct.
h.settings.Recursive = true
return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string, scanMode madmin.HealScanMode) error { return objAPI.HealObjects(h.ctx, minioMetaBucket, metaPrefix, h.settings, func(bucket, object, versionID string, scanMode madmin.HealScanMode) error {
if h.isQuitting() { if h.isQuitting() {
return errHealStopSignalled return errHealStopSignalled
@@ -856,14 +858,14 @@ func (h *healSequence) healMinioSysMeta(objAPI ObjectLayer, metaPrefix string) f
} }
// healBuckets - check for all buckets heal or just particular bucket. // healBuckets - check for all buckets heal or just particular bucket.
func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error { func (h *healSequence) healBuckets(objAPI ObjectLayer) error {
if h.isQuitting() { if h.isQuitting() {
return errHealStopSignalled return errHealStopSignalled
} }
// 1. If a bucket was specified, heal only the bucket. // 1. If a bucket was specified, heal only the bucket.
if h.bucket != "" { if h.bucket != "" {
return h.healBucket(objAPI, h.bucket, bucketsOnly) return h.healBucket(objAPI, h.bucket, false)
} }
buckets, err := objAPI.ListBuckets(h.ctx, BucketOptions{}) buckets, err := objAPI.ListBuckets(h.ctx, BucketOptions{})
@@ -877,7 +879,7 @@ func (h *healSequence) healBuckets(objAPI ObjectLayer, bucketsOnly bool) error {
}) })
for _, bucket := range buckets { for _, bucket := range buckets {
if err = h.healBucket(objAPI, bucket.Name, bucketsOnly); err != nil { if err = h.healBucket(objAPI, bucket.Name, false); err != nil {
return err return err
} }
} }
@@ -895,16 +897,6 @@ func (h *healSequence) healBucket(objAPI ObjectLayer, bucket string, bucketsOnly
return nil return nil
} }
if !h.settings.Recursive {
if h.object != "" {
if err := h.healObject(bucket, h.object, "", h.settings.ScanMode); err != nil {
return err
}
}
return nil
}
if err := objAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil { if err := objAPI.HealObjects(h.ctx, bucket, h.object, h.settings, h.healObject); err != nil {
return errFnHealFromAPIErr(h.ctx, err) return errFnHealFromAPIErr(h.ctx, err)
} }
+6
View File
@@ -213,6 +213,7 @@ const (
ErrPolicyAlreadyAttached ErrPolicyAlreadyAttached
ErrPolicyNotAttached ErrPolicyNotAttached
ErrExcessData ErrExcessData
ErrPolicyInvalidName
// Add new error codes here. // Add new error codes here.
// SSE-S3/SSE-KMS related API errors // SSE-S3/SSE-KMS related API errors
@@ -561,6 +562,11 @@ var errorCodes = errorCodeMap{
Description: "More data provided than indicated content length", Description: "More data provided than indicated content length",
HTTPStatusCode: http.StatusBadRequest, HTTPStatusCode: http.StatusBadRequest,
}, },
ErrPolicyInvalidName: {
Code: "PolicyInvalidName",
Description: "Policy name may not contain comma",
HTTPStatusCode: http.StatusBadRequest,
},
ErrPolicyTooLarge: { ErrPolicyTooLarge: {
Code: "PolicyTooLarge", Code: "PolicyTooLarge",
Description: "Policy exceeds the maximum allowed document size.", Description: "Policy exceeds the maximum allowed document size.",
-3
View File
@@ -593,8 +593,6 @@ func generateListVersionsResponse(ctx context.Context, bucket, prefix, marker, v
for k, v := range cleanReservedKeys(object.UserDefined) { for k, v := range cleanReservedKeys(object.UserDefined) {
content.UserMetadata.Set(k, v) content.UserMetadata.Set(k, v)
} }
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
content.Internal = &ObjectInternalInfo{ content.Internal = &ObjectInternalInfo{
K: object.DataBlocks, K: object.DataBlocks,
M: object.ParityBlocks, M: object.ParityBlocks,
@@ -729,7 +727,6 @@ func generateListObjectsV2Response(ctx context.Context, bucket, prefix, token, n
for k, v := range cleanReservedKeys(object.UserDefined) { for k, v := range cleanReservedKeys(object.UserDefined) {
content.UserMetadata.Set(k, v) content.UserMetadata.Set(k, v)
} }
content.UserMetadata.Set("expires", object.Expires.Format(http.TimeFormat))
content.Internal = &ObjectInternalInfo{ content.Internal = &ObjectInternalInfo{
K: object.DataBlocks, K: object.DataBlocks,
M: object.ParityBlocks, M: object.ParityBlocks,
+200 -199
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -223,9 +223,6 @@ func (h *healingTracker) updateProgress(success, skipped bool, bytes uint64) {
// update will update the tracker on the disk. // update will update the tracker on the disk.
// If the tracker has been deleted an error is returned. // If the tracker has been deleted an error is returned.
func (h *healingTracker) update(ctx context.Context) error { func (h *healingTracker) update(ctx context.Context) error {
if h.disk.Healing() == nil {
return fmt.Errorf("healingTracker: drive %q is not marked as healing", h.ID)
}
h.mu.Lock() h.mu.Lock()
if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 { if h.ID == "" || h.PoolIndex < 0 || h.SetIndex < 0 || h.DiskIndex < 0 {
h.ID, _ = h.disk.GetDiskID() h.ID, _ = h.disk.GetDiskID()
+4 -4
View File
@@ -301,12 +301,12 @@ func (r *BatchJobReplicateV1) StartFromSource(ctx context.Context, api ObjectLay
return true return true
} }
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.Before(oi.ModTime) { if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.After(oi.ModTime) {
// skip all objects that are created before the specified time. // skip all objects that are created before the specified time.
return true return true
} }
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.After(oi.ModTime) { if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.Before(oi.ModTime) {
// skip all objects that are created after the specified time. // skip all objects that are created after the specified time.
return true return true
} }
@@ -1047,12 +1047,12 @@ func (r *BatchJobReplicateV1) Start(ctx context.Context, api ObjectLayer, job Ba
return false return false
} }
if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.Before(info.ModTime) { if !r.Flags.Filter.CreatedAfter.IsZero() && r.Flags.Filter.CreatedAfter.After(info.ModTime) {
// skip all objects that are created before the specified time. // skip all objects that are created before the specified time.
return false return false
} }
if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.After(info.ModTime) { if !r.Flags.Filter.CreatedBefore.IsZero() && r.Flags.Filter.CreatedBefore.Before(info.ModTime) {
// skip all objects that are created after the specified time. // skip all objects that are created after the specified time.
return false return false
} }
+11
View File
@@ -20,6 +20,7 @@ package cmd
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"hash" "hash"
"io" "io"
"sync" "sync"
@@ -37,12 +38,22 @@ type streamingBitrotWriter struct {
shardSize int64 shardSize int64
canClose *sync.WaitGroup canClose *sync.WaitGroup
byteBuf []byte byteBuf []byte
finished bool
} }
func (b *streamingBitrotWriter) Write(p []byte) (int, error) { func (b *streamingBitrotWriter) Write(p []byte) (int, error) {
if len(p) == 0 { if len(p) == 0 {
return 0, nil return 0, nil
} }
if b.finished {
return 0, errors.New("bitrot write not allowed")
}
if int64(len(p)) > b.shardSize {
return 0, errors.New("unexpected bitrot buffer size")
}
if int64(len(p)) < b.shardSize {
b.finished = true
}
b.h.Reset() b.h.Reset()
b.h.Write(p) b.h.Write(p)
hashBytes := b.h.Sum(nil) hashBytes := b.h.Sum(nil)
+5
View File
@@ -1338,6 +1338,7 @@ func (api objectAPIHandlers) PostPolicyBucketHandler(w http.ResponseWriter, r *h
return return
} }
} }
opts.EncryptFn = metadataEncrypter(objectEncryptionKey)
pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey) pReader, err = pReader.WithEncryption(hashReader, &objectEncryptionKey)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
@@ -1809,6 +1810,10 @@ func (api objectAPIHandlers) PutBucketObjectLockConfigHandler(w http.ResponseWri
return return
} }
// Audit log tags.
reqInfo := logger.GetReqInfo(ctx)
reqInfo.SetTags("retention", config.String())
configData, err := xml.Marshal(config) configData, err := xml.Marshal(config)
if err != nil { if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
+12 -7
View File
@@ -71,7 +71,7 @@ func NewLifecycleSys() *LifecycleSys {
return &LifecycleSys{} return &LifecycleSys{}
} }
func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string, metadata map[string]string) madmin.TraceInfo { func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event string, metadata map[string]string, err string) madmin.TraceInfo {
sz, _ := oi.GetActualSize() sz, _ := oi.GetActualSize()
return madmin.TraceInfo{ return madmin.TraceInfo{
TraceType: madmin.TraceILM, TraceType: madmin.TraceILM,
@@ -81,18 +81,22 @@ func ilmTrace(startTime time.Time, duration time.Duration, oi ObjectInfo, event
Duration: duration, Duration: duration,
Path: pathJoin(oi.Bucket, oi.Name), Path: pathJoin(oi.Bucket, oi.Name),
Bytes: sz, Bytes: sz,
Error: "", Error: err,
Message: getSource(4), Message: getSource(4),
Custom: metadata, Custom: metadata,
} }
} }
func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string, metadata map[string]string) { func (sys *LifecycleSys) trace(oi ObjectInfo) func(event string, metadata map[string]string, err error) {
startTime := time.Now() startTime := time.Now()
return func(event string, metadata map[string]string) { return func(event string, metadata map[string]string, err error) {
duration := time.Since(startTime) duration := time.Since(startTime)
if globalTrace.NumSubscribers(madmin.TraceILM) > 0 { if globalTrace.NumSubscribers(madmin.TraceILM) > 0 {
globalTrace.Publish(ilmTrace(startTime, duration, oi, event, metadata)) e := ""
if err != nil {
e = err.Error()
}
globalTrace.Publish(ilmTrace(startTime, duration, oi, event, metadata, e))
} }
} }
} }
@@ -348,7 +352,7 @@ func (es *expiryState) Worker(input <-chan expiryOp) {
traceFn := globalLifecycleSys.trace(oi) traceFn := globalLifecycleSys.trace(oi)
if !oi.TransitionedObject.FreeVersion { if !oi.TransitionedObject.FreeVersion {
// nothing to be done // nothing to be done
return continue
} }
ignoreNotFoundErr := func(err error) error { ignoreNotFoundErr := func(err error) error {
@@ -362,7 +366,8 @@ func (es *expiryState) Worker(input <-chan expiryOp) {
err := deleteObjectFromRemoteTier(es.ctx, oi.TransitionedObject.Name, oi.TransitionedObject.VersionID, oi.TransitionedObject.Tier) err := deleteObjectFromRemoteTier(es.ctx, oi.TransitionedObject.Name, oi.TransitionedObject.VersionID, oi.TransitionedObject.Tier)
if ignoreNotFoundErr(err) != nil { if ignoreNotFoundErr(err) != nil {
transitionLogIf(es.ctx, err) transitionLogIf(es.ctx, err)
return traceFn(ILMFreeVersionDelete, nil, err)
continue
} }
// Remove this free version // Remove this free version
+1 -1
View File
@@ -245,7 +245,7 @@ func parseRequestToken(token string) (subToken string, nodeIndex int) {
func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string) (string, bool) { func proxyRequestByToken(ctx context.Context, w http.ResponseWriter, r *http.Request, token string) (string, bool) {
subToken, nodeIndex := parseRequestToken(token) subToken, nodeIndex := parseRequestToken(token)
if nodeIndex > 0 { if nodeIndex >= 0 {
return subToken, proxyRequestByNodeIndex(ctx, w, r, nodeIndex) return subToken, proxyRequestByNodeIndex(ctx, w, r, nodeIndex)
} }
return subToken, false return subToken, false
+3 -4
View File
@@ -53,8 +53,6 @@ import (
"github.com/minio/minio/internal/once" "github.com/minio/minio/internal/once"
"github.com/tinylib/msgp/msgp" "github.com/tinylib/msgp/msgp"
"github.com/zeebo/xxh3" "github.com/zeebo/xxh3"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
) )
const ( const (
@@ -780,8 +778,9 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined) isSSEC := crypto.SSEC.IsEncrypted(objInfo.UserDefined)
for k, v := range objInfo.UserDefined { for k, v := range objInfo.UserDefined {
_, isValidSSEHeader := validSSEReplicationHeaders[k]
// 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 !isSSEC || !slices.Contains(maps.Keys(validSSEReplicationHeaders), k) { if !isSSEC || !isValidSSEHeader {
if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) { if stringsHasPrefixFold(k, ReservedMetadataPrefixLower) {
continue continue
} }
@@ -789,7 +788,7 @@ func putReplicationOpts(ctx context.Context, sc string, objInfo ObjectInfo, part
continue continue
} }
} }
if slices.Contains(maps.Keys(validSSEReplicationHeaders), k) { if isValidSSEHeader {
meta[validSSEReplicationHeaders[k]] = v meta[validSSEReplicationHeaders[k]] = v
} else { } else {
meta[k] = v meta[k] = v
+7 -7
View File
@@ -108,18 +108,18 @@ func (l ReplicationLastHour) merge(o ReplicationLastHour) (merged ReplicationLas
// Add a new duration data // Add a new duration data
func (l *ReplicationLastHour) addsize(sz int64) { func (l *ReplicationLastHour) addsize(sz int64) {
min := time.Now().Unix() / 60 minutes := time.Now().Unix() / 60
l.forwardTo(min) l.forwardTo(minutes)
winIdx := min % 60 winIdx := minutes % 60
l.Totals[winIdx].merge(AccElem{Total: min, Size: sz, N: 1}) l.Totals[winIdx].merge(AccElem{Total: minutes, Size: sz, N: 1})
l.LastMin = min l.LastMin = minutes
} }
// Merge all recorded counts of last hour into one // Merge all recorded counts of last hour into one
func (l *ReplicationLastHour) getTotal() AccElem { func (l *ReplicationLastHour) getTotal() AccElem {
var res AccElem var res AccElem
min := time.Now().Unix() / 60 minutes := time.Now().Unix() / 60
l.forwardTo(min) l.forwardTo(minutes)
for _, elem := range l.Totals[:] { for _, elem := range l.Totals[:] {
res.merge(elem) res.merge(elem)
} }
+6 -3
View File
@@ -1325,10 +1325,13 @@ func applyExpiryOnNonTransitionedObjects(ctx context.Context, objLayer ObjectLay
dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, encodeDirObject(obj.Name), opts) dobj, err = objLayer.DeleteObject(ctx, obj.Bucket, encodeDirObject(obj.Name), opts)
if err != nil { if err != nil {
if isErrObjectNotFound(err) || isErrVersionNotFound(err) { if isErrObjectNotFound(err) || isErrVersionNotFound(err) {
traceFn(ILMExpiry, nil, nil)
return false return false
} }
// Assume it is still there. // Assume it is still there.
ilmLogOnceIf(ctx, fmt.Errorf("DeleteObject(%s, %s): %w", obj.Bucket, obj.Name, err), "non-transition-expiry"+obj.Name) err := fmt.Errorf("DeleteObject(%s, %s): %w", obj.Bucket, obj.Name, err)
ilmLogOnceIf(ctx, err, "non-transition-expiry"+obj.Name)
traceFn(ILMExpiry, nil, err)
return false return false
} }
if dobj.Name == "" { if dobj.Name == "" {
@@ -1549,7 +1552,7 @@ const (
ILMTransition = " ilm:transition" ILMTransition = " ilm:transition"
) )
func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, tags map[string]string, traceFn func(event string, metadata map[string]string)) { func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, tags map[string]string, traceFn func(event string, metadata map[string]string, err error)) {
var apiName string var apiName string
switch event { switch event {
case ILMExpiry: case ILMExpiry:
@@ -1567,5 +1570,5 @@ func auditLogLifecycle(ctx context.Context, oi ObjectInfo, event string, tags ma
VersionID: oi.VersionID, VersionID: oi.VersionID,
Tags: tags, Tags: tags,
}) })
traceFn(event, tags) traceFn(event, tags, nil)
} }
+2
View File
@@ -39,6 +39,8 @@ import (
"github.com/valyala/bytebufferpool" "github.com/valyala/bytebufferpool"
) )
//msgp:clearomitted
//go:generate msgp -file $GOFILE -unexported //go:generate msgp -file $GOFILE -unexported
// dataUsageHash is the hash type used. // dataUsageHash is the hash type used.
+591 -161
View File
@@ -1633,6 +1633,8 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, err = dc.ReadMapKeyPtr() field, err = dc.ReadMapKeyPtr()
@@ -1719,12 +1721,95 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
if z.AllTierStats == nil { if z.AllTierStats == nil {
z.AllTierStats = new(allTierStats) z.AllTierStats = new(allTierStats)
} }
err = z.AllTierStats.DecodeMsg(dc) var zb0004 uint32
zb0004, err = dc.ReadMapHeader()
if err != nil { if err != nil {
err = msgp.WrapError(err, "AllTierStats") err = msgp.WrapError(err, "AllTierStats")
return return
} }
for zb0004 > 0 {
zb0004--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
switch msgp.UnsafeString(field) {
case "ts":
var zb0005 uint32
zb0005, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
if z.AllTierStats.Tiers == nil {
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
} else if len(z.AllTierStats.Tiers) > 0 {
for key := range z.AllTierStats.Tiers {
delete(z.AllTierStats.Tiers, key)
}
}
for zb0005 > 0 {
zb0005--
var za0003 string
var za0004 tierStats
za0003, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
var zb0006 uint32
zb0006, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
for zb0006 > 0 {
zb0006--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
switch msgp.UnsafeString(field) {
case "ts":
za0004.TotalSize, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
return
}
case "nv":
za0004.NumVersions, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
return
}
case "no":
za0004.NumObjects, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
}
}
z.AllTierStats.Tiers[za0003] = za0004
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
}
}
} }
zb0001Mask |= 0x1
case "c": case "c":
z.Compacted, err = dc.ReadBool() z.Compacted, err = dc.ReadBool()
if err != nil { if err != nil {
@@ -1739,6 +1824,11 @@ func (z *dataUsageEntry) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.AllTierStats = nil
}
return return
} }
@@ -1757,121 +1847,166 @@ func (z *dataUsageEntry) EncodeMsg(en *msgp.Writer) (err error) {
if err != nil { if err != nil {
return return
} }
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// write "ch" // write "ch"
err = en.Append(0xa2, 0x63, 0x68) err = en.Append(0xa2, 0x63, 0x68)
if err != nil {
return
}
err = z.Children.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "Children")
return
}
// write "sz"
err = en.Append(0xa2, 0x73, 0x7a)
if err != nil {
return
}
err = en.WriteInt64(z.Size)
if err != nil {
err = msgp.WrapError(err, "Size")
return
}
// write "os"
err = en.Append(0xa2, 0x6f, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.Objects)
if err != nil {
err = msgp.WrapError(err, "Objects")
return
}
// write "vs"
err = en.Append(0xa2, 0x76, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.Versions)
if err != nil {
err = msgp.WrapError(err, "Versions")
return
}
// write "dms"
err = en.Append(0xa3, 0x64, 0x6d, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.DeleteMarkers)
if err != nil {
err = msgp.WrapError(err, "DeleteMarkers")
return
}
// write "szs"
err = en.Append(0xa3, 0x73, 0x7a, 0x73)
if err != nil {
return
}
err = en.WriteArrayHeader(uint32(dataUsageBucketLen))
if err != nil {
err = msgp.WrapError(err, "ObjSizes")
return
}
for za0001 := range z.ObjSizes {
err = en.WriteUint64(z.ObjSizes[za0001])
if err != nil {
err = msgp.WrapError(err, "ObjSizes", za0001)
return
}
}
// write "vh"
err = en.Append(0xa2, 0x76, 0x68)
if err != nil {
return
}
err = en.WriteArrayHeader(uint32(dataUsageVersionLen))
if err != nil {
err = msgp.WrapError(err, "ObjVersions")
return
}
for za0002 := range z.ObjVersions {
err = en.WriteUint64(z.ObjVersions[za0002])
if err != nil {
err = msgp.WrapError(err, "ObjVersions", za0002)
return
}
}
if (zb0001Mask & 0x80) == 0 { // if not omitted
// write "ats"
err = en.Append(0xa3, 0x61, 0x74, 0x73)
if err != nil { if err != nil {
return return
} }
if z.AllTierStats == nil { err = z.Children.EncodeMsg(en)
err = en.WriteNil() if err != nil {
err = msgp.WrapError(err, "Children")
return
}
// write "sz"
err = en.Append(0xa2, 0x73, 0x7a)
if err != nil {
return
}
err = en.WriteInt64(z.Size)
if err != nil {
err = msgp.WrapError(err, "Size")
return
}
// write "os"
err = en.Append(0xa2, 0x6f, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.Objects)
if err != nil {
err = msgp.WrapError(err, "Objects")
return
}
// write "vs"
err = en.Append(0xa2, 0x76, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.Versions)
if err != nil {
err = msgp.WrapError(err, "Versions")
return
}
// write "dms"
err = en.Append(0xa3, 0x64, 0x6d, 0x73)
if err != nil {
return
}
err = en.WriteUint64(z.DeleteMarkers)
if err != nil {
err = msgp.WrapError(err, "DeleteMarkers")
return
}
// write "szs"
err = en.Append(0xa3, 0x73, 0x7a, 0x73)
if err != nil {
return
}
err = en.WriteArrayHeader(uint32(dataUsageBucketLen))
if err != nil {
err = msgp.WrapError(err, "ObjSizes")
return
}
for za0001 := range z.ObjSizes {
err = en.WriteUint64(z.ObjSizes[za0001])
if err != nil { if err != nil {
return err = msgp.WrapError(err, "ObjSizes", za0001)
}
} else {
err = z.AllTierStats.EncodeMsg(en)
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return return
} }
} }
} // write "vh"
// write "c" err = en.Append(0xa2, 0x76, 0x68)
err = en.Append(0xa1, 0x63) if err != nil {
if err != nil { return
return }
} err = en.WriteArrayHeader(uint32(dataUsageVersionLen))
err = en.WriteBool(z.Compacted) if err != nil {
if err != nil { err = msgp.WrapError(err, "ObjVersions")
err = msgp.WrapError(err, "Compacted") return
return }
for za0002 := range z.ObjVersions {
err = en.WriteUint64(z.ObjVersions[za0002])
if err != nil {
err = msgp.WrapError(err, "ObjVersions", za0002)
return
}
}
if (zb0001Mask & 0x80) == 0 { // if not omitted
// write "ats"
err = en.Append(0xa3, 0x61, 0x74, 0x73)
if err != nil {
return
}
if z.AllTierStats == nil {
err = en.WriteNil()
if err != nil {
return
}
} else {
// map header, size 1
// write "ts"
err = en.Append(0x81, 0xa2, 0x74, 0x73)
if err != nil {
return
}
err = en.WriteMapHeader(uint32(len(z.AllTierStats.Tiers)))
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
for za0003, za0004 := range z.AllTierStats.Tiers {
err = en.WriteString(za0003)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
// map header, size 3
// write "ts"
err = en.Append(0x83, 0xa2, 0x74, 0x73)
if err != nil {
return
}
err = en.WriteUint64(za0004.TotalSize)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
return
}
// write "nv"
err = en.Append(0xa2, 0x6e, 0x76)
if err != nil {
return
}
err = en.WriteInt(za0004.NumVersions)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
return
}
// write "no"
err = en.Append(0xa2, 0x6e, 0x6f)
if err != nil {
return
}
err = en.WriteInt(za0004.NumObjects)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
return
}
}
}
}
// write "c"
err = en.Append(0xa1, 0x63)
if err != nil {
return
}
err = en.WriteBool(z.Compacted)
if err != nil {
err = msgp.WrapError(err, "Compacted")
return
}
} }
return return
} }
@@ -1889,56 +2024,69 @@ func (z *dataUsageEntry) MarshalMsg(b []byte) (o []byte, err error) {
} }
// variable map header, size zb0001Len // variable map header, size zb0001Len
o = append(o, 0x80|uint8(zb0001Len)) o = append(o, 0x80|uint8(zb0001Len))
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// string "ch" // string "ch"
o = append(o, 0xa2, 0x63, 0x68) o = append(o, 0xa2, 0x63, 0x68)
o, err = z.Children.MarshalMsg(o) o, err = z.Children.MarshalMsg(o)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Children") err = msgp.WrapError(err, "Children")
return return
} }
// string "sz" // string "sz"
o = append(o, 0xa2, 0x73, 0x7a) o = append(o, 0xa2, 0x73, 0x7a)
o = msgp.AppendInt64(o, z.Size) o = msgp.AppendInt64(o, z.Size)
// string "os" // string "os"
o = append(o, 0xa2, 0x6f, 0x73) o = append(o, 0xa2, 0x6f, 0x73)
o = msgp.AppendUint64(o, z.Objects) o = msgp.AppendUint64(o, z.Objects)
// string "vs" // string "vs"
o = append(o, 0xa2, 0x76, 0x73) o = append(o, 0xa2, 0x76, 0x73)
o = msgp.AppendUint64(o, z.Versions) o = msgp.AppendUint64(o, z.Versions)
// string "dms" // string "dms"
o = append(o, 0xa3, 0x64, 0x6d, 0x73) o = append(o, 0xa3, 0x64, 0x6d, 0x73)
o = msgp.AppendUint64(o, z.DeleteMarkers) o = msgp.AppendUint64(o, z.DeleteMarkers)
// string "szs" // string "szs"
o = append(o, 0xa3, 0x73, 0x7a, 0x73) o = append(o, 0xa3, 0x73, 0x7a, 0x73)
o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen)) o = msgp.AppendArrayHeader(o, uint32(dataUsageBucketLen))
for za0001 := range z.ObjSizes { for za0001 := range z.ObjSizes {
o = msgp.AppendUint64(o, z.ObjSizes[za0001]) o = msgp.AppendUint64(o, z.ObjSizes[za0001])
} }
// string "vh" // string "vh"
o = append(o, 0xa2, 0x76, 0x68) o = append(o, 0xa2, 0x76, 0x68)
o = msgp.AppendArrayHeader(o, uint32(dataUsageVersionLen)) o = msgp.AppendArrayHeader(o, uint32(dataUsageVersionLen))
for za0002 := range z.ObjVersions { for za0002 := range z.ObjVersions {
o = msgp.AppendUint64(o, z.ObjVersions[za0002]) o = msgp.AppendUint64(o, z.ObjVersions[za0002])
} }
if (zb0001Mask & 0x80) == 0 { // if not omitted if (zb0001Mask & 0x80) == 0 { // if not omitted
// string "ats" // string "ats"
o = append(o, 0xa3, 0x61, 0x74, 0x73) o = append(o, 0xa3, 0x61, 0x74, 0x73)
if z.AllTierStats == nil { if z.AllTierStats == nil {
o = msgp.AppendNil(o) o = msgp.AppendNil(o)
} else { } else {
o, err = z.AllTierStats.MarshalMsg(o) // map header, size 1
if err != nil { // string "ts"
err = msgp.WrapError(err, "AllTierStats") o = append(o, 0x81, 0xa2, 0x74, 0x73)
return o = msgp.AppendMapHeader(o, uint32(len(z.AllTierStats.Tiers)))
for za0003, za0004 := range z.AllTierStats.Tiers {
o = msgp.AppendString(o, za0003)
// map header, size 3
// string "ts"
o = append(o, 0x83, 0xa2, 0x74, 0x73)
o = msgp.AppendUint64(o, za0004.TotalSize)
// string "nv"
o = append(o, 0xa2, 0x6e, 0x76)
o = msgp.AppendInt(o, za0004.NumVersions)
// string "no"
o = append(o, 0xa2, 0x6e, 0x6f)
o = msgp.AppendInt(o, za0004.NumObjects)
}
} }
} }
// string "c"
o = append(o, 0xa1, 0x63)
o = msgp.AppendBool(o, z.Compacted)
} }
// string "c"
o = append(o, 0xa1, 0x63)
o = msgp.AppendBool(o, z.Compacted)
return return
} }
@@ -1952,6 +2100,8 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts) field, bts, err = msgp.ReadMapKeyZC(bts)
@@ -2037,12 +2187,95 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
if z.AllTierStats == nil { if z.AllTierStats == nil {
z.AllTierStats = new(allTierStats) z.AllTierStats = new(allTierStats)
} }
bts, err = z.AllTierStats.UnmarshalMsg(bts) var zb0004 uint32
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err, "AllTierStats") err = msgp.WrapError(err, "AllTierStats")
return return
} }
for zb0004 > 0 {
zb0004--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
switch msgp.UnsafeString(field) {
case "ts":
var zb0005 uint32
zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
if z.AllTierStats.Tiers == nil {
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
} else if len(z.AllTierStats.Tiers) > 0 {
for key := range z.AllTierStats.Tiers {
delete(z.AllTierStats.Tiers, key)
}
}
for zb0005 > 0 {
var za0003 string
var za0004 tierStats
zb0005--
za0003, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
var zb0006 uint32
zb0006, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
for zb0006 > 0 {
zb0006--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
switch msgp.UnsafeString(field) {
case "ts":
za0004.TotalSize, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
return
}
case "nv":
za0004.NumVersions, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
return
}
case "no":
za0004.NumObjects, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
}
}
z.AllTierStats.Tiers[za0003] = za0004
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
}
}
} }
zb0001Mask |= 0x1
case "c": case "c":
z.Compacted, bts, err = msgp.ReadBoolBytes(bts) z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
if err != nil { if err != nil {
@@ -2057,6 +2290,11 @@ func (z *dataUsageEntry) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.AllTierStats = nil
}
o = bts o = bts
return return
} }
@@ -2067,7 +2305,13 @@ func (z *dataUsageEntry) Msgsize() (s int) {
if z.AllTierStats == nil { if z.AllTierStats == nil {
s += msgp.NilSize s += msgp.NilSize
} else { } else {
s += z.AllTierStats.Msgsize() s += 1 + 3 + msgp.MapHeaderSize
if z.AllTierStats.Tiers != nil {
for za0003, za0004 := range z.AllTierStats.Tiers {
_ = za0004
s += msgp.StringPrefixSize + len(za0003) + 1 + 3 + msgp.Uint64Size + 3 + msgp.IntSize + 3 + msgp.IntSize
}
}
} }
s += 2 + msgp.BoolSize s += 2 + msgp.BoolSize
return return
@@ -2628,6 +2872,8 @@ func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, err = dc.ReadMapKeyPtr() field, err = dc.ReadMapKeyPtr()
@@ -2714,12 +2960,95 @@ func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
if z.AllTierStats == nil { if z.AllTierStats == nil {
z.AllTierStats = new(allTierStats) z.AllTierStats = new(allTierStats)
} }
err = z.AllTierStats.DecodeMsg(dc) var zb0004 uint32
zb0004, err = dc.ReadMapHeader()
if err != nil { if err != nil {
err = msgp.WrapError(err, "AllTierStats") err = msgp.WrapError(err, "AllTierStats")
return return
} }
for zb0004 > 0 {
zb0004--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
switch msgp.UnsafeString(field) {
case "ts":
var zb0005 uint32
zb0005, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
if z.AllTierStats.Tiers == nil {
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
} else if len(z.AllTierStats.Tiers) > 0 {
for key := range z.AllTierStats.Tiers {
delete(z.AllTierStats.Tiers, key)
}
}
for zb0005 > 0 {
zb0005--
var za0003 string
var za0004 tierStats
za0003, err = dc.ReadString()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
var zb0006 uint32
zb0006, err = dc.ReadMapHeader()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
for zb0006 > 0 {
zb0006--
field, err = dc.ReadMapKeyPtr()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
switch msgp.UnsafeString(field) {
case "ts":
za0004.TotalSize, err = dc.ReadUint64()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
return
}
case "nv":
za0004.NumVersions, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
return
}
case "no":
za0004.NumObjects, err = dc.ReadInt()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
return
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
}
}
z.AllTierStats.Tiers[za0003] = za0004
}
default:
err = dc.Skip()
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
}
}
} }
zb0001Mask |= 0x1
case "c": case "c":
z.Compacted, err = dc.ReadBool() z.Compacted, err = dc.ReadBool()
if err != nil { if err != nil {
@@ -2734,6 +3063,11 @@ func (z *dataUsageEntryV7) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.AllTierStats = nil
}
return return
} }
@@ -2747,6 +3081,8 @@ func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts) field, bts, err = msgp.ReadMapKeyZC(bts)
@@ -2832,12 +3168,95 @@ func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
if z.AllTierStats == nil { if z.AllTierStats == nil {
z.AllTierStats = new(allTierStats) z.AllTierStats = new(allTierStats)
} }
bts, err = z.AllTierStats.UnmarshalMsg(bts) var zb0004 uint32
zb0004, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err, "AllTierStats") err = msgp.WrapError(err, "AllTierStats")
return return
} }
for zb0004 > 0 {
zb0004--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
switch msgp.UnsafeString(field) {
case "ts":
var zb0005 uint32
zb0005, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
if z.AllTierStats.Tiers == nil {
z.AllTierStats.Tiers = make(map[string]tierStats, zb0005)
} else if len(z.AllTierStats.Tiers) > 0 {
for key := range z.AllTierStats.Tiers {
delete(z.AllTierStats.Tiers, key)
}
}
for zb0005 > 0 {
var za0003 string
var za0004 tierStats
zb0005--
za0003, bts, err = msgp.ReadStringBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers")
return
}
var zb0006 uint32
zb0006, bts, err = msgp.ReadMapHeaderBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
for zb0006 > 0 {
zb0006--
field, bts, err = msgp.ReadMapKeyZC(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
switch msgp.UnsafeString(field) {
case "ts":
za0004.TotalSize, bts, err = msgp.ReadUint64Bytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "TotalSize")
return
}
case "nv":
za0004.NumVersions, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumVersions")
return
}
case "no":
za0004.NumObjects, bts, err = msgp.ReadIntBytes(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003, "NumObjects")
return
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats", "Tiers", za0003)
return
}
}
}
z.AllTierStats.Tiers[za0003] = za0004
}
default:
bts, err = msgp.Skip(bts)
if err != nil {
err = msgp.WrapError(err, "AllTierStats")
return
}
}
}
} }
zb0001Mask |= 0x1
case "c": case "c":
z.Compacted, bts, err = msgp.ReadBoolBytes(bts) z.Compacted, bts, err = msgp.ReadBoolBytes(bts)
if err != nil { if err != nil {
@@ -2852,6 +3271,11 @@ func (z *dataUsageEntryV7) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.AllTierStats = nil
}
o = bts o = bts
return return
} }
@@ -2862,7 +3286,13 @@ func (z *dataUsageEntryV7) Msgsize() (s int) {
if z.AllTierStats == nil { if z.AllTierStats == nil {
s += msgp.NilSize s += msgp.NilSize
} else { } else {
s += z.AllTierStats.Msgsize() s += 1 + 3 + msgp.MapHeaderSize
if z.AllTierStats.Tiers != nil {
for za0003, za0004 := range z.AllTierStats.Tiers {
_ = za0004
s += msgp.StringPrefixSize + len(za0003) + 1 + 3 + msgp.Uint64Size + 3 + msgp.IntSize + 3 + msgp.IntSize
}
}
} }
s += 2 + msgp.BoolSize s += 2 + msgp.BoolSize
return return
+6 -6
View File
@@ -117,12 +117,12 @@ func (dt *dynamicTimeout) logEntry(duration time.Duration) {
// adjust changes the value of the dynamic timeout based on the // adjust changes the value of the dynamic timeout based on the
// previous results // previous results
func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) { func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) {
failures, max := 0, time.Duration(0) failures, maxDur := 0, time.Duration(0)
for _, dur := range entries[:] { for _, dur := range entries[:] {
if dur == maxDuration { if dur == maxDuration {
failures++ failures++
} else if dur > max { } else if dur > maxDur {
max = dur maxDur = dur
} }
} }
@@ -144,12 +144,12 @@ func (dt *dynamicTimeout) adjust(entries [dynamicTimeoutLogSize]time.Duration) {
} else if failPct < dynamicTimeoutDecreaseThresholdPct { } else if failPct < dynamicTimeoutDecreaseThresholdPct {
// We are hitting the timeout relatively few times, // We are hitting the timeout relatively few times,
// so decrease the timeout towards 25 % of maximum time spent. // so decrease the timeout towards 25 % of maximum time spent.
max = max * 125 / 100 maxDur = maxDur * 125 / 100
timeout := atomic.LoadInt64(&dt.timeout) timeout := atomic.LoadInt64(&dt.timeout)
if max < time.Duration(timeout) { if maxDur < time.Duration(timeout) {
// Move 50% toward the max. // Move 50% toward the max.
timeout = (int64(max) + timeout) / 2 timeout = (int64(maxDur) + timeout) / 2
} }
if timeout < dt.minimum { if timeout < dt.minimum {
timeout = dt.minimum timeout = dt.minimum
+1 -1
View File
@@ -109,7 +109,7 @@ func kmsKeyIDFromMetadata(metadata map[string]string) string {
// be AWS S3 compliant. // be AWS S3 compliant.
// //
// DecryptETags uses a KMS bulk decryption API, if available, which // DecryptETags uses a KMS bulk decryption API, if available, which
// is more efficient than decrypting ETags sequentually. // is more efficient than decrypting ETags sequentially.
func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error { func DecryptETags(ctx context.Context, k *kms.KMS, objects []ObjectInfo) error {
const BatchSize = 250 // We process the objects in batches - 250 is a reasonable default. const BatchSize = 250 // We process the objects in batches - 250 is a reasonable default.
var ( var (
+1 -1
View File
@@ -26,6 +26,7 @@ import (
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"slices"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -38,7 +39,6 @@ import (
"github.com/minio/minio/internal/mountinfo" "github.com/minio/minio/internal/mountinfo"
"github.com/minio/pkg/v3/env" "github.com/minio/pkg/v3/env"
xnet "github.com/minio/pkg/v3/net" xnet "github.com/minio/pkg/v3/net"
"golang.org/x/exp/slices"
) )
// EndpointType - enum for endpoint type. // EndpointType - enum for endpoint type.
+17 -27
View File
@@ -277,23 +277,21 @@ func partNeedsHealing(partErrs []int) bool {
return slices.IndexFunc(partErrs, func(i int) bool { return i != checkPartSuccess && i != checkPartUnknown }) > -1 return slices.IndexFunc(partErrs, func(i int) bool { return i != checkPartSuccess && i != checkPartUnknown }) > -1
} }
func hasPartErr(partErrs []int) bool { func countPartNotSuccess(partErrs []int) (c int) {
return slices.IndexFunc(partErrs, func(i int) bool { return i != checkPartSuccess }) > -1 for _, pe := range partErrs {
if pe != checkPartSuccess {
c++
}
}
return
} }
// disksWithAllParts - This function needs to be called with // checkObjectWithAllParts sets partsMetadata and onlineDisks when xl.meta is inexistant/corrupted or outdated
// []StorageAPI returned by listOnlineDisks. Returns, // it also checks if the status of each part (corrupted, missing, ok) in each drive
// func checkObjectWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetadata []FileInfo,
// - disks which have all parts specified in the latest xl.meta.
//
// - slice of errors about the state of data files on disk - can have
// a not-found error or a hash-mismatch error.
func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetadata []FileInfo,
errs []error, latestMeta FileInfo, filterByETag bool, bucket, object string, errs []error, latestMeta FileInfo, filterByETag bool, bucket, object string,
scanMode madmin.HealScanMode, scanMode madmin.HealScanMode,
) (availableDisks []StorageAPI, dataErrsByDisk map[int][]int, dataErrsByPart map[int][]int) { ) (dataErrsByDisk map[int][]int, dataErrsByPart map[int][]int) {
availableDisks = make([]StorageAPI, len(onlineDisks))
dataErrsByDisk = make(map[int][]int, len(onlineDisks)) dataErrsByDisk = make(map[int][]int, len(onlineDisks))
for i := range onlineDisks { for i := range onlineDisks {
dataErrsByDisk[i] = make([]int, len(latestMeta.Parts)) dataErrsByDisk[i] = make([]int, len(latestMeta.Parts))
@@ -334,12 +332,12 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
metaErrs := make([]error, len(errs)) metaErrs := make([]error, len(errs))
for i, onlineDisk := range onlineDisks { for i := range onlineDisks {
if errs[i] != nil { if errs[i] != nil {
metaErrs[i] = errs[i] metaErrs[i] = errs[i]
continue continue
} }
if onlineDisk == OfflineDisk { if onlineDisks[i] == OfflineDisk {
metaErrs[i] = errDiskNotFound metaErrs[i] = errDiskNotFound
continue continue
} }
@@ -355,6 +353,7 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
if corrupted { if corrupted {
metaErrs[i] = errFileCorrupt metaErrs[i] = errFileCorrupt
partsMetadata[i] = FileInfo{} partsMetadata[i] = FileInfo{}
onlineDisks[i] = nil
continue continue
} }
@@ -362,6 +361,7 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
if !meta.IsValid() { if !meta.IsValid() {
partsMetadata[i] = FileInfo{} partsMetadata[i] = FileInfo{}
metaErrs[i] = errFileCorrupt metaErrs[i] = errFileCorrupt
onlineDisks[i] = nil
continue continue
} }
@@ -372,6 +372,7 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
// might have the right erasure distribution. // might have the right erasure distribution.
partsMetadata[i] = FileInfo{} partsMetadata[i] = FileInfo{}
metaErrs[i] = errFileCorrupt metaErrs[i] = errFileCorrupt
onlineDisks[i] = nil
continue continue
} }
} }
@@ -392,8 +393,8 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
if metaErrs[i] != nil { if metaErrs[i] != nil {
continue continue
} }
meta := partsMetadata[i]
meta := partsMetadata[i]
if meta.Deleted || meta.IsRemote() { if meta.Deleted || meta.IsRemote() {
continue continue
} }
@@ -440,16 +441,5 @@ func disksWithAllParts(ctx context.Context, onlineDisks []StorageAPI, partsMetad
dataErrsByDisk[disk][part] = dataErrsByPart[part][disk] dataErrsByDisk[disk][part] = dataErrsByPart[part][disk]
} }
} }
for i, onlineDisk := range onlineDisks {
if metaErrs[i] == nil && !hasPartErr(dataErrsByDisk[i]) {
// All parts verified, mark it as all data available.
availableDisks[i] = onlineDisk
} else {
// upon errors just make that disk's fileinfo invalid
partsMetadata[i] = FileInfo{}
}
}
return return
} }
+16 -33
View File
@@ -313,11 +313,11 @@ func TestListOnlineDisks(t *testing.T) {
t.Fatalf("Expected modTime to be equal to %v but was found to be %v", t.Fatalf("Expected modTime to be equal to %v but was found to be %v",
test.expectedTime, modTime) test.expectedTime, modTime)
} }
availableDisks, _, _ := disksWithAllParts(ctx, onlineDisks, partsMetadata, _, _ = checkObjectWithAllParts(ctx, onlineDisks, partsMetadata,
test.errs, fi, false, bucket, object, madmin.HealDeepScan) test.errs, fi, false, bucket, object, madmin.HealDeepScan)
if test._tamperBackend != noTamper { if test._tamperBackend != noTamper {
if tamperedIndex != -1 && availableDisks[tamperedIndex] != nil { if tamperedIndex != -1 && onlineDisks[tamperedIndex] != nil {
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data", t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
erasureDisks[tamperedIndex]) erasureDisks[tamperedIndex])
} }
@@ -495,11 +495,11 @@ func TestListOnlineDisksSmallObjects(t *testing.T) {
test.expectedTime, modTime) test.expectedTime, modTime)
} }
availableDisks, _, _ := disksWithAllParts(ctx, onlineDisks, partsMetadata, _, _ = checkObjectWithAllParts(ctx, onlineDisks, partsMetadata,
test.errs, fi, false, bucket, object, madmin.HealDeepScan) test.errs, fi, false, bucket, object, madmin.HealDeepScan)
if test._tamperBackend != noTamper { if test._tamperBackend != noTamper {
if tamperedIndex != -1 && availableDisks[tamperedIndex] != nil { if tamperedIndex != -1 && onlineDisks[tamperedIndex] != nil {
t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data", t.Fatalf("Drive (%v) with part.1 missing is not a drive with available data",
erasureDisks[tamperedIndex]) erasureDisks[tamperedIndex])
} }
@@ -545,6 +545,7 @@ func TestDisksWithAllParts(t *testing.T) {
// Test 1: Test that all disks are returned without any failures with // Test 1: Test that all disks are returned without any failures with
// unmodified meta data // unmodified meta data
erasureDisks = s.getDisks()
partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true) partsMetadata, errs := readAllFileInfo(ctx, erasureDisks, "", bucket, object, "", false, true)
if err != nil { if err != nil {
t.Fatalf("Failed to read xl meta data %v", err) t.Fatalf("Failed to read xl meta data %v", err)
@@ -557,14 +558,10 @@ func TestDisksWithAllParts(t *testing.T) {
erasureDisks, _, _ = listOnlineDisks(erasureDisks, partsMetadata, errs, readQuorum) erasureDisks, _, _ = listOnlineDisks(erasureDisks, partsMetadata, errs, readQuorum)
filteredDisks, _, dataErrsPerDisk := disksWithAllParts(ctx, erasureDisks, partsMetadata, dataErrsPerDisk, _ := checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
errs, fi, false, bucket, object, madmin.HealDeepScan) errs, fi, false, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) { for diskIndex, disk := range erasureDisks {
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if partNeedsHealing(dataErrsPerDisk[diskIndex]) { if partNeedsHealing(dataErrsPerDisk[diskIndex]) {
t.Errorf("Unexpected error: %v", dataErrsPerDisk[diskIndex]) t.Errorf("Unexpected error: %v", dataErrsPerDisk[diskIndex])
} }
@@ -575,17 +572,15 @@ func TestDisksWithAllParts(t *testing.T) {
} }
// Test 2: Not synchronized modtime // Test 2: Not synchronized modtime
erasureDisks = s.getDisks()
partsMetadataBackup := partsMetadata[0] partsMetadataBackup := partsMetadata[0]
partsMetadata[0].ModTime = partsMetadata[0].ModTime.Add(-1 * time.Hour) partsMetadata[0].ModTime = partsMetadata[0].ModTime.Add(-1 * time.Hour)
errs = make([]error, len(erasureDisks)) errs = make([]error, len(erasureDisks))
filteredDisks, _, _ = disksWithAllParts(ctx, erasureDisks, partsMetadata, _, _ = checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
errs, fi, false, bucket, object, madmin.HealDeepScan) errs, fi, false, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) { for diskIndex, disk := range erasureDisks {
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if diskIndex == 0 && disk != nil { if diskIndex == 0 && disk != nil {
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex) t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
} }
@@ -596,17 +591,15 @@ func TestDisksWithAllParts(t *testing.T) {
partsMetadata[0] = partsMetadataBackup // Revert before going to the next test partsMetadata[0] = partsMetadataBackup // Revert before going to the next test
// Test 3: Not synchronized DataDir // Test 3: Not synchronized DataDir
erasureDisks = s.getDisks()
partsMetadataBackup = partsMetadata[1] partsMetadataBackup = partsMetadata[1]
partsMetadata[1].DataDir = "foo-random" partsMetadata[1].DataDir = "foo-random"
errs = make([]error, len(erasureDisks)) errs = make([]error, len(erasureDisks))
filteredDisks, _, _ = disksWithAllParts(ctx, erasureDisks, partsMetadata, _, _ = checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
errs, fi, false, bucket, object, madmin.HealDeepScan) errs, fi, false, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) { for diskIndex, disk := range erasureDisks {
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if diskIndex == 1 && disk != nil { if diskIndex == 1 && disk != nil {
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex) t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
} }
@@ -617,6 +610,7 @@ func TestDisksWithAllParts(t *testing.T) {
partsMetadata[1] = partsMetadataBackup // Revert before going to the next test partsMetadata[1] = partsMetadataBackup // Revert before going to the next test
// Test 4: key = disk index, value = part name with hash mismatch // Test 4: key = disk index, value = part name with hash mismatch
erasureDisks = s.getDisks()
diskFailures := make(map[int]string) diskFailures := make(map[int]string)
diskFailures[0] = "part.1" diskFailures[0] = "part.1"
diskFailures[3] = "part.1" diskFailures[3] = "part.1"
@@ -637,29 +631,18 @@ func TestDisksWithAllParts(t *testing.T) {
} }
errs = make([]error, len(erasureDisks)) errs = make([]error, len(erasureDisks))
filteredDisks, dataErrsPerDisk, _ = disksWithAllParts(ctx, erasureDisks, partsMetadata, dataErrsPerDisk, _ = checkObjectWithAllParts(ctx, erasureDisks, partsMetadata,
errs, fi, false, bucket, object, madmin.HealDeepScan) errs, fi, false, bucket, object, madmin.HealDeepScan)
if len(filteredDisks) != len(erasureDisks) { for diskIndex := range erasureDisks {
t.Errorf("Unexpected number of drives: %d", len(filteredDisks))
}
for diskIndex, disk := range filteredDisks {
if _, ok := diskFailures[diskIndex]; ok { if _, ok := diskFailures[diskIndex]; ok {
if disk != nil {
t.Errorf("Drive not filtered as expected, drive: %d", diskIndex)
}
if !partNeedsHealing(dataErrsPerDisk[diskIndex]) { if !partNeedsHealing(dataErrsPerDisk[diskIndex]) {
t.Errorf("Disk expected to be healed, driveIndex: %d", diskIndex) t.Errorf("Disk expected to be healed, driveIndex: %d", diskIndex)
} }
} else { } else {
if disk == nil {
t.Errorf("Drive erroneously filtered, driveIndex: %d", diskIndex)
}
if partNeedsHealing(dataErrsPerDisk[diskIndex]) { if partNeedsHealing(dataErrsPerDisk[diskIndex]) {
t.Errorf("Disk not expected to be healed, driveIndex: %d", diskIndex) t.Errorf("Disk not expected to be healed, driveIndex: %d", diskIndex)
} }
} }
} }
} }
+68 -65
View File
@@ -32,7 +32,6 @@ import (
"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/v3/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
"golang.org/x/exp/slices"
) )
//go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE //go:generate stringer -type=healingMetric -trimprefix=healingMetric $GOFILE
@@ -45,7 +44,8 @@ const (
healingMetricCheckAbandonedParts healingMetricCheckAbandonedParts
) )
func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string, scanMode madmin.HealScanMode, healEntry func(string, metaCacheEntry, madmin.HealScanMode) error) error { // List a prefix or a single object versions and heal
func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string, recursive bool, scanMode madmin.HealScanMode, healEntry func(string, metaCacheEntry, madmin.HealScanMode) error) error {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
@@ -78,11 +78,14 @@ func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string,
bucket: bucket, bucket: bucket,
path: path, path: path,
filterPrefix: filterPrefix, filterPrefix: filterPrefix,
recursive: true, recursive: recursive,
forwardTo: "", forwardTo: "",
minDisks: 1, minDisks: 1,
reportNotFound: false, reportNotFound: false,
agreed: func(entry metaCacheEntry) { agreed: func(entry metaCacheEntry) {
if !recursive && prefix != entry.name {
return
}
if err := healEntry(bucket, entry, scanMode); err != nil { if err := healEntry(bucket, entry, scanMode); err != nil {
cancel() cancel()
} }
@@ -94,7 +97,9 @@ func (er erasureObjects) listAndHeal(ctx context.Context, bucket, prefix string,
// proceed to heal nonetheless. // proceed to heal nonetheless.
entry, _ = entries.firstFound() entry, _ = entries.firstFound()
} }
if !recursive && prefix != entry.name {
return
}
if err := healEntry(bucket, *entry, scanMode); err != nil { if err := healEntry(bucket, *entry, scanMode); err != nil {
cancel() cancel()
return return
@@ -149,37 +154,40 @@ var errLegacyXLMeta = errors.New("legacy XL meta")
var errOutdatedXLMeta = errors.New("outdated XL meta") var errOutdatedXLMeta = errors.New("outdated XL meta")
var errPartMissingOrCorrupt = errors.New("part missing or corrupt") var (
errPartCorrupt = errors.New("part corrupt")
errPartMissing = errors.New("part missing")
)
// Only heal on disks where we are sure that healing is needed. We can expand // Only heal on disks where we are sure that healing is needed. We can expand
// this list as and when we figure out more errors can be added to this list safely. // this list as and when we figure out more errors can be added to this list safely.
func shouldHealObjectOnDisk(erErr error, partsErrs []int, meta FileInfo, latestMeta FileInfo) (bool, error) { func shouldHealObjectOnDisk(erErr error, partsErrs []int, meta FileInfo, latestMeta FileInfo) (bool, bool, error) {
if errors.Is(erErr, errFileNotFound) || errors.Is(erErr, errFileVersionNotFound) || errors.Is(erErr, errFileCorrupt) { if errors.Is(erErr, errFileNotFound) || errors.Is(erErr, errFileVersionNotFound) || errors.Is(erErr, errFileCorrupt) {
return true, erErr return true, true, erErr
} }
if erErr == nil { if erErr == nil {
if meta.XLV1 { if meta.XLV1 {
// Legacy means heal always // Legacy means heal always
// always check first. // always check first.
return true, errLegacyXLMeta return true, true, errLegacyXLMeta
} }
if !latestMeta.Equals(meta) { if !latestMeta.Equals(meta) {
return true, errOutdatedXLMeta return true, true, errOutdatedXLMeta
} }
if !meta.Deleted && !meta.IsRemote() { if !meta.Deleted && !meta.IsRemote() {
// If xl.meta was read fine but there may be problem with the part.N files. // If xl.meta was read fine but there may be problem with the part.N files.
for _, partErr := range partsErrs { for _, partErr := range partsErrs {
if slices.Contains([]int{ if partErr == checkPartFileNotFound {
checkPartFileNotFound, return true, false, errPartMissing
checkPartFileCorrupt, }
}, partErr) { if partErr == checkPartFileCorrupt {
return true, errPartMissingOrCorrupt return true, false, errPartCorrupt
} }
} }
} }
return false, nil return false, false, nil
} }
return false, erErr return false, false, erErr
} }
const ( const (
@@ -254,6 +262,21 @@ func (er *erasureObjects) auditHealObject(ctx context.Context, bucket, object, v
auditLogInternal(ctx, opts) auditLogInternal(ctx, opts)
} }
func objectErrToDriveState(reason error) string {
switch {
case reason == nil:
return madmin.DriveStateOk
case IsErr(reason, errDiskNotFound):
return madmin.DriveStateOffline
case IsErr(reason, errFileNotFound, errFileVersionNotFound, errVolumeNotFound, errPartMissing, errOutdatedXLMeta, errLegacyXLMeta):
return madmin.DriveStateMissing
case IsErr(reason, errFileCorrupt, errPartCorrupt):
return madmin.DriveStateCorrupt
default:
return fmt.Sprintf("%s (%s)", madmin.DriveStateUnknown, reason.Error())
}
}
// Heals an object by re-writing corrupt/missing erasure blocks. // Heals an object by re-writing corrupt/missing erasure blocks.
func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) { func (er *erasureObjects) healObject(ctx context.Context, bucket string, object string, versionID string, opts madmin.HealOpts) (result madmin.HealResultItem, err error) {
dryRun := opts.DryRun dryRun := opts.DryRun
@@ -340,16 +363,7 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
// No modtime quorum // No modtime quorum
filterDisksByETag := quorumETag != "" filterDisksByETag := quorumETag != ""
// List of disks having all parts as per latest metadata. dataErrsByDisk, dataErrsByPart := checkObjectWithAllParts(ctx, onlineDisks, partsMetadata,
// NOTE: do not pass in latestDisks to diskWithAllParts since
// the diskWithAllParts needs to reach the drive to ensure
// validity of the metadata content, we should make sure that
// we pass in disks as is for it to be verified. Once verified
// the disksWithAllParts() returns the actual disks that can be
// used here for reconstruction. This is done to ensure that
// we do not skip drives that have inconsistent metadata to be
// skipped from purging when they are stale.
availableDisks, dataErrsByDisk, dataErrsByPart := disksWithAllParts(ctx, onlineDisks, partsMetadata,
errs, latestMeta, filterDisksByETag, bucket, object, scanMode) errs, latestMeta, filterDisksByETag, bucket, object, scanMode)
var erasure Erasure var erasure Erasure
@@ -371,26 +385,18 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
// data state and a list of outdated disks on which data needs // data state and a list of outdated disks on which data needs
// to be healed. // to be healed.
outDatedDisks := make([]StorageAPI, len(storageDisks)) outDatedDisks := make([]StorageAPI, len(storageDisks))
disksToHealCount := 0 disksToHealCount, xlMetaToHealCount := 0, 0
for i := range availableDisks { for i := range onlineDisks {
yes, reason := shouldHealObjectOnDisk(errs[i], dataErrsByDisk[i], partsMetadata[i], latestMeta) yes, isMeta, reason := shouldHealObjectOnDisk(errs[i], dataErrsByDisk[i], partsMetadata[i], latestMeta)
if yes { if yes {
outDatedDisks[i] = storageDisks[i] outDatedDisks[i] = storageDisks[i]
disksToHealCount++ disksToHealCount++
if isMeta {
xlMetaToHealCount++
}
} }
driveState := "" driveState := objectErrToDriveState(reason)
switch {
case reason == nil:
driveState = madmin.DriveStateOk
case IsErr(reason, errDiskNotFound):
driveState = madmin.DriveStateOffline
case IsErr(reason, errFileNotFound, errFileVersionNotFound, errVolumeNotFound, errPartMissingOrCorrupt, errOutdatedXLMeta, errLegacyXLMeta):
driveState = madmin.DriveStateMissing
default:
// all remaining cases imply corrupt data/metadata
driveState = madmin.DriveStateCorrupt
}
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{ result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
UUID: "", UUID: "",
@@ -404,16 +410,6 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
}) })
} }
if isAllNotFound(errs) {
// File is fully gone, fileInfo is empty.
err := errFileNotFound
if versionID != "" {
err = errFileVersionNotFound
}
return er.defaultHealResult(FileInfo{}, storageDisks, storageEndpoints, errs,
bucket, object, versionID), err
}
if disksToHealCount == 0 { if disksToHealCount == 0 {
// Nothing to heal! // Nothing to heal!
return result, nil return result, nil
@@ -425,13 +421,23 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
return result, nil return result, nil
} }
cannotHeal := !latestMeta.XLV1 && !latestMeta.Deleted && disksToHealCount > latestMeta.Erasure.ParityBlocks cannotHeal := !latestMeta.XLV1 && !latestMeta.Deleted && xlMetaToHealCount > latestMeta.Erasure.ParityBlocks
if cannotHeal && quorumETag != "" { if cannotHeal && quorumETag != "" {
// This is an object that is supposed to be removed by the dangling code // This is an object that is supposed to be removed by the dangling code
// but we noticed that ETag is the same for all objects, let's give it a shot // but we noticed that ETag is the same for all objects, let's give it a shot
cannotHeal = false cannotHeal = false
} }
if !latestMeta.Deleted && !latestMeta.IsRemote() {
// check if there is a part that lost its quorum
for _, partErrs := range dataErrsByPart {
if countPartNotSuccess(partErrs) > latestMeta.Erasure.ParityBlocks {
cannotHeal = true
break
}
}
}
if cannotHeal { if cannotHeal {
// Allow for dangling deletes, on versions that have DataDir missing etc. // Allow for dangling deletes, on versions that have DataDir missing etc.
// this would end up restoring the correct readable versions. // this would end up restoring the correct readable versions.
@@ -470,16 +476,15 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
tmpID := mustGetUUID() tmpID := mustGetUUID()
migrateDataDir := mustGetUUID() migrateDataDir := mustGetUUID()
// Reorder so that we have data disks first and parity disks next. if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(onlineDisks) {
if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(availableDisks) { err := fmt.Errorf("unexpected file distribution (%v) from online disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)",
err := fmt.Errorf("unexpected file distribution (%v) from available disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)", latestMeta.Erasure.Distribution, onlineDisks, bucket, object, versionID)
latestMeta.Erasure.Distribution, availableDisks, bucket, object, versionID) healingLogOnceIf(ctx, err, "heal-object-online-disks")
healingLogOnceIf(ctx, err, "heal-object-available-disks")
return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs, return er.defaultHealResult(latestMeta, storageDisks, storageEndpoints, errs,
bucket, object, versionID), err bucket, object, versionID), err
} }
latestDisks := shuffleDisks(availableDisks, latestMeta.Erasure.Distribution) latestDisks := shuffleDisks(onlineDisks, latestMeta.Erasure.Distribution)
if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(outDatedDisks) { if !latestMeta.Deleted && len(latestMeta.Erasure.Distribution) != len(outDatedDisks) {
err := fmt.Errorf("unexpected file distribution (%v) from outdated disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)", err := fmt.Errorf("unexpected file distribution (%v) from outdated disks (%v), looks like backend disks have been manually modified refusing to heal %s/%s(%s)",
@@ -550,6 +555,10 @@ func (er *erasureObjects) healObject(ctx context.Context, bucket string, object
if disk == OfflineDisk { if disk == OfflineDisk {
continue continue
} }
thisPartErrs := shuffleCheckParts(dataErrsByPart[partIndex], latestMeta.Erasure.Distribution)
if thisPartErrs[i] != checkPartSuccess {
continue
}
checksumInfo := copyPartsMetadata[i].Erasure.GetChecksumInfo(partNumber) checksumInfo := copyPartsMetadata[i].Erasure.GetChecksumInfo(partNumber)
partPath := pathJoin(object, srcDataDir, fmt.Sprintf("part.%d", partNumber)) partPath := pathJoin(object, srcDataDir, fmt.Sprintf("part.%d", partNumber))
readers[i] = newBitrotReader(disk, copyPartsMetadata[i].Data, bucket, partPath, tillOffset, checksumAlgo, readers[i] = newBitrotReader(disk, copyPartsMetadata[i].Data, bucket, partPath, tillOffset, checksumAlgo,
@@ -817,13 +826,7 @@ func (er *erasureObjects) defaultHealResult(lfi FileInfo, storageDisks []Storage
}) })
continue continue
} }
driveState := madmin.DriveStateCorrupt driveState := objectErrToDriveState(errs[index])
switch errs[index] {
case errFileNotFound, errVolumeNotFound:
driveState = madmin.DriveStateMissing
case nil:
driveState = madmin.DriveStateOk
}
result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{ result.Before.Drives = append(result.Before.Drives, madmin.HealDriveInfo{
UUID: "", UUID: "",
Endpoint: storageEndpoints[index].String(), Endpoint: storageEndpoints[index].String(),
+3 -3
View File
@@ -733,7 +733,7 @@ func TestHealingDanglingObject(t *testing.T) {
t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions) t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions)
} }
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true}, if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Recursive: true, Remove: true},
func(bucket, object, vid string, scanMode madmin.HealScanMode) error { func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true}) _, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
return err return err
@@ -780,7 +780,7 @@ func TestHealingDanglingObject(t *testing.T) {
t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions) t.Fatalf("Expected versions 1, got %d", fileInfoPreHeal.NumVersions)
} }
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true}, if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Recursive: true, Remove: true},
func(bucket, object, vid string, scanMode madmin.HealScanMode) error { func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true}) _, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
return err return err
@@ -829,7 +829,7 @@ func TestHealingDanglingObject(t *testing.T) {
t.Fatalf("Expected versions 3, got %d", fileInfoPreHeal.NumVersions) t.Fatalf("Expected versions 3, got %d", fileInfoPreHeal.NumVersions)
} }
if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Remove: true}, if err = objLayer.HealObjects(ctx, bucket, "", madmin.HealOpts{Recursive: true, Remove: true},
func(bucket, object, vid string, scanMode madmin.HealScanMode) error { func(bucket, object, vid string, scanMode madmin.HealScanMode) error {
_, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true}) _, err := objLayer.HealObject(ctx, bucket, object, vid, madmin.HealOpts{ScanMode: scanMode, Remove: true})
return err return err
+32 -32
View File
@@ -52,15 +52,15 @@ func reduceCommonVersions(diskVersions [][]byte, writeQuorum int) (versions []by
} }
var commonVersions uint64 var commonVersions uint64
max := 0 maxCnt := 0
for versions, count := range diskVersionsCount { for versions, count := range diskVersionsCount {
if max < count { if maxCnt < count {
max = count maxCnt = count
commonVersions = versions commonVersions = versions
} }
} }
if max >= writeQuorum { if maxCnt >= writeQuorum {
for _, versions := range diskVersions { for _, versions := range diskVersions {
if binary.BigEndian.Uint64(versions) == commonVersions { if binary.BigEndian.Uint64(versions) == commonVersions {
return versions return versions
@@ -80,15 +80,15 @@ func reduceCommonDataDir(dataDirs []string, writeQuorum int) (dataDir string) {
dataDirsCount[ddir]++ dataDirsCount[ddir]++
} }
max := 0 maxCnt := 0
for ddir, count := range dataDirsCount { for ddir, count := range dataDirsCount {
if max < count { if maxCnt < count {
max = count maxCnt = count
dataDir = ddir dataDir = ddir
} }
} }
if max >= writeQuorum { if maxCnt >= writeQuorum {
return dataDir return dataDir
} }
@@ -115,20 +115,20 @@ func reduceErrs(errs []error, ignoredErrs []error) (maxCount int, maxErr error)
errorCounts[err]++ errorCounts[err]++
} }
max := 0 maxCnt := 0
for err, count := range errorCounts { for err, count := range errorCounts {
switch { switch {
case max < count: case maxCnt < count:
max = count maxCnt = count
maxErr = err maxErr = err
// Prefer `nil` over other error values with the same // Prefer `nil` over other error values with the same
// number of occurrences. // number of occurrences.
case max == count && err == nil: case maxCnt == count && err == nil:
maxErr = err maxErr = err
} }
} }
return max, maxErr return maxCnt, maxErr
} }
// reduceQuorumErrs behaves like reduceErrs by only for returning // reduceQuorumErrs behaves like reduceErrs by only for returning
@@ -295,34 +295,34 @@ func shuffleDisksAndPartsMetadata(disks []StorageAPI, partsMetadata []FileInfo,
return shuffledDisks, shuffledPartsMetadata return shuffledDisks, shuffledPartsMetadata
} }
// Return shuffled partsMetadata depending on distribution. func shuffleWithDist[T any](input []T, distribution []int) []T {
func shufflePartsMetadata(partsMetadata []FileInfo, distribution []int) (shuffledPartsMetadata []FileInfo) {
if distribution == nil { if distribution == nil {
return partsMetadata return input
} }
shuffledPartsMetadata = make([]FileInfo, len(partsMetadata)) shuffled := make([]T, len(input))
// Shuffle slice xl metadata for expected distribution. for index := range input {
for index := range partsMetadata {
blockIndex := distribution[index] blockIndex := distribution[index]
shuffledPartsMetadata[blockIndex-1] = partsMetadata[index] shuffled[blockIndex-1] = input[index]
} }
return shuffledPartsMetadata return shuffled
}
// Return shuffled partsMetadata depending on distribution.
func shufflePartsMetadata(partsMetadata []FileInfo, distribution []int) []FileInfo {
return shuffleWithDist[FileInfo](partsMetadata, distribution)
}
// shuffleCheckParts - shuffle CheckParts slice depending on the
// erasure distribution.
func shuffleCheckParts(parts []int, distribution []int) []int {
return shuffleWithDist[int](parts, distribution)
} }
// shuffleDisks - shuffle input disks slice depending on the // shuffleDisks - shuffle input disks slice depending on the
// erasure distribution. Return shuffled slice of disks with // erasure distribution. Return shuffled slice of disks with
// their expected distribution. // their expected distribution.
func shuffleDisks(disks []StorageAPI, distribution []int) (shuffledDisks []StorageAPI) { func shuffleDisks(disks []StorageAPI, distribution []int) []StorageAPI {
if distribution == nil { return shuffleWithDist[StorageAPI](disks, distribution)
return disks
}
shuffledDisks = make([]StorageAPI, len(disks))
// Shuffle disks for expected distribution.
for index := range disks {
blockIndex := distribution[index]
shuffledDisks[blockIndex-1] = disks[index]
}
return shuffledDisks
} }
// evalDisks - returns a new slice of disks where nil is set if // evalDisks - returns a new slice of disks where nil is set if
+4 -2
View File
@@ -514,8 +514,10 @@ func listObjectParities(partsMetadata []FileInfo, errs []error) (parities []int)
if metadata.Deleted || metadata.Size == 0 { if metadata.Deleted || metadata.Size == 0 {
parities[index] = totalShards / 2 parities[index] = totalShards / 2
} else if metadata.TransitionStatus == lifecycle.TransitionComplete { } else if metadata.TransitionStatus == lifecycle.TransitionComplete {
// For tiered objects, read quorum is N/2+1 to ensure simple majority on xl.meta. It is not equal to EcM because the data integrity is entrusted with the warm tier. // For tiered objects, read quorum is N/2+1 to ensure simple majority on xl.meta.
parities[index] = totalShards - (totalShards/2 + 1) // It is not equal to EcM because the data integrity is entrusted with the warm tier.
// However, we never go below EcM, in case of a EcM=EcN setup.
parities[index] = max(totalShards-(totalShards/2+1), metadata.Erasure.ParityBlocks)
} else { } else {
parities[index] = metadata.Erasure.ParityBlocks parities[index] = metadata.Erasure.ParityBlocks
} }
+12 -7
View File
@@ -626,17 +626,13 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
switch size := data.Size(); { switch size := data.Size(); {
case size == 0: case size == 0:
buffer = make([]byte, 1) // Allocate at least a byte to reach EOF buffer = make([]byte, 1) // Allocate at least a byte to reach EOF
case size == -1: case size >= fi.Erasure.BlockSize || size == -1:
if size := data.ActualSize(); size > 0 && size < fi.Erasure.BlockSize { if int64(globalBytePoolCap.Load().Width()) < fi.Erasure.BlockSize {
// Account for padding and forced compression overhead and encryption. buffer = make([]byte, fi.Erasure.BlockSize, 2*fi.Erasure.BlockSize)
buffer = make([]byte, data.ActualSize()+256+32+32, data.ActualSize()*2+512)
} else { } else {
buffer = globalBytePoolCap.Load().Get() buffer = globalBytePoolCap.Load().Get()
defer globalBytePoolCap.Load().Put(buffer) defer globalBytePoolCap.Load().Put(buffer)
} }
case size >= fi.Erasure.BlockSize:
buffer = globalBytePoolCap.Load().Get()
defer globalBytePoolCap.Load().Put(buffer)
case size < fi.Erasure.BlockSize: case size < fi.Erasure.BlockSize:
// No need to allocate fully fi.Erasure.BlockSize buffer if the incoming data is smaller. // No need to allocate fully fi.Erasure.BlockSize buffer if the incoming data is smaller.
buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1)) buffer = make([]byte, size, 2*size+int64(fi.Erasure.ParityBlocks+fi.Erasure.DataBlocks-1))
@@ -744,6 +740,15 @@ func (er erasureObjects) PutObjectPart(ctx context.Context, bucket, object, uplo
ctx = plkctx.Context() ctx = plkctx.Context()
defer partIDLock.Unlock(plkctx) defer partIDLock.Unlock(plkctx)
// Read lock for upload id, only held while reading the upload metadata.
uploadIDRLock := er.NewNSLock(bucket, pathJoin(object, uploadID))
rlkctx, err := uploadIDRLock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return PartInfo{}, err
}
ctx = rlkctx.Context()
defer uploadIDRLock.RUnlock(rlkctx)
onlineDisks, err = er.renamePart(ctx, onlineDisks, minioMetaTmpBucket, tmpPartPath, minioMetaMultipartBucket, partPath, partFI, writeQuorum) onlineDisks, err = er.renamePart(ctx, onlineDisks, minioMetaTmpBucket, tmpPartPath, minioMetaMultipartBucket, partPath, partFI, writeQuorum)
if err != nil { if err != nil {
if errors.Is(err, errFileNotFound) { if errors.Is(err, errFileNotFound) {
+7 -1
View File
@@ -2365,6 +2365,7 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
destObj, err := genTransitionObjName(bucket) destObj, err := genTransitionObjName(bucket)
if err != nil { if err != nil {
traceFn(ILMTransition, nil, err)
return err return err
} }
@@ -2375,9 +2376,14 @@ func (er erasureObjects) TransitionObject(ctx context.Context, bucket, object st
}() }()
var rv remoteVersionID var rv remoteVersionID
rv, err = tgtClient.Put(ctx, destObj, pr, fi.Size) rv, err = tgtClient.PutWithMeta(ctx, destObj, pr, fi.Size, map[string]string{
"name": object, // preserve the original name of the object on the remote tier object metadata.
// this is just for future reverse lookup() purposes (applies only for new objects)
// does not apply retro-actively on already transitioned objects.
})
pr.CloseWithError(err) pr.CloseWithError(err)
if err != nil { if err != nil {
traceFn(ILMTransition, nil, err)
return err return err
} }
fi.TransitionStatus = lifecycle.TransitionComplete fi.TransitionStatus = lifecycle.TransitionComplete
+1 -1
View File
@@ -845,7 +845,7 @@ func (z *erasureServerPools) decommissionPool(ctx context.Context, idx int, pool
// to decommission, just skip it, this also includes // to decommission, just skip it, this also includes
// any other versions that have already expired. // any other versions that have already expired.
remainingVersions := len(fivs.Versions) - expired remainingVersions := len(fivs.Versions) - expired
if version.Deleted && remainingVersions == 1 { if version.Deleted && remainingVersions == 1 && rcfg == nil {
decommissioned++ decommissioned++
stopFn(version.Size, errors.New("DELETE marked object with no other non-current versions will be skipped")) stopFn(version.Size, errors.New("DELETE marked object with no other non-current versions will be skipped"))
continue continue
+15 -17
View File
@@ -256,7 +256,7 @@ func (z *erasureServerPools) NewNSLock(bucket string, objects ...string) RWLocke
// GetDisksID will return disks by their ID. // GetDisksID will return disks by their ID.
func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI { func (z *erasureServerPools) GetDisksID(ids ...string) []StorageAPI {
idMap := make(map[string]struct{}) idMap := make(map[string]struct{}, len(ids))
for _, id := range ids { for _, id := range ids {
idMap[id] = struct{}{} idMap[id] = struct{}{}
} }
@@ -357,7 +357,7 @@ func (p serverPoolsAvailableSpace) TotalAvailable() uint64 {
// FilterMaxUsed will filter out any pools that has used percent bigger than max, // FilterMaxUsed will filter out any pools that has used percent bigger than max,
// unless all have that, in which case all are preserved. // unless all have that, in which case all are preserved.
func (p serverPoolsAvailableSpace) FilterMaxUsed(max int) { func (p serverPoolsAvailableSpace) FilterMaxUsed(maxUsed int) {
// We aren't modifying p, only entries in it, so we don't need to receive a pointer. // We aren't modifying p, only entries in it, so we don't need to receive a pointer.
if len(p) <= 1 { if len(p) <= 1 {
// Nothing to do. // Nothing to do.
@@ -365,7 +365,7 @@ func (p serverPoolsAvailableSpace) FilterMaxUsed(max int) {
} }
var ok bool var ok bool
for _, z := range p { for _, z := range p {
if z.Available > 0 && z.MaxUsedPct < max { if z.Available > 0 && z.MaxUsedPct < maxUsed {
ok = true ok = true
break break
} }
@@ -378,7 +378,7 @@ func (p serverPoolsAvailableSpace) FilterMaxUsed(max int) {
// Remove entries that are above. // Remove entries that are above.
for i, z := range p { for i, z := range p {
if z.Available > 0 && z.MaxUsedPct < max { if z.Available > 0 && z.MaxUsedPct < maxUsed {
continue continue
} }
p[i].Available = 0 p[i].Available = 0
@@ -1858,16 +1858,6 @@ func (z *erasureServerPools) PutObjectPart(ctx context.Context, bucket, object,
return PartInfo{}, err return PartInfo{}, err
} }
// Read lock for upload id.
// Only held while reading the upload metadata.
uploadIDRLock := z.NewNSLock(bucket, pathJoin(object, uploadID))
rlkctx, err := uploadIDRLock.GetRLock(ctx, globalOperationTimeout)
if err != nil {
return PartInfo{}, err
}
ctx = rlkctx.Context()
defer uploadIDRLock.RUnlock(rlkctx)
if z.SinglePool() { if z.SinglePool() {
return z.serverPools[0].PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts) return z.serverPools[0].PutObjectPart(ctx, bucket, object, uploadID, partID, data, opts)
} }
@@ -2478,6 +2468,7 @@ func (z *erasureServerPools) Walk(ctx context.Context, bucket, prefix string, re
// HealObjectFn closure function heals the object. // HealObjectFn closure function heals the object.
type HealObjectFn func(bucket, object, versionID string, scanMode madmin.HealScanMode) error type HealObjectFn func(bucket, object, versionID string, scanMode madmin.HealScanMode) error
// List a prefix or a single object versions and heal
func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObjectFn HealObjectFn) error { func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix string, opts madmin.HealOpts, healObjectFn HealObjectFn) error {
healEntry := func(bucket string, entry metaCacheEntry, scanMode madmin.HealScanMode) error { healEntry := func(bucket string, entry metaCacheEntry, scanMode madmin.HealScanMode) error {
if entry.isDir() { if entry.isDir() {
@@ -2541,7 +2532,7 @@ func (z *erasureServerPools) HealObjects(ctx context.Context, bucket, prefix str
go func(idx int, set *erasureObjects) { go func(idx int, set *erasureObjects) {
defer wg.Done() defer wg.Done()
errs[idx] = set.listAndHeal(ctx, bucket, prefix, opts.ScanMode, healEntry) errs[idx] = set.listAndHeal(ctx, bucket, prefix, opts.Recursive, opts.ScanMode, healEntry)
}(idx, set) }(idx, set)
} }
wg.Wait() wg.Wait()
@@ -2593,15 +2584,22 @@ func (z *erasureServerPools) HealObject(ctx context.Context, bucket, object, ver
} }
} }
hr := madmin.HealResultItem{
Type: madmin.HealItemObject,
Bucket: bucket,
Object: object,
VersionID: versionID,
}
// At this stage, all errors are 'not found' // At this stage, all errors are 'not found'
if versionID != "" { if versionID != "" {
return madmin.HealResultItem{}, VersionNotFound{ return hr, VersionNotFound{
Bucket: bucket, Bucket: bucket,
Object: object, Object: object,
VersionID: versionID, VersionID: versionID,
} }
} }
return madmin.HealResultItem{}, ObjectNotFound{ return hr, ObjectNotFound{
Bucket: bucket, Bucket: bucket,
Object: object, Object: object,
} }
+8 -2
View File
@@ -24,6 +24,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net"
"net/http"
"os" "os"
"path" "path"
"strings" "strings"
@@ -286,6 +288,10 @@ func (driver *ftpDriver) CheckPasswd(c *ftp.Context, username, password string)
} }
func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error) { func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error) {
tr := http.RoundTripper(globalRemoteFTPClientTransport)
if host, _, err := net.SplitHostPort(ctx.Sess.RemoteAddr().String()); err == nil {
tr = forwardForTransport{tr: tr, fwd: host}
}
ui, ok := globalIAMSys.GetUser(context.Background(), ctx.Sess.LoginUser()) ui, ok := globalIAMSys.GetUser(context.Background(), ctx.Sess.LoginUser())
if !ok && !globalIAMSys.LDAPConfig.Enabled() { if !ok && !globalIAMSys.LDAPConfig.Enabled() {
return nil, errNoSuchUser return nil, errNoSuchUser
@@ -363,7 +369,7 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
return minio.New(driver.endpoint, &minio.Options{ return minio.New(driver.endpoint, &minio.Options{
Creds: mcreds, Creds: mcreds,
Secure: globalIsTLS, Secure: globalIsTLS,
Transport: globalRemoteFTPClientTransport, Transport: tr,
}) })
} }
@@ -377,7 +383,7 @@ func (driver *ftpDriver) getMinIOClient(ctx *ftp.Context) (*minio.Client, error)
return minio.New(driver.endpoint, &minio.Options{ return minio.New(driver.endpoint, &minio.Options{
Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""), Creds: credentials.NewStaticV4(ui.Credentials.AccessKey, ui.Credentials.SecretKey, ""),
Secure: globalIsTLS, Secure: globalIsTLS,
Transport: globalRemoteFTPClientTransport, Transport: tr,
}) })
} }
+1 -3
View File
@@ -33,8 +33,6 @@ 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"
xnet "github.com/minio/pkg/v3/net" xnet "github.com/minio/pkg/v3/net"
"golang.org/x/exp/maps"
"golang.org/x/exp/slices"
"github.com/minio/minio/internal/amztime" "github.com/minio/minio/internal/amztime"
"github.com/minio/minio/internal/config/dns" "github.com/minio/minio/internal/config/dns"
@@ -75,7 +73,7 @@ const (
// and must not set by clients // and must not set by clients
func containsReservedMetadata(header http.Header) bool { func containsReservedMetadata(header http.Header) bool {
for key := range header { for key := range header {
if slices.Contains(maps.Keys(validSSEReplicationHeaders), key) { if _, ok := validSSEReplicationHeaders[key]; ok {
return false return false
} }
if stringsHasPrefixFold(key, ReservedMetadataPrefix) { if stringsHasPrefixFold(key, ReservedMetadataPrefix) {
+7 -7
View File
@@ -459,8 +459,6 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
continue continue
} }
var versionHealed bool
res, err := er.HealObject(ctx, bucket, encodedEntryName, res, err := er.HealObject(ctx, bucket, encodedEntryName,
version.VersionID, madmin.HealOpts{ version.VersionID, madmin.HealOpts{
ScanMode: scanMode, ScanMode: scanMode,
@@ -475,21 +473,23 @@ func (er *erasureObjects) healErasureSet(ctx context.Context, buckets []string,
} }
} else { } else {
// Look for the healing results // Look for the healing results
if res.After.Drives[tracker.DiskIndex].State == madmin.DriveStateOk { if res.After.Drives[tracker.DiskIndex].State != madmin.DriveStateOk {
versionHealed = true err = fmt.Errorf("unexpected after heal state: %s", res.After.Drives[tracker.DiskIndex].State)
} }
} }
if versionHealed { if err == nil {
bgSeq.countHealed(madmin.HealItemObject) bgSeq.countHealed(madmin.HealItemObject)
result = healEntrySuccess(uint64(version.Size)) result = healEntrySuccess(uint64(version.Size))
} else { } else {
bgSeq.countFailed(madmin.HealItemObject) bgSeq.countFailed(madmin.HealItemObject)
result = healEntryFailure(uint64(version.Size)) result = healEntryFailure(uint64(version.Size))
if version.VersionID != "" { if version.VersionID != "" {
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s-v(%s): %w", bucket, version.Name, version.VersionID, err)) healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s (version-id=%s): %w",
bucket, version.Name, version.VersionID, err))
} else { } else {
healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w", bucket, version.Name, err)) healingLogIf(ctx, fmt.Errorf("unable to heal object %s/%s: %w",
bucket, version.Name, err))
} }
} }
+8 -2
View File
@@ -793,7 +793,10 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
for _, item := range listedConfigItems[stsListKey] { for _, item := range listedConfigItems[stsListKey] {
userName := path.Dir(item) userName := path.Dir(item)
// loadUser() will delete expired user during the load. // loadUser() will delete expired user during the load.
iamLogIf(ctx, iamOS.loadUser(ctx, userName, stsUser, stsAccountsFromStore)) err := iamOS.loadUser(ctx, userName, stsUser, stsAccountsFromStore)
if err != nil && !errors.Is(err, errNoSuchUser) {
iamLogIf(ctx, err)
}
// No need to return errors for failed expiration of STS users // No need to return errors for failed expiration of STS users
} }
@@ -801,7 +804,10 @@ func (iamOS *IAMObjectStore) loadAllFromObjStore(ctx context.Context, cache *iam
// (removed during loadUser() in the loop above) are removed from memory. // (removed during loadUser() in the loop above) are removed from memory.
for _, item := range listedConfigItems[policyDBSTSUsersListKey] { for _, item := range listedConfigItems[policyDBSTSUsersListKey] {
stsName := strings.TrimSuffix(item, ".json") stsName := strings.TrimSuffix(item, ".json")
iamLogIf(ctx, iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, stsAccPoliciesFromStore)) err := iamOS.loadMappedPolicy(ctx, stsName, stsUser, false, stsAccPoliciesFromStore)
if err != nil && !errors.Is(err, errNoSuchPolicy) {
iamLogIf(ctx, err)
}
// No need to return errors for failed expiration of STS users // No need to return errors for failed expiration of STS users
} }
+91 -14
View File
@@ -26,6 +26,7 @@ import (
"path" "path"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@@ -37,6 +38,7 @@ import (
"github.com/minio/minio/internal/jwt" "github.com/minio/minio/internal/jwt"
"github.com/minio/pkg/v3/env" "github.com/minio/pkg/v3/env"
"github.com/minio/pkg/v3/policy" "github.com/minio/pkg/v3/policy"
"github.com/minio/pkg/v3/sync/errgroup"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"golang.org/x/sync/singleflight" "golang.org/x/sync/singleflight"
) )
@@ -357,6 +359,68 @@ func (c *iamCache) removeGroupFromMembershipsMap(group string) {
} }
} }
func (c *iamCache) policyDBGetGroups(store *IAMStoreSys, userPolicyPresent bool, groups ...string) ([]string, error) {
var policies []string
for _, group := range groups {
if store.getUsersSysType() == MinIOUsersSysType {
g, ok := c.iamGroupsMap[group]
if !ok {
continue
}
// Group is disabled, so we return no policy - this
// ensures the request is denied.
if g.Status == statusDisabled {
continue
}
}
policy, ok := c.iamGroupPolicyMap.Load(group)
if !ok {
continue
}
policies = append(policies, policy.toSlice()...)
}
found := len(policies) > 0
if found {
return policies, nil
}
if userPolicyPresent {
// if user mapping present and no group policies found
// rely on user policy for access, instead of fallback.
return nil, nil
}
var mu sync.Mutex
// no mappings found, fallback for all groups.
g := errgroup.WithNErrs(len(groups)).WithConcurrency(10) // load like 10 groups at a time.
for index := range groups {
index := index
g.Go(func() error {
err := store.loadMappedPolicy(context.TODO(), groups[index], regUser, true, c.iamGroupPolicyMap)
if err != nil && !errors.Is(err, errNoSuchPolicy) {
return err
}
if errors.Is(err, errNoSuchPolicy) {
return nil
}
policy, _ := c.iamGroupPolicyMap.Load(groups[index])
mu.Lock()
policies = append(policies, policy.toSlice()...)
mu.Unlock()
return nil
}, index)
}
err := errors.Join(g.Wait()...)
return policies, err
}
// policyDBGet - lower-level helper; does not take locks. // policyDBGet - lower-level helper; does not take locks.
// //
// If a group is passed, it returns policies associated with the group. // If a group is passed, it returns policies associated with the group.
@@ -676,7 +740,8 @@ func (store *IAMStoreSys) LoadIAMCache(ctx context.Context, firstTime bool) erro
type IAMStoreSys struct { type IAMStoreSys struct {
IAMStorageAPI IAMStorageAPI
group *singleflight.Group group *singleflight.Group
policy *singleflight.Group
} }
// HasWatcher - returns if the storage system has a watcher. // HasWatcher - returns if the storage system has a watcher.
@@ -757,21 +822,32 @@ func (store *IAMStoreSys) PolicyDBGet(name string, groups ...string) ([]string,
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
policies, _, err := cache.policyDBGet(store, name, false, false) getPolicies := func() ([]string, error) {
if err != nil { policies, _, err := cache.policyDBGet(store, name, false, false)
return nil, err
}
userPolicyPresent := len(policies) > 0
for _, group := range groups {
ps, _, err := cache.policyDBGet(store, group, true, userPolicyPresent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
policies = append(policies, ps...)
}
return policies, nil userPolicyPresent := len(policies) > 0
groupPolicies, err := cache.policyDBGetGroups(store, userPolicyPresent, groups...)
if err != nil {
return nil, err
}
policies = append(policies, groupPolicies...)
return policies, nil
}
if store.policy != nil {
val, err, _ := store.policy.Do(name, func() (interface{}, error) {
return getPolicies()
})
if err != nil {
return nil, err
}
return val.([]string), nil
}
return getPolicies()
} }
// AddUsersToGroup - adds users to group, creating the group if needed. // AddUsersToGroup - adds users to group, creating the group if needed.
@@ -2009,7 +2085,8 @@ type ParentUserInfo struct {
// GetAllParentUsers - returns all distinct "parent-users" associated with STS // GetAllParentUsers - returns all distinct "parent-users" associated with STS
// or service credentials, mapped to all distinct roleARNs associated with the // or service credentials, mapped to all distinct roleARNs associated with the
// parent user. The dummy role ARN is associated with parent users from // parent user. The dummy role ARN is associated with parent users from
// policy-claim based OpenID providers. // policy-claim based OpenID providers. The root credential as a parent
// user is not included in the result.
func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo { func (store *IAMStoreSys) GetAllParentUsers() map[string]ParentUserInfo {
cache := store.rlock() cache := store.rlock()
defer store.runlock() defer store.runlock()
@@ -2048,7 +2125,7 @@ func (store *IAMStoreSys) getParentUsers(cache *iamCache) map[string]ParentUserI
if err != nil { if err != nil {
continue continue
} }
if cred.ParentUser == "" { if cred.ParentUser == "" || cred.ParentUser == globalActiveCred.AccessKey {
continue continue
} }
+10 -4
View File
@@ -178,13 +178,18 @@ func (sys *IAMSys) initStore(objAPI ObjectLayer, etcdClient *etcd.Client) {
} }
if etcdClient == nil { if etcdClient == nil {
var group *singleflight.Group var (
group *singleflight.Group
policy *singleflight.Group
)
if env.Get("_MINIO_IAM_SINGLE_FLIGHT", config.EnableOn) == config.EnableOn { if env.Get("_MINIO_IAM_SINGLE_FLIGHT", config.EnableOn) == config.EnableOn {
group = &singleflight.Group{} group = &singleflight.Group{}
policy = &singleflight.Group{}
} }
sys.store = &IAMStoreSys{ sys.store = &IAMStoreSys{
IAMStorageAPI: newIAMObjectStore(objAPI, sys.usersSysType), IAMStorageAPI: newIAMObjectStore(objAPI, sys.usersSysType),
group: group, group: group,
policy: policy,
} }
} else { } else {
sys.store = &IAMStoreSys{IAMStorageAPI: newIAMEtcdStore(etcdClient, sys.usersSysType)} sys.store = &IAMStoreSys{IAMStorageAPI: newIAMEtcdStore(etcdClient, sys.usersSysType)}
@@ -406,6 +411,7 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
timer := time.NewTimer(waitInterval()) timer := time.NewTimer(waitInterval())
defer timer.Stop() defer timer.Stop()
lastPurgeHour := -1
for { for {
select { select {
case <-timer.C: case <-timer.C:
@@ -421,9 +427,9 @@ func (sys *IAMSys) periodicRoutines(ctx context.Context, baseInterval time.Durat
} }
} }
// The following actions are performed about once in 4 times that // Run purge routines once in each hour.
// IAM is refreshed: if refreshStart.Hour() != lastPurgeHour {
if r.Intn(4) == 0 { lastPurgeHour = refreshStart.Hour()
// Poll and remove accounts for those users who were removed // Poll and remove accounts for those users who were removed
// from LDAP/OpenID. // from LDAP/OpenID.
if sys.LDAPConfig.Enabled() { if sys.LDAPConfig.Enabled() {
+12 -16
View File
@@ -29,6 +29,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@@ -182,8 +183,7 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
resultsDone := make(chan metaCacheEntriesSorted) resultsDone := make(chan metaCacheEntriesSorted)
// Copy so we can mutate // Copy so we can mutate
resCh := resultsDone resCh := resultsDone
var done bool var done atomic.Bool
var mu sync.Mutex
resErr := io.EOF resErr := io.EOF
go func() { go func() {
@@ -194,9 +194,7 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
// past limit // past limit
continue continue
} }
mu.Lock() returned = done.Load()
returned = done
mu.Unlock()
if returned { if returned {
resCh = nil resCh = nil
continue continue
@@ -250,9 +248,7 @@ func (o *listPathOptions) gatherResults(ctx context.Context, in <-chan metaCache
return func() (metaCacheEntriesSorted, error) { return func() (metaCacheEntriesSorted, error) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
mu.Lock() done.Store(true)
done = true
mu.Unlock()
return metaCacheEntriesSorted{}, ctx.Err() return metaCacheEntriesSorted{}, ctx.Err()
case r := <-resultsDone: case r := <-resultsDone:
return r, resErr return r, resErr
@@ -626,18 +622,18 @@ func calcCommonWritesDeletes(infos []DiskInfo, readQuorum int) (commonWrite, com
} }
filter := func(list []uint64) (commonCount uint64) { filter := func(list []uint64) (commonCount uint64) {
max := 0 maxCnt := 0
signatureMap := map[uint64]int{} signatureMap := map[uint64]int{}
for _, v := range list { for _, v := range list {
signatureMap[v]++ signatureMap[v]++
} }
for ops, count := range signatureMap { for ops, count := range signatureMap {
if max < count && commonCount < ops { if maxCnt < count && commonCount < ops {
max = count maxCnt = count
commonCount = ops commonCount = ops
} }
} }
if max < readQuorum { if maxCnt < readQuorum {
return 0 return 0
} }
return commonCount return commonCount
@@ -650,7 +646,7 @@ func calcCommonWritesDeletes(infos []DiskInfo, readQuorum int) (commonWrite, com
func calcCommonCounter(infos []DiskInfo, readQuorum int) (commonCount uint64) { func calcCommonCounter(infos []DiskInfo, readQuorum int) (commonCount uint64) {
filter := func() (commonCount uint64) { filter := func() (commonCount uint64) {
max := 0 maxCnt := 0
signatureMap := map[uint64]int{} signatureMap := map[uint64]int{}
for _, info := range infos { for _, info := range infos {
if info.Error != "" { if info.Error != "" {
@@ -660,12 +656,12 @@ func calcCommonCounter(infos []DiskInfo, readQuorum int) (commonCount uint64) {
signatureMap[mutations]++ signatureMap[mutations]++
} }
for ops, count := range signatureMap { for ops, count := range signatureMap {
if max < count && commonCount < ops { if maxCnt < count && commonCount < ops {
max = count maxCnt = count
commonCount = ops commonCount = ops
} }
} }
if max < readQuorum { if maxCnt < readQuorum {
return 0 return 0
} }
return commonCount return commonCount
+17 -3
View File
@@ -685,6 +685,16 @@ func getUsageLastScanActivityMD() MetricDescription {
} }
} }
func getBucketUsageLastScanActivityMD() MetricDescription {
return MetricDescription{
Namespace: bucketMetricNamespace,
Subsystem: usageSubsystem,
Name: lastActivityTime,
Help: "Time elapsed (in nano seconds) since last scan activity",
Type: gaugeMetric,
}
}
func getBucketUsageQuotaTotalBytesMD() MetricDescription { func getBucketUsageQuotaTotalBytesMD() MetricDescription {
return MetricDescription{ return MetricDescription{
Namespace: bucketMetricNamespace, Namespace: bucketMetricNamespace,
@@ -1875,13 +1885,17 @@ func getHistogramMetrics(hist *prometheus.HistogramVec, desc MetricDescription,
// add metrics with +Inf label // add metrics with +Inf label
labels1 := make(map[string]string) labels1 := make(map[string]string)
for _, lp := range dtoMetric.GetLabel() { for _, lp := range dtoMetric.GetLabel() {
labels1[*lp.Name] = *lp.Value if *lp.Name == "api" && toLowerAPILabels {
labels1[*lp.Name] = strings.ToLower(*lp.Value)
} else {
labels1[*lp.Name] = *lp.Value
}
} }
labels1["le"] = fmt.Sprintf("%.3f", math.Inf(+1)) labels1["le"] = fmt.Sprintf("%.3f", math.Inf(+1))
metrics = append(metrics, MetricV2{ metrics = append(metrics, MetricV2{
Description: desc, Description: desc,
VariableLabels: labels1, VariableLabels: labels1,
Value: dtoMetric.Counter.GetValue(), Value: float64(dtoMetric.Histogram.GetSampleCount()),
}) })
} }
return metrics return metrics
@@ -3239,7 +3253,7 @@ func getBucketUsageMetrics(opts MetricsGroupOpts) *MetricsGroupV2 {
} }
metrics = append(metrics, MetricV2{ metrics = append(metrics, MetricV2{
Description: getUsageLastScanActivityMD(), Description: getBucketUsageLastScanActivityMD(),
Value: float64(time.Since(dataUsageInfo.LastUpdate)), Value: float64(time.Since(dataUsageInfo.LastUpdate)),
}) })
+133 -2
View File
@@ -18,13 +18,15 @@
package cmd package cmd
import ( import (
"slices"
"strings"
"testing" "testing"
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
func TestGetHistogramMetrics(t *testing.T) { func TestGetHistogramMetrics_BucketCount(t *testing.T) {
histBuckets := []float64{0.05, 0.1, 0.25, 0.5, 0.75} histBuckets := []float64{0.05, 0.1, 0.25, 0.5, 0.75}
labels := []string{"GetObject", "PutObject", "CopyObject", "CompleteMultipartUpload"} labels := []string{"GetObject", "PutObject", "CopyObject", "CompleteMultipartUpload"}
ttfbHist := prometheus.NewHistogramVec( ttfbHist := prometheus.NewHistogramVec(
@@ -83,6 +85,135 @@ func TestGetHistogramMetrics(t *testing.T) {
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), false) metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), false)
// additional labels for +Inf for all histogram metrics // additional labels for +Inf for all histogram metrics
if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) { if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) {
t.Fatalf("Expected %v data points but got %v", expPoints, len(metrics)) t.Fatalf("Expected %v data points when toLowerAPILabels=false but got %v", expPoints, len(metrics))
}
metrics = getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), true)
// additional labels for +Inf for all histogram metrics
if expPoints := len(labels) * (len(histBuckets) + 1); expPoints != len(metrics) {
t.Fatalf("Expected %v data points when toLowerAPILabels=true but got %v", expPoints, len(metrics))
}
}
func TestGetHistogramMetrics_Values(t *testing.T) {
histBuckets := []float64{0.50, 5.00}
labels := []string{"PutObject", "CopyObject"}
ttfbHist := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "s3_ttfb_seconds",
Help: "Time taken by requests served by current MinIO server instance",
Buckets: histBuckets,
},
[]string{"api"},
)
observations := []struct {
val float64
label string
}{
{
val: 0.02,
label: labels[0],
},
{
val: 0.19,
label: labels[1],
},
{
val: 0.31,
label: labels[1],
},
{
val: 0.61,
label: labels[0],
},
{
val: 6.79,
label: labels[1],
},
}
ticker := time.NewTicker(1 * time.Millisecond)
defer ticker.Stop()
for _, obs := range observations {
// Send observations once every 1ms, to simulate delay between
// observations. This is to test the channel based
// synchronization used internally.
select {
case <-ticker.C:
ttfbHist.With(prometheus.Labels{"api": obs.label}).Observe(obs.val)
}
}
// Accumulate regular-cased API label metrics for 'PutObject' for deeper verification
metrics := getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), false)
capitalPutObjects := make([]MetricV2, 0, len(histBuckets)+1)
for _, metric := range metrics {
if value := metric.VariableLabels["api"]; value == "PutObject" {
capitalPutObjects = append(capitalPutObjects, metric)
}
}
if expMetricsPerAPI := len(histBuckets) + 1; expMetricsPerAPI != len(capitalPutObjects) {
t.Fatalf("Expected %d api=PutObject metrics but got %d", expMetricsPerAPI, len(capitalPutObjects))
}
// Deterministic ordering
slices.SortFunc(capitalPutObjects, func(a MetricV2, b MetricV2) int {
le1 := a.VariableLabels["le"]
le2 := a.VariableLabels["le"]
return strings.Compare(le1, le2)
})
if le := capitalPutObjects[0].VariableLabels["le"]; le != "0.500" {
t.Errorf("Expected le='0.050' api=PutObject metrics but got '%v'", le)
}
if value := capitalPutObjects[0].Value; value != 1 {
t.Errorf("Expected le='0.050' api=PutObject value to be 1 but got '%v'", value)
}
if le := capitalPutObjects[1].VariableLabels["le"]; le != "5.000" {
t.Errorf("Expected le='5.000' api=PutObject metrics but got '%v'", le)
}
if value := capitalPutObjects[1].Value; value != 2 {
t.Errorf("Expected le='5.000' api=PutObject value to be 2 but got '%v'", value)
}
if le := capitalPutObjects[2].VariableLabels["le"]; le != "+Inf" {
t.Errorf("Expected le='+Inf' api=PutObject metrics but got '%v'", le)
}
if value := capitalPutObjects[2].Value; value != 2 {
t.Errorf("Expected le='+Inf' api=PutObject value to be 2 but got '%v'", value)
}
// Accumulate lower-cased API label metrics for 'copyobject' for deeper verification
metrics = getHistogramMetrics(ttfbHist, getBucketTTFBDistributionMD(), true)
lowerCopyObjects := make([]MetricV2, 0, len(histBuckets)+1)
for _, metric := range metrics {
if value := metric.VariableLabels["api"]; value == "copyobject" {
lowerCopyObjects = append(lowerCopyObjects, metric)
}
}
if expMetricsPerAPI := len(histBuckets) + 1; expMetricsPerAPI != len(lowerCopyObjects) {
t.Fatalf("Expected %d api=copyobject metrics but got %d", expMetricsPerAPI, len(lowerCopyObjects))
}
// Deterministic ordering
slices.SortFunc(lowerCopyObjects, func(a MetricV2, b MetricV2) int {
le1 := a.VariableLabels["le"]
le2 := a.VariableLabels["le"]
return strings.Compare(le1, le2)
})
if le := lowerCopyObjects[0].VariableLabels["le"]; le != "0.500" {
t.Errorf("Expected le='0.050' api=copyobject metrics but got '%v'", le)
}
if value := lowerCopyObjects[0].Value; value != 2 {
t.Errorf("Expected le='0.050' api=copyobject value to be 2 but got '%v'", value)
}
if le := lowerCopyObjects[1].VariableLabels["le"]; le != "5.000" {
t.Errorf("Expected le='5.000' api=copyobject metrics but got '%v'", le)
}
if value := lowerCopyObjects[1].Value; value != 2 {
t.Errorf("Expected le='5.000' api=copyobject value to be 2 but got '%v'", value)
}
if le := lowerCopyObjects[2].VariableLabels["le"]; le != "+Inf" {
t.Errorf("Expected le='+Inf' api=copyobject metrics but got '%v'", le)
}
if value := lowerCopyObjects[2].Value; value != 3 {
t.Errorf("Expected le='+Inf' api=copyobject value to be 3 but got '%v'", value)
} }
} }
+1 -1
View File
@@ -20,6 +20,7 @@ package cmd
import ( import (
"context" "context"
"fmt" "fmt"
"slices"
"strings" "strings"
"sync" "sync"
@@ -27,7 +28,6 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"golang.org/x/exp/slices"
) )
type collectorPath string type collectorPath string
-7
View File
@@ -404,12 +404,7 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
metadata = make(map[string]string) metadata = make(map[string]string)
} }
wantCRC, err := hash.GetContentChecksum(hdr)
if err != nil {
return opts, fmt.Errorf("invalid/unknown checksum sent: %v", err)
}
etag := strings.TrimSpace(hdr.Get(xhttp.MinIOSourceETag)) etag := strings.TrimSpace(hdr.Get(xhttp.MinIOSourceETag))
if crypto.S3KMS.IsRequested(hdr) { if crypto.S3KMS.IsRequested(hdr) {
keyID, context, err := crypto.S3KMS.ParseHTTP(hdr) keyID, context, err := crypto.S3KMS.ParseHTTP(hdr)
if err != nil { if err != nil {
@@ -423,7 +418,6 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
ServerSideEncryption: sseKms, ServerSideEncryption: sseKms,
UserDefined: metadata, UserDefined: metadata,
MTime: mtime, MTime: mtime,
WantChecksum: wantCRC,
PreserveETag: etag, PreserveETag: etag,
}, nil }, nil
} }
@@ -438,7 +432,6 @@ func putOptsFromHeaders(ctx context.Context, hdr http.Header, metadata map[strin
opts.ReplicationSourceRetentionTimestamp = retaintimestmp opts.ReplicationSourceRetentionTimestamp = retaintimestmp
opts.ReplicationSourceTaggingTimestamp = taggingtimestmp opts.ReplicationSourceTaggingTimestamp = taggingtimestmp
opts.PreserveETag = etag opts.PreserveETag = etag
opts.WantChecksum = wantCRC
return opts, nil return opts, nil
} }
+1 -1
View File
@@ -29,6 +29,7 @@ import (
"net/http" "net/http"
"path" "path"
"runtime" "runtime"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -52,7 +53,6 @@ import (
"github.com/minio/pkg/v3/trie" "github.com/minio/pkg/v3/trie"
"github.com/minio/pkg/v3/wildcard" "github.com/minio/pkg/v3/wildcard"
"github.com/valyala/bytebufferpool" "github.com/valyala/bytebufferpool"
"golang.org/x/exp/slices"
) )
const ( const (
+4 -4
View File
@@ -352,11 +352,11 @@ func isETagEqual(left, right string) bool {
// setPutObjHeaders sets all the necessary headers returned back // setPutObjHeaders sets all the necessary headers returned back
// upon a success Put/Copy/CompleteMultipart/Delete requests // upon a success Put/Copy/CompleteMultipart/Delete requests
// to activate delete only headers set delete as true // to activate delete only headers set delete as true
func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool, h http.Header) { func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, del bool, h http.Header) {
// We must not use the http.Header().Set method here because some (broken) // We must not use the http.Header().Set method here because some (broken)
// clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive). // clients expect the ETag header key to be literally "ETag" - not "Etag" (case-sensitive).
// Therefore, we have to set the ETag directly as map entry. // Therefore, we have to set the ETag directly as map entry.
if objInfo.ETag != "" && !delete { if objInfo.ETag != "" && !del {
w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`} w.Header()[xhttp.ETag] = []string{`"` + objInfo.ETag + `"`}
} }
@@ -364,13 +364,13 @@ func setPutObjHeaders(w http.ResponseWriter, objInfo ObjectInfo, delete bool, h
if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID { if objInfo.VersionID != "" && objInfo.VersionID != nullVersionID {
w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID} w.Header()[xhttp.AmzVersionID] = []string{objInfo.VersionID}
// If version is a deleted marker, set this header as well // If version is a deleted marker, set this header as well
if objInfo.DeleteMarker && delete { // only returned during delete object if objInfo.DeleteMarker && del { // only returned during delete object
w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)} w.Header()[xhttp.AmzDeleteMarker] = []string{strconv.FormatBool(objInfo.DeleteMarker)}
} }
} }
if objInfo.Bucket != "" && objInfo.Name != "" { if objInfo.Bucket != "" && objInfo.Name != "" {
if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !delete { if lc, err := globalLifecycleSys.Get(objInfo.Bucket); err == nil && !del {
lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts()) lc.SetPredictionHeaders(w, objInfo.ToLifecycleOpts())
} }
} }
+20 -11
View File
@@ -1191,6 +1191,9 @@ func (api objectAPIHandlers) CopyObjectHandler(w http.ResponseWriter, r *http.Re
return return
} }
// Sanitize the source object name similar to NewMultipart and PutObject API
srcObject = trimLeadingSlash(srcObject)
if vid != "" && vid != nullVersionID { if vid != "" && vid != nullVersionID {
_, err := uuid.Parse(vid) _, err := uuid.Parse(vid)
if err != nil { if err != nil {
@@ -1899,6 +1902,13 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
var reader io.Reader var reader io.Reader
reader = rd reader = rd
var opts ObjectOptions
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
actualSize := size actualSize := size
var idxCb func() []byte var idxCb func() []byte
if isCompressible(r.Header, object) && size > minCompressibleSize { if isCompressible(r.Header, object) && size > minCompressibleSize {
@@ -1915,6 +1925,8 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL) writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
return return
} }
opts.WantChecksum = actualReader.Checksum()
// Set compression metrics. // Set compression metrics.
var s2c io.ReadCloser var s2c io.ReadCloser
wantEncryption := crypto.Requested(r.Header) wantEncryption := crypto.Requested(r.Header)
@@ -1945,22 +1957,17 @@ func (api objectAPIHandlers) PutObjectHandler(w http.ResponseWriter, r *http.Req
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL) writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return return
} }
if err := hashReader.AddChecksum(r, size < 0); err != nil { if size >= 0 {
writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL) if err := hashReader.AddChecksum(r, false); err != nil {
return writeErrorResponse(ctx, w, errorCodes.ToAPIErr(ErrInvalidChecksum), r.URL)
return
}
opts.WantChecksum = hashReader.Checksum()
} }
rawReader := hashReader rawReader := hashReader
pReader := NewPutObjReader(rawReader) pReader := NewPutObjReader(rawReader)
var opts ObjectOptions
opts, err = putOptsFromReq(ctx, r, bucket, object, metadata)
if err != nil {
writeErrorResponse(ctx, w, toAPIError(ctx, err), r.URL)
return
}
opts.IndexCB = idxCb opts.IndexCB = idxCb
opts.WantChecksum = hashReader.Checksum()
if opts.PreserveETag != "" || if opts.PreserveETag != "" ||
r.Header.Get(xhttp.IfMatch) != "" || r.Header.Get(xhttp.IfMatch) != "" ||
@@ -2899,6 +2906,8 @@ func (api objectAPIHandlers) PutObjectRetentionHandler(w http.ResponseWriter, r
writeErrorResponse(ctx, w, apiErr, r.URL) writeErrorResponse(ctx, w, apiErr, r.URL)
return return
} }
reqInfo := logger.GetReqInfo(ctx)
reqInfo.SetTags("retention", objRetention.String())
opts, err := getOpts(ctx, r, bucket, object) opts, err := getOpts(ctx, r, bucket, object)
if err != nil { if err != nil {
+4
View File
@@ -1204,6 +1204,10 @@ func (api objectAPIHandlers) ListObjectPartsHandler(w http.ResponseWriter, r *ht
listPartsInfo.Parts[i].ETag = tryDecryptETag(objectEncryptionKey, p.ETag, kind == crypto.S3) listPartsInfo.Parts[i].ETag = tryDecryptETag(objectEncryptionKey, p.ETag, kind == crypto.S3)
listPartsInfo.Parts[i].Size = p.ActualSize listPartsInfo.Parts[i].Size = p.ActualSize
} }
} else if _, ok := listPartsInfo.UserDefined[ReservedMetadataPrefix+"compression"]; ok {
for i, p := range listPartsInfo.Parts {
listPartsInfo.Parts[i].Size = p.ActualSize
}
} }
response := generateListPartsResponse(listPartsInfo, encodingType) response := generateListPartsResponse(listPartsInfo, encodingType)
+17 -19
View File
@@ -60,20 +60,19 @@ func readDirFn(dirPath string, filter func(name string, typ os.FileMode) error)
if err != nil { if err != nil {
if err == syscall.ERROR_NO_MORE_FILES { if err == syscall.ERROR_NO_MORE_FILES {
break break
} else {
if isSysErrPathNotFound(err) {
return nil
}
err = osErrToFileErr(&os.PathError{
Op: "FindNextFile",
Path: dirPath,
Err: err,
})
if err == errFileNotFound {
return nil
}
return err
} }
if isSysErrPathNotFound(err) {
return nil
}
err = osErrToFileErr(&os.PathError{
Op: "FindNextFile",
Path: dirPath,
Err: err,
})
if err == errFileNotFound {
return nil
}
return err
} }
name := syscall.UTF16ToString(data.FileName[0:]) name := syscall.UTF16ToString(data.FileName[0:])
if name == "" || name == "." || name == ".." { // Useless names if name == "" || name == "." || name == ".." { // Useless names
@@ -136,13 +135,12 @@ func readDirWithOpts(dirPath string, opts readDirOpts) (entries []string, err er
if err != nil { if err != nil {
if err == syscall.ERROR_NO_MORE_FILES { if err == syscall.ERROR_NO_MORE_FILES {
break break
} else {
return nil, osErrToFileErr(&os.PathError{
Op: "FindNextFile",
Path: dirPath,
Err: err,
})
} }
return nil, osErrToFileErr(&os.PathError{
Op: "FindNextFile",
Path: dirPath,
Err: err,
})
} }
name := syscall.UTF16ToString(data.FileName[0:]) name := syscall.UTF16ToString(data.FileName[0:])
+5 -4
View File
@@ -56,6 +56,7 @@ var (
aoBucketInfo = grid.NewArrayOf[*BucketInfo](func() *BucketInfo { return &BucketInfo{} }) aoBucketInfo = grid.NewArrayOf[*BucketInfo](func() *BucketInfo { return &BucketInfo{} })
aoMetricsGroup = grid.NewArrayOf[*MetricV2](func() *MetricV2 { return &MetricV2{} }) aoMetricsGroup = grid.NewArrayOf[*MetricV2](func() *MetricV2 { return &MetricV2{} })
madminBgHealState = grid.NewJSONPool[madmin.BgHealState]() madminBgHealState = grid.NewJSONPool[madmin.BgHealState]()
madminHealResultItem = grid.NewJSONPool[madmin.HealResultItem]()
madminCPUs = grid.NewJSONPool[madmin.CPUs]() madminCPUs = grid.NewJSONPool[madmin.CPUs]()
madminMemInfo = grid.NewJSONPool[madmin.MemInfo]() madminMemInfo = grid.NewJSONPool[madmin.MemInfo]()
madminNetInfo = grid.NewJSONPool[madmin.NetInfo]() madminNetInfo = grid.NewJSONPool[madmin.NetInfo]()
@@ -97,7 +98,7 @@ var (
getSysErrorsRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysErrors]](grid.HandlerGetSysErrors, grid.NewMSS, madminSysErrors.NewJSON) getSysErrorsRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysErrors]](grid.HandlerGetSysErrors, grid.NewMSS, madminSysErrors.NewJSON)
getSysServicesRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysServices]](grid.HandlerGetSysServices, grid.NewMSS, madminSysServices.NewJSON) getSysServicesRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.SysServices]](grid.HandlerGetSysServices, grid.NewMSS, madminSysServices.NewJSON)
headBucketRPC = grid.NewSingleHandler[*grid.MSS, *VolInfo](grid.HandlerHeadBucket, grid.NewMSS, func() *VolInfo { return &VolInfo{} }) headBucketRPC = grid.NewSingleHandler[*grid.MSS, *VolInfo](grid.HandlerHeadBucket, grid.NewMSS, func() *VolInfo { return &VolInfo{} })
healBucketRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerHealBucket, grid.NewMSS, grid.NewNoPayload) healBucketRPC = grid.NewSingleHandler[*grid.MSS, *grid.JSON[madmin.HealResultItem]](grid.HandlerHealBucket, grid.NewMSS, madminHealResultItem.NewJSON)
listBucketsRPC = grid.NewSingleHandler[*BucketOptions, *grid.Array[*BucketInfo]](grid.HandlerListBuckets, func() *BucketOptions { return &BucketOptions{} }, aoBucketInfo.New) listBucketsRPC = grid.NewSingleHandler[*BucketOptions, *grid.Array[*BucketInfo]](grid.HandlerListBuckets, func() *BucketOptions { return &BucketOptions{} }, aoBucketInfo.New)
loadBucketMetadataRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadBucketMetadata, grid.NewMSS, grid.NewNoPayload).IgnoreNilConn() loadBucketMetadataRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadBucketMetadata, grid.NewMSS, grid.NewNoPayload).IgnoreNilConn()
loadGroupRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadGroup, grid.NewMSS, grid.NewNoPayload) loadGroupRPC = grid.NewSingleHandler[*grid.MSS, grid.NoPayload](grid.HandlerLoadGroup, grid.NewMSS, grid.NewNoPayload)
@@ -1258,21 +1259,21 @@ func (s *peerRESTServer) NetSpeedTestHandler(w http.ResponseWriter, r *http.Requ
peersLogIf(r.Context(), gob.NewEncoder(w).Encode(result)) peersLogIf(r.Context(), gob.NewEncoder(w).Encode(result))
} }
func (s *peerRESTServer) HealBucketHandler(mss *grid.MSS) (np grid.NoPayload, nerr *grid.RemoteErr) { func (s *peerRESTServer) HealBucketHandler(mss *grid.MSS) (np *grid.JSON[madmin.HealResultItem], nerr *grid.RemoteErr) {
bucket := mss.Get(peerS3Bucket) bucket := mss.Get(peerS3Bucket)
if isMinioMetaBucket(bucket) { if isMinioMetaBucket(bucket) {
return np, grid.NewRemoteErr(errInvalidArgument) return np, grid.NewRemoteErr(errInvalidArgument)
} }
bucketDeleted := mss.Get(peerS3BucketDeleted) == "true" bucketDeleted := mss.Get(peerS3BucketDeleted) == "true"
_, err := healBucketLocal(context.Background(), bucket, madmin.HealOpts{ res, err := healBucketLocal(context.Background(), bucket, madmin.HealOpts{
Remove: bucketDeleted, Remove: bucketDeleted,
}) })
if err != nil { if err != nil {
return np, grid.NewRemoteErr(err) return np, grid.NewRemoteErr(err)
} }
return np, nil return madminHealResultItem.NewJSONWith(&res), nil
} }
func (s *peerRESTServer) ListBucketsHandler(opts *BucketOptions) (*grid.Array[*BucketInfo], *grid.RemoteErr) { func (s *peerRESTServer) ListBucketsHandler(opts *BucketOptions) (*grid.Array[*BucketInfo], *grid.RemoteErr) {
+16 -11
View File
@@ -21,6 +21,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"slices"
"sort" "sort"
"strconv" "strconv"
"sync/atomic" "sync/atomic"
@@ -29,7 +30,6 @@ import (
"github.com/minio/madmin-go/v3" "github.com/minio/madmin-go/v3"
"github.com/minio/minio/internal/grid" "github.com/minio/minio/internal/grid"
"github.com/minio/pkg/v3/sync/errgroup" "github.com/minio/pkg/v3/sync/errgroup"
"golang.org/x/exp/slices"
) )
var errPeerOffline = errors.New("peer is offline") var errPeerOffline = errors.New("peer is offline")
@@ -178,13 +178,24 @@ func (sys *S3PeerSys) HealBucket(ctx context.Context, bucket string, opts madmin
} }
} }
if healBucketErr := reduceWriteQuorumErrs(ctx, errs, bucketOpIgnoredErrs, len(errs)/2+1); healBucketErr != nil {
return madmin.HealResultItem{}, toObjectErr(healBucketErr, bucket)
}
res := madmin.HealResultItem{
Type: madmin.HealItemBucket,
Bucket: bucket,
SetCount: -1, // explicitly set an invalid value -1, for bucket heal scenario
}
for i, err := range errs { for i, err := range errs {
if err == nil { if err == nil {
return healBucketResults[i], nil res.Before.Drives = append(res.Before.Drives, healBucketResults[i].Before.Drives...)
res.After.Drives = append(res.After.Drives, healBucketResults[i].After.Drives...)
} }
} }
return madmin.HealResultItem{}, toObjectErr(errVolumeNotFound, bucket) return res, nil
} }
// ListBuckets lists buckets across all nodes and returns a consistent view: // ListBuckets lists buckets across all nodes and returns a consistent view:
@@ -355,14 +366,8 @@ func (client *remotePeerS3Client) HealBucket(ctx context.Context, bucket string,
ctx, cancel := context.WithTimeout(ctx, globalDriveConfig.GetMaxTimeout()) ctx, cancel := context.WithTimeout(ctx, globalDriveConfig.GetMaxTimeout())
defer cancel() defer cancel()
_, err := healBucketRPC.Call(ctx, conn, mss) resp, err := healBucketRPC.Call(ctx, conn, mss)
return resp.ValueOrZero(), toStorageErr(err)
// Initialize heal result info
return madmin.HealResultItem{
Type: madmin.HealItemBucket,
Bucket: bucket,
SetCount: -1, // explicitly set an invalid value -1, for bucket heal scenario
}, toStorageErr(err)
} }
// GetBucketInfo returns bucket stat info from a peer // GetBucketInfo returns bucket stat info from a peer
+2 -2
View File
@@ -101,7 +101,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
for i := range beforeState { for i := range beforeState {
res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{ res.Before.Drives = append(res.Before.Drives, madmin.HealDriveInfo{
UUID: "", UUID: "",
Endpoint: localDrives[i].String(), Endpoint: localDrives[i].Endpoint().String(),
State: beforeState[i], State: beforeState[i],
}) })
} }
@@ -149,7 +149,7 @@ func healBucketLocal(ctx context.Context, bucket string, opts madmin.HealOpts) (
for i := range afterState { for i := range afterState {
res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{ res.After.Drives = append(res.After.Drives, madmin.HealDriveInfo{
UUID: "", UUID: "",
Endpoint: localDrives[i].String(), Endpoint: localDrives[i].Endpoint().String(),
State: afterState[i], State: afterState[i],
}) })
} }
+12 -8
View File
@@ -56,10 +56,12 @@ func newPostPolicyBytesV4WithContentRange(credential, bucketName, objectKey stri
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential) credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
// Add the meta-uuid string, set to 1234 // Add the meta-uuid string, set to 1234
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234") uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
// Add the content-encoding string, set to gzip.
contentEncodingConditionStr := fmt.Sprintf(`["eq", "$content-encoding", "%s"]`, "gzip")
// Combine all conditions into one string. // Combine all conditions into one string.
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr, conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr,
keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr) keyConditionStr, contentLengthCondStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr, contentEncodingConditionStr)
retStr := "{" retStr := "{"
retStr = retStr + expirationStr + "," retStr = retStr + expirationStr + ","
retStr += conditionStr retStr += conditionStr
@@ -85,9 +87,11 @@ func newPostPolicyBytesV4(credential, bucketName, objectKey string, expiration t
credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential) credentialConditionStr := fmt.Sprintf(`["eq", "$x-amz-credential", "%s"]`, credential)
// Add the meta-uuid string, set to 1234 // Add the meta-uuid string, set to 1234
uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234") uuidConditionStr := fmt.Sprintf(`["eq", "$x-amz-meta-uuid", "%s"]`, "1234")
// Add the content-encoding string, set to gzip
contentEncodingConditionStr := fmt.Sprintf(`["eq", "$content-encoding", "%s"]`, "gzip")
// Combine all conditions into one string. // Combine all conditions into one string.
conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr) conditionStr := fmt.Sprintf(`"conditions":[%s, %s, %s, %s, %s, %s, %s]`, bucketConditionStr, keyConditionStr, algorithmConditionStr, dateConditionStr, credentialConditionStr, uuidConditionStr, contentEncodingConditionStr)
retStr := "{" retStr := "{"
retStr = retStr + expirationStr + "," retStr = retStr + expirationStr + ","
retStr += conditionStr retStr += conditionStr
@@ -331,7 +335,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
}, },
// Success case, no multipart filename. // Success case, no multipart filename.
{ {
@@ -341,7 +345,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
noFilename: true, noFilename: true,
}, },
// Success case, big body. // Success case, big body.
@@ -352,7 +356,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
accessKey: credentials.AccessKey, accessKey: credentials.AccessKey,
secretKey: credentials.SecretKey, secretKey: credentials.SecretKey,
dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}, dates: []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)},
policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"]]}`, policy: `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], ["starts-with", "$key", "test/"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$x-amz-meta-uuid", "1234"],["eq", "$content-encoding", "gzip"]]}`,
}, },
// Corrupted Base 64 result // Corrupted Base 64 result
{ {
@@ -447,7 +451,7 @@ func testPostPolicyBucketHandler(obj ObjectLayer, instanceType string, t TestErr
malformedBody: false, malformedBody: false,
ignoreContentLength: false, ignoreContentLength: false,
}, },
// Failed with Content-Length not specified. // Success with Content-Length not specified.
{ {
objectName: "test", objectName: "test",
data: bytes.Repeat([]byte("a"), 1025), data: bytes.Repeat([]byte("a"), 1025),
@@ -547,7 +551,7 @@ func testPostPolicyBucketHandlerRedirect(obj ObjectLayer, instanceType string, t
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)} dates := []interface{}{curTimePlus5Min.Format(iso8601TimeFormat), curTime.Format(iso8601DateFormat), curTime.Format(yyyymmdd)}
policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"]]}` policy := `{"expiration": "%s","conditions":[["eq", "$bucket", "` + bucketName + `"], {"success_action_redirect":"` + redirectURL.String() + `"},["starts-with", "$key", "test/"], ["eq", "$x-amz-meta-uuid", "1234"], ["eq", "$x-amz-algorithm", "AWS4-HMAC-SHA256"], ["eq", "$x-amz-date", "%s"], ["eq", "$x-amz-credential", "` + credentials.AccessKey + `/%s/us-east-1/s3/aws4_request"],["eq", "$content-encoding", "gzip"]]}`
// Generate the final policy document // Generate the final policy document
policy = fmt.Sprintf(policy, dates...) policy = fmt.Sprintf(policy, dates...)
+51 -66
View File
@@ -53,18 +53,6 @@ var startsWithConds = map[string]bool{
"$x-amz-date": false, "$x-amz-date": false,
} }
var postPolicyIgnoreKeys = map[string]bool{
"Policy": true,
xhttp.AmzSignature: true,
xhttp.ContentEncoding: true,
http.CanonicalHeaderKey(xhttp.AmzChecksumAlgo): true,
http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32): true,
http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32C): true,
http.CanonicalHeaderKey(xhttp.AmzChecksumSHA1): true,
http.CanonicalHeaderKey(xhttp.AmzChecksumSHA256): true,
http.CanonicalHeaderKey(xhttp.AmzChecksumMode): true,
}
// Add policy conditionals. // Add policy conditionals.
const ( const (
policyCondEqual = "eq" policyCondEqual = "eq"
@@ -232,19 +220,19 @@ func parsePostPolicyForm(r io.Reader) (PostPolicyForm, error) {
operator, matchType, value, operator, matchType, value,
}) })
case policyCondContentLength: case policyCondContentLength:
min, err := toInteger(condt[1]) minLen, err := toInteger(condt[1])
if err != nil { if err != nil {
return parsedPolicy, err return parsedPolicy, err
} }
max, err := toInteger(condt[2]) maxLen, err := toInteger(condt[2])
if err != nil { if err != nil {
return parsedPolicy, err return parsedPolicy, err
} }
parsedPolicy.Conditions.ContentLengthRange = contentLengthRange{ parsedPolicy.Conditions.ContentLengthRange = contentLengthRange{
Min: min, Min: minLen,
Max: max, Max: maxLen,
Valid: true, Valid: true,
} }
default: default:
@@ -272,60 +260,57 @@ func checkPolicyCond(op string, input1, input2 string) bool {
return false return false
} }
// S3 docs: "Each form field that you specify in a form (except x-amz-signature, file, policy, and field names
// that have an x-ignore- prefix) must appear in the list of conditions."
// https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html
// keyInPolicyExceptions - list of keys that, when present in the form, can be missing in the conditions of the policy.
var keyInPolicyExceptions = map[string]bool{
xhttp.AmzSignature: true,
"File": true,
"Policy": true,
// MinIO specific exceptions to the general S3 rule above.
encrypt.SseKmsKeyID: true,
encrypt.SseEncryptionContext: true,
encrypt.SseCustomerAlgorithm: true,
encrypt.SseCustomerKey: true,
encrypt.SseCustomerKeyMD5: true,
}
// checkPostPolicy - apply policy conditions and validate input values. // checkPostPolicy - apply policy conditions and validate input values.
// (http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html) // Note that content-length-range is checked in the API handler function PostPolicyBucketHandler.
// formValues is the already-canonicalized form values from the POST request.
func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) error { func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) error {
// Check if policy document expiry date is still not reached // Check if policy document expiry date is still not reached
if !postPolicyForm.Expiration.After(UTCNow()) { if !postPolicyForm.Expiration.After(UTCNow()) {
return fmt.Errorf("Invalid according to Policy: Policy expired") return fmt.Errorf("Invalid according to Policy: Policy expired")
} }
// check all formValues appear in postPolicyForm or return error. #https://github.com/minio/minio/issues/17391
checkHeader := map[string][]string{}
ignoreKeys := map[string]bool{}
for key, value := range formValues {
switch {
case ignoreKeys[key], postPolicyIgnoreKeys[key], strings.HasPrefix(key, encrypt.SseGenericHeader):
continue
case strings.HasPrefix(key, "X-Amz-Ignore-"):
ignoreKey := strings.Replace(key, "X-Amz-Ignore-", "", 1)
ignoreKeys[ignoreKey] = true
// if it have already
delete(checkHeader, ignoreKey)
default:
checkHeader[key] = value
}
}
// map to store the metadata
metaMap := make(map[string]string)
for _, policy := range postPolicyForm.Conditions.Policies {
if strings.HasPrefix(policy.Key, "$x-amz-meta-") {
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
metaMap[formCanonicalName] = policy.Value
}
}
// Check if any extra metadata field is passed as input
for key := range formValues {
if strings.HasPrefix(key, "X-Amz-Meta-") {
if _, ok := metaMap[key]; !ok {
return fmt.Errorf("Invalid according to Policy: Extra input fields: %s", key)
}
}
}
// Flag to indicate if all policies conditions are satisfied // mustFindInPolicy is a map to list all the keys that we must find in the policy as
var condPassed bool // we process it below. At the end of checkPostPolicy function, if any key is left in
// this map, that's an error.
mustFindInPolicy := make(map[string][]string, len(formValues))
for key, values := range formValues {
if keyInPolicyExceptions[key] || strings.HasPrefix(key, "X-Ignore-") {
continue
}
mustFindInPolicy[key] = values
}
// Iterate over policy conditions and check them against received form fields // Iterate over policy conditions and check them against received form fields
for _, policy := range postPolicyForm.Conditions.Policies { for _, policy := range postPolicyForm.Conditions.Policies {
// Form fields names are in canonical format, convert conditions names // Form fields names are in canonical format, convert conditions names
// to canonical for simplification purpose, so `$key` will become `Key` // to canonical for simplification purpose, so `$key` will become `Key`
formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$")) formCanonicalName := http.CanonicalHeaderKey(strings.TrimPrefix(policy.Key, "$"))
// Operator for the current policy condition // Operator for the current policy condition
op := policy.Operator op := policy.Operator
// Multiple values should not occur
if len(checkHeader[formCanonicalName]) >= 2 { // Multiple values are not allowed for a single form field
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]", op, policy.Key, policy.Value, strings.Join(checkHeader[formCanonicalName], ", ")) if len(mustFindInPolicy[formCanonicalName]) >= 2 {
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]. FormValues have multiple values: [%s]", op, policy.Key, policy.Value, strings.Join(mustFindInPolicy[formCanonicalName], ", "))
} }
// If the current policy condition is known // If the current policy condition is known
if startsWithSupported, condFound := startsWithConds[policy.Key]; condFound { if startsWithSupported, condFound := startsWithConds[policy.Key]; condFound {
// Check if the current condition supports starts-with operator // Check if the current condition supports starts-with operator
@@ -333,35 +318,35 @@ func checkPostPolicy(formValues http.Header, postPolicyForm PostPolicyForm) erro
return fmt.Errorf("Invalid according to Policy: Policy Condition failed") return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
} }
// Check if current policy condition is satisfied // Check if current policy condition is satisfied
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) if !checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) {
if !condPassed {
return fmt.Errorf("Invalid according to Policy: Policy Condition failed") return fmt.Errorf("Invalid according to Policy: Policy Condition failed")
} }
} else if strings.HasPrefix(policy.Key, "$x-amz-meta-") || strings.HasPrefix(policy.Key, "$x-amz-") { } else if strings.HasPrefix(policy.Key, "$x-amz-meta-") || strings.HasPrefix(policy.Key, "$x-amz-") {
// This covers all conditions X-Amz-Meta-* and X-Amz-* // This covers all conditions X-Amz-Meta-* and X-Amz-*
// Check if policy condition is satisfied // Check if policy condition is satisfied
condPassed = checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) if !checkPolicyCond(op, formValues.Get(formCanonicalName), policy.Value) {
if !condPassed {
return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, policy.Key, policy.Value) return fmt.Errorf("Invalid according to Policy: Policy Condition failed: [%s, %s, %s]", op, policy.Key, policy.Value)
} }
} }
delete(checkHeader, formCanonicalName) delete(mustFindInPolicy, formCanonicalName)
} }
// For SignV2 - Signature/AWSAccessKeyId field will be ignored.
// For SignV2 - Signature/AWSAccessKeyId fields do not need to be in the policy
if _, ok := formValues[xhttp.AmzSignatureV2]; ok { if _, ok := formValues[xhttp.AmzSignatureV2]; ok {
delete(checkHeader, xhttp.AmzSignatureV2) delete(mustFindInPolicy, xhttp.AmzSignatureV2)
for k := range checkHeader { for k := range mustFindInPolicy {
// case-insensitivity for AWSAccessKeyId // case-insensitivity for AWSAccessKeyId
if strings.EqualFold(k, xhttp.AmzAccessKeyID) { if strings.EqualFold(k, xhttp.AmzAccessKeyID) {
delete(checkHeader, k) delete(mustFindInPolicy, k)
break break
} }
} }
} }
if len(checkHeader) != 0 { // Check mustFindInPolicy to see if any key is left, if so, it was not found in policy and we return an error.
logKeys := make([]string, 0, len(checkHeader)) if len(mustFindInPolicy) != 0 {
for key := range checkHeader { logKeys := make([]string, 0, len(mustFindInPolicy))
for key := range mustFindInPolicy {
logKeys = append(logKeys, key) logKeys = append(logKeys, key)
} }
return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", ")) return fmt.Errorf("Each form field that you specify in a form must appear in the list of policy conditions. %q not specified in the policy.", strings.Join(logKeys, ", "))
+201 -62
View File
@@ -20,12 +20,12 @@ package cmd
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"fmt"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
minio "github.com/minio/minio-go/v7" minio "github.com/minio/minio-go/v7"
xhttp "github.com/minio/minio/internal/http"
) )
func TestParsePostPolicyForm(t *testing.T) { func TestParsePostPolicyForm(t *testing.T) {
@@ -78,6 +78,28 @@ func TestParsePostPolicyForm(t *testing.T) {
} }
} }
type formValues struct {
http.Header
}
func newFormValues() formValues {
return formValues{make(http.Header)}
}
func (f formValues) Set(key, value string) formValues {
f.Header.Set(key, value)
return f
}
func (f formValues) Add(key, value string) formValues {
f.Header.Add(key, value)
return f
}
func (f formValues) Clone() formValues {
return formValues{f.Header.Clone()}
}
// Test Post Policy parsing and checking conditions // Test Post Policy parsing and checking conditions
func TestPostPolicyForm(t *testing.T) { func TestPostPolicyForm(t *testing.T) {
pp := minio.NewPostPolicy() pp := minio.NewPostPolicy()
@@ -85,76 +107,193 @@ func TestPostPolicyForm(t *testing.T) {
pp.SetContentType("image/jpeg") pp.SetContentType("image/jpeg")
pp.SetUserMetadata("uuid", "14365123651274") pp.SetUserMetadata("uuid", "14365123651274")
pp.SetKeyStartsWith("user/user1/filename") pp.SetKeyStartsWith("user/user1/filename")
pp.SetContentLengthRange(1048579, 10485760) pp.SetContentLengthRange(100, 999999) // not testable from this layer, condition is checked in the API handler.
pp.SetSuccessStatusAction("201") pp.SetSuccessStatusAction("201")
pp.SetCondition("eq", "X-Amz-Credential", "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request")
pp.SetCondition("eq", "X-Amz-Algorithm", "AWS4-HMAC-SHA256")
pp.SetCondition("eq", xhttp.AmzDate, "20160727T000000Z")
defaultFormVals := newFormValues()
defaultFormVals.Set("Bucket", "testbucket")
defaultFormVals.Set("Content-Type", "image/jpeg")
defaultFormVals.Set(xhttp.AmzMetaUUID, "14365123651274")
defaultFormVals.Set("Key", "user/user1/filename/${filename}/myfile.txt")
defaultFormVals.Set("X-Amz-Credential", "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request")
defaultFormVals.Set("X-Amz-Algorithm", "AWS4-HMAC-SHA256")
defaultFormVals.Set(xhttp.AmzDate, "20160727T000000Z")
defaultFormVals.Set("Success_action_status", "201")
policyCondFailedErr := "Invalid according to Policy: Policy Condition failed"
type testCase struct { type testCase struct {
Bucket string name string
Key string fv formValues
XAmzDate string expired bool
XAmzAlgorithm string wantErr string
XAmzCredential string
XAmzMetaUUID string
ContentType string
SuccessActionStatus string
Policy string
Expired bool
expectedErr error
} }
// Test case just contains fields we override from defaultFormVals.
testCases := []testCase{ testCases := []testCase{
// Everything is fine with this test {
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: nil}, name: "happy path no errors",
// Expired policy document fv: defaultFormVals.Clone(),
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", Expired: true, expectedErr: fmt.Errorf("Invalid according to Policy: Policy expired")}, wantErr: "",
// Different AMZ date },
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "2017T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")}, {
// Key which doesn't start with user/user1/filename name: "expired policy document",
{Bucket: "testbucket", Key: "myfile.txt", XAmzDate: "20160727T000000Z", XAmzMetaUUID: "14365123651274", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")}, fv: defaultFormVals.Clone(),
// Incorrect bucket name. expired: true,
{Bucket: "incorrect", Key: "user/user1/filename/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")}, wantErr: "Invalid according to Policy: Policy expired",
// Incorrect key name },
{Bucket: "testbucket", Key: "incorrect", XAmzDate: "20160727T000000Z", XAmzMetaUUID: "14365123651274", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")}, {
// Incorrect date name: "different AMZ date",
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "incorrect", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")}, fv: defaultFormVals.Clone().Set(xhttp.AmzDate, "2017T000000Z"),
// Incorrect ContentType wantErr: policyCondFailedErr,
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "14365123651274", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "incorrect", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed")}, },
// Incorrect Metadata {
{Bucket: "testbucket", Key: "user/user1/filename/${filename}/myfile.txt", XAmzMetaUUID: "151274", SuccessActionStatus: "201", XAmzCredential: "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", XAmzDate: "20160727T000000Z", XAmzAlgorithm: "AWS4-HMAC-SHA256", ContentType: "image/jpeg", expectedErr: fmt.Errorf("Invalid according to Policy: Policy Condition failed: [eq, $x-amz-meta-uuid, 14365123651274]")}, name: "incorrect date",
fv: defaultFormVals.Clone().Set(xhttp.AmzDate, "incorrect"),
wantErr: policyCondFailedErr,
},
{
name: "key which doesn't start with user/user1/filename",
fv: defaultFormVals.Clone().Set("Key", "myfile.txt"),
wantErr: policyCondFailedErr,
},
{
name: "incorrect key name",
fv: defaultFormVals.Clone().Set("Key", "incorrect"),
wantErr: policyCondFailedErr,
},
{
name: "incorrect bucket name",
fv: defaultFormVals.Clone().Set("Bucket", "incorrect"),
wantErr: policyCondFailedErr,
},
{
name: "incorrect ContentType",
fv: defaultFormVals.Clone().Set(xhttp.ContentType, "incorrect"),
wantErr: policyCondFailedErr,
},
{
name: "incorrect X-Amz-Algorithm",
fv: defaultFormVals.Clone().Set(xhttp.AmzAlgorithm, "incorrect"),
wantErr: policyCondFailedErr,
},
{
name: "incorrect X-Amz-Credential",
fv: defaultFormVals.Clone().Set(xhttp.AmzCredential, "incorrect"),
wantErr: policyCondFailedErr,
},
{
name: "incorrect metadata uuid",
fv: defaultFormVals.Clone().Set(xhttp.AmzMetaUUID, "151274"),
wantErr: "Invalid according to Policy: Policy Condition failed: [eq, $x-amz-meta-uuid, 14365123651274]",
},
{
name: "unknown key XAmzMetaName is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(xhttp.AmzMetaName, "my-name"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Meta-Name" not specified in the policy.`,
},
{
name: "unknown key XAmzChecksumAlgo is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumAlgo), "algo-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Algorithm" not specified in the policy.`,
},
{
name: "unknown key XAmzChecksumCRC32 is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32), "crc32-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Crc32" not specified in the policy.`,
},
{
name: "unknown key XAmzChecksumCRC32C is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumCRC32C), "crc32c-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Crc32c" not specified in the policy.`,
},
{
name: "unknown key XAmzChecksumSHA1 is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumSHA1), "sha1-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Sha1" not specified in the policy.`,
},
{
name: "unknown key XAmzChecksumSHA256 is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumSHA256), "sha256-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Sha256" not specified in the policy.`,
},
{
name: "unknown key XAmzChecksumMode is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.AmzChecksumMode), "mode-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "X-Amz-Checksum-Mode" not specified in the policy.`,
},
{
name: "unknown key Content-Encoding is error as it does not appear in policy",
fv: defaultFormVals.Clone().Set(http.CanonicalHeaderKey(xhttp.ContentEncoding), "encoding-val"),
wantErr: `Each form field that you specify in a form must appear in the list of policy conditions. "Content-Encoding" not specified in the policy.`,
},
{
name: "many bucket values",
fv: defaultFormVals.Clone().Add("Bucket", "anotherbucket"),
wantErr: "Invalid according to Policy: Policy Condition failed: [eq, $bucket, testbucket]. FormValues have multiple values: [testbucket, anotherbucket]",
},
{
name: "XAmzSignature does not have to appear in policy",
fv: defaultFormVals.Clone().Set(xhttp.AmzSignature, "my-signature"),
},
{
name: "XIgnoreFoo does not have to appear in policy",
fv: defaultFormVals.Clone().Set("X-Ignore-Foo", "my-foo-value"),
},
{
name: "File does not have to appear in policy",
fv: defaultFormVals.Clone().Set("File", "file-value"),
},
{
name: "Signature does not have to appear in policy",
fv: defaultFormVals.Clone().Set(xhttp.AmzSignatureV2, "signature-value"),
},
{
name: "AWSAccessKeyID does not have to appear in policy",
fv: defaultFormVals.Clone().Set(xhttp.AmzAccessKeyID, "access").Set(xhttp.AmzSignatureV2, "signature-value"),
},
{
name: "any form value starting with X-Amz-Server-Side-Encryption- does not have to appear in policy",
fv: defaultFormVals.Clone().
Set(xhttp.AmzServerSideEncryptionKmsContext, "context-val").
Set(xhttp.AmzServerSideEncryptionCustomerAlgorithm, "algo-val"),
},
} }
// Validate all the test cases.
for i, tt := range testCases {
formValues := make(http.Header)
formValues.Set("Bucket", tt.Bucket)
formValues.Set("Key", tt.Key)
formValues.Set("Content-Type", tt.ContentType)
formValues.Set("X-Amz-Date", tt.XAmzDate)
formValues.Set("X-Amz-Meta-Uuid", tt.XAmzMetaUUID)
formValues.Set("X-Amz-Algorithm", tt.XAmzAlgorithm)
formValues.Set("X-Amz-Credential", tt.XAmzCredential)
if tt.Expired {
// Expired already.
pp.SetExpires(UTCNow().AddDate(0, 0, -10))
} else {
// Expires in 10 days.
pp.SetExpires(UTCNow().AddDate(0, 0, 10))
}
formValues.Set("Policy", base64.StdEncoding.EncodeToString([]byte(pp.String()))) // Run tests
formValues.Set("Success_action_status", tt.SuccessActionStatus) for _, tt := range testCases {
policyBytes, err := base64.StdEncoding.DecodeString(base64.StdEncoding.EncodeToString([]byte(pp.String()))) t.Run(tt.name, func(t *testing.T) {
if err != nil { if tt.expired {
t.Fatal(err) // Expired already.
} pp.SetExpires(UTCNow().AddDate(0, 0, -10))
} else {
// Expires in 10 days.
pp.SetExpires(UTCNow().AddDate(0, 0, 10))
}
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes)) tt.fv.Set("Policy", base64.StdEncoding.EncodeToString([]byte(pp.String())))
if err != nil {
t.Fatal(err)
}
err = checkPostPolicy(formValues, postPolicyForm) policyBytes, err := base64.StdEncoding.DecodeString(base64.StdEncoding.EncodeToString([]byte(pp.String())))
if err != nil && tt.expectedErr != nil && err.Error() != tt.expectedErr.Error() { if err != nil {
t.Fatalf("Test %d:, Expected %s, got %s", i+1, tt.expectedErr.Error(), err.Error()) t.Fatal(err)
} }
postPolicyForm, err := parsePostPolicyForm(bytes.NewReader(policyBytes))
if err != nil {
t.Fatal(err)
}
errStr := ""
err = checkPostPolicy(tt.fv.Header, postPolicyForm)
if err != nil {
errStr = err.Error()
}
if errStr != tt.wantErr {
t.Errorf("test: '%s', want error: '%s', got error: '%s'", tt.name, tt.wantErr, errStr)
}
})
} }
} }
+4 -1
View File
@@ -31,6 +31,7 @@ import (
"os/signal" "os/signal"
"path/filepath" "path/filepath"
"runtime" "runtime"
"slices"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@@ -53,7 +54,6 @@ import (
"github.com/minio/minio/internal/logger" "github.com/minio/minio/internal/logger"
"github.com/minio/pkg/v3/certs" "github.com/minio/pkg/v3/certs"
"github.com/minio/pkg/v3/env" "github.com/minio/pkg/v3/env"
"golang.org/x/exp/slices"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@@ -421,6 +421,7 @@ func serverHandleCmdArgs(ctxt serverCtxt) {
Interface: ctxt.Interface, Interface: ctxt.Interface,
SendBufSize: ctxt.SendBufSize, SendBufSize: ctxt.SendBufSize,
RecvBufSize: ctxt.RecvBufSize, RecvBufSize: ctxt.RecvBufSize,
IdleTimeout: ctxt.IdleTimeout,
} }
// allow transport to be HTTP/1.1 for proxying. // allow transport to be HTTP/1.1 for proxying.
@@ -878,6 +879,8 @@ func serverMain(ctx *cli.Context) {
UseHandler(setCriticalErrorHandler(corsHandler(handler))). UseHandler(setCriticalErrorHandler(corsHandler(handler))).
UseTLSConfig(newTLSConfig(getCert)). UseTLSConfig(newTLSConfig(getCert)).
UseIdleTimeout(globalServerCtxt.IdleTimeout). UseIdleTimeout(globalServerCtxt.IdleTimeout).
UseReadTimeout(globalServerCtxt.IdleTimeout).
UseWriteTimeout(globalServerCtxt.IdleTimeout).
UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout). UseReadHeaderTimeout(globalServerCtxt.ReadHeaderTimeout).
UseBaseContext(GlobalContext). UseBaseContext(GlobalContext).
UseCustomLogger(log.New(io.Discard, "", 0)). // Turn-off random logging by Go stdlib UseCustomLogger(log.New(io.Discard, "", 0)). // Turn-off random logging by Go stdlib
+24 -3
View File
@@ -23,6 +23,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"path" "path"
"strings" "strings"
@@ -45,6 +46,7 @@ const ftpMaxWriteOffset = 100 << 20
type sftpDriver struct { type sftpDriver struct {
permissions *ssh.Permissions permissions *ssh.Permissions
endpoint string endpoint string
remoteIP string
} }
//msgp:ignore sftpMetrics //msgp:ignore sftpMetrics
@@ -89,8 +91,12 @@ func (m *sftpMetrics) log(s *sftp.Request, user string) func(sz int64, err error
// - sftp.Filewrite // - sftp.Filewrite
// - sftp.Filelist // - sftp.Filelist
// - sftp.Filecmd // - sftp.Filecmd
func NewSFTPDriver(perms *ssh.Permissions) sftp.Handlers { func NewSFTPDriver(perms *ssh.Permissions, remoteIP string) sftp.Handlers {
handler := &sftpDriver{endpoint: fmt.Sprintf("127.0.0.1:%s", globalMinioPort), permissions: perms} handler := &sftpDriver{
endpoint: fmt.Sprintf("127.0.0.1:%s", globalMinioPort),
permissions: perms,
remoteIP: remoteIP,
}
return sftp.Handlers{ return sftp.Handlers{
FileGet: handler, FileGet: handler,
FilePut: handler, FilePut: handler,
@@ -99,16 +105,31 @@ func NewSFTPDriver(perms *ssh.Permissions) sftp.Handlers {
} }
} }
type forwardForTransport struct {
tr http.RoundTripper
fwd string
}
func (f forwardForTransport) RoundTrip(r *http.Request) (*http.Response, error) {
r.Header.Set("X-Forwarded-For", f.fwd)
return f.tr.RoundTrip(r)
}
func (f *sftpDriver) getMinIOClient() (*minio.Client, error) { func (f *sftpDriver) getMinIOClient() (*minio.Client, error) {
mcreds := credentials.NewStaticV4( mcreds := credentials.NewStaticV4(
f.permissions.CriticalOptions["AccessKey"], f.permissions.CriticalOptions["AccessKey"],
f.permissions.CriticalOptions["SecretKey"], f.permissions.CriticalOptions["SecretKey"],
f.permissions.CriticalOptions["SessionToken"], f.permissions.CriticalOptions["SessionToken"],
) )
// Set X-Forwarded-For on all requests.
tr := http.RoundTripper(globalRemoteFTPClientTransport)
if f.remoteIP != "" {
tr = forwardForTransport{tr: tr, fwd: f.remoteIP}
}
return minio.New(f.endpoint, &minio.Options{ return minio.New(f.endpoint, &minio.Options{
Creds: mcreds, Creds: mcreds,
Secure: globalIsTLS, Secure: globalIsTLS,
Transport: globalRemoteFTPClientTransport, Transport: tr,
}) })
} }
+6 -1
View File
@@ -488,7 +488,12 @@ func startSFTPServer(args []string) {
sshConfig.AddHostKey(private) sshConfig.AddHostKey(private)
handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) { handleSFTPSession := func(channel ssh.Channel, sconn *ssh.ServerConn) {
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions), sftp.WithRSAllocator()) var remoteIP string
if host, _, err := net.SplitHostPort(sconn.RemoteAddr().String()); err == nil {
remoteIP = host
}
server := sftp.NewRequestServer(channel, NewSFTPDriver(sconn.Permissions, remoteIP), sftp.WithRSAllocator())
defer server.Close() defer server.Close()
server.Serve() server.Serve()
} }
+1 -1
View File
@@ -23,6 +23,7 @@ import (
"encoding/hex" "encoding/hex"
"io" "io"
"net/http" "net/http"
"slices"
"strconv" "strconv"
"strings" "strings"
@@ -31,7 +32,6 @@ 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/pkg/v3/policy" "github.com/minio/pkg/v3/policy"
"golang.org/x/exp/slices"
) )
// http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the // http Header "x-amz-content-sha256" == "UNSIGNED-PAYLOAD" indicates that the
+1 -1
View File
@@ -60,7 +60,7 @@ const (
// getCanonicalHeaders generate a list of request headers with their values // getCanonicalHeaders generate a list of request headers with their values
func getCanonicalHeaders(signedHeaders http.Header) string { func getCanonicalHeaders(signedHeaders http.Header) string {
var headers []string var headers []string
vals := make(http.Header) vals := make(http.Header, len(signedHeaders))
for k, vv := range signedHeaders { for k, vv := range signedHeaders {
k = strings.ToLower(k) k = strings.ToLower(k)
headers = append(headers, k) headers = append(headers, k)
+2
View File
@@ -25,6 +25,8 @@ import (
xioutil "github.com/minio/minio/internal/ioutil" xioutil "github.com/minio/minio/internal/ioutil"
) )
//msgp:clearomitted
//go:generate msgp -file=$GOFILE //go:generate msgp -file=$GOFILE
// DeleteOptions represents the disk level delete options available for the APIs // DeleteOptions represents the disk level delete options available for the APIs
+343 -261
View File
@@ -746,6 +746,8 @@ func (z *DeleteOptions) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, err = dc.ReadMapKeyPtr() field, err = dc.ReadMapKeyPtr()
@@ -801,6 +803,7 @@ func (z *DeleteOptions) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "OldDataDir") err = msgp.WrapError(err, "OldDataDir")
return return
} }
zb0001Mask |= 0x1
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -809,6 +812,11 @@ func (z *DeleteOptions) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.OldDataDir = ""
}
return return
} }
@@ -827,61 +835,62 @@ func (z *DeleteOptions) EncodeMsg(en *msgp.Writer) (err error) {
if err != nil { if err != nil {
return return
} }
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// write "BaseOptions" // write "BaseOptions"
err = en.Append(0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73) err = en.Append(0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
if err != nil {
return
}
// map header, size 0
_ = z.BaseOptions
err = en.Append(0x80)
if err != nil {
return
}
// write "r"
err = en.Append(0xa1, 0x72)
if err != nil {
return
}
err = en.WriteBool(z.Recursive)
if err != nil {
err = msgp.WrapError(err, "Recursive")
return
}
// write "i"
err = en.Append(0xa1, 0x69)
if err != nil {
return
}
err = en.WriteBool(z.Immediate)
if err != nil {
err = msgp.WrapError(err, "Immediate")
return
}
// write "u"
err = en.Append(0xa1, 0x75)
if err != nil {
return
}
err = en.WriteBool(z.UndoWrite)
if err != nil {
err = msgp.WrapError(err, "UndoWrite")
return
}
if (zb0001Mask & 0x10) == 0 { // if not omitted
// write "o"
err = en.Append(0xa1, 0x6f)
if err != nil { if err != nil {
return return
} }
err = en.WriteString(z.OldDataDir) // map header, size 0
_ = z.BaseOptions
err = en.Append(0x80)
if err != nil { if err != nil {
err = msgp.WrapError(err, "OldDataDir")
return return
} }
// write "r"
err = en.Append(0xa1, 0x72)
if err != nil {
return
}
err = en.WriteBool(z.Recursive)
if err != nil {
err = msgp.WrapError(err, "Recursive")
return
}
// write "i"
err = en.Append(0xa1, 0x69)
if err != nil {
return
}
err = en.WriteBool(z.Immediate)
if err != nil {
err = msgp.WrapError(err, "Immediate")
return
}
// write "u"
err = en.Append(0xa1, 0x75)
if err != nil {
return
}
err = en.WriteBool(z.UndoWrite)
if err != nil {
err = msgp.WrapError(err, "UndoWrite")
return
}
if (zb0001Mask & 0x10) == 0 { // if not omitted
// write "o"
err = en.Append(0xa1, 0x6f)
if err != nil {
return
}
err = en.WriteString(z.OldDataDir)
if err != nil {
err = msgp.WrapError(err, "OldDataDir")
return
}
}
} }
return return
} }
@@ -899,27 +908,28 @@ func (z *DeleteOptions) MarshalMsg(b []byte) (o []byte, err error) {
} }
// variable map header, size zb0001Len // variable map header, size zb0001Len
o = append(o, 0x80|uint8(zb0001Len)) o = append(o, 0x80|uint8(zb0001Len))
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// string "BaseOptions" // string "BaseOptions"
o = append(o, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73) o = append(o, 0xab, 0x42, 0x61, 0x73, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73)
// map header, size 0 // map header, size 0
_ = z.BaseOptions _ = z.BaseOptions
o = append(o, 0x80) o = append(o, 0x80)
// string "r" // string "r"
o = append(o, 0xa1, 0x72) o = append(o, 0xa1, 0x72)
o = msgp.AppendBool(o, z.Recursive) o = msgp.AppendBool(o, z.Recursive)
// string "i" // string "i"
o = append(o, 0xa1, 0x69) o = append(o, 0xa1, 0x69)
o = msgp.AppendBool(o, z.Immediate) o = msgp.AppendBool(o, z.Immediate)
// string "u" // string "u"
o = append(o, 0xa1, 0x75) o = append(o, 0xa1, 0x75)
o = msgp.AppendBool(o, z.UndoWrite) o = msgp.AppendBool(o, z.UndoWrite)
if (zb0001Mask & 0x10) == 0 { // if not omitted if (zb0001Mask & 0x10) == 0 { // if not omitted
// string "o" // string "o"
o = append(o, 0xa1, 0x6f) o = append(o, 0xa1, 0x6f)
o = msgp.AppendString(o, z.OldDataDir) o = msgp.AppendString(o, z.OldDataDir)
}
} }
return return
} }
@@ -934,6 +944,8 @@ func (z *DeleteOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts) field, bts, err = msgp.ReadMapKeyZC(bts)
@@ -989,6 +1001,7 @@ func (z *DeleteOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "OldDataDir") err = msgp.WrapError(err, "OldDataDir")
return return
} }
zb0001Mask |= 0x1
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -997,6 +1010,11 @@ func (z *DeleteOptions) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.OldDataDir = ""
}
o = bts o = bts
return return
} }
@@ -2453,6 +2471,9 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Data") err = msgp.WrapError(err, "Data")
return return
} }
if z.Data == nil {
z.Data = make([]byte, 0)
}
} }
z.NumVersions, err = dc.ReadInt() z.NumVersions, err = dc.ReadInt()
if err != nil { if err != nil {
@@ -2487,6 +2508,9 @@ func (z *FileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Checksum") err = msgp.WrapError(err, "Checksum")
return return
} }
if z.Checksum == nil {
z.Checksum = make([]byte, 0)
}
} }
z.Versioned, err = dc.ReadBool() z.Versioned, err = dc.ReadBool()
if err != nil { if err != nil {
@@ -2907,6 +2931,9 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Data") err = msgp.WrapError(err, "Data")
return return
} }
if z.Data == nil {
z.Data = make([]byte, 0)
}
} }
z.NumVersions, bts, err = msgp.ReadIntBytes(bts) z.NumVersions, bts, err = msgp.ReadIntBytes(bts)
if err != nil { if err != nil {
@@ -2937,6 +2964,9 @@ func (z *FileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Checksum") err = msgp.WrapError(err, "Checksum")
return return
} }
if z.Checksum == nil {
z.Checksum = make([]byte, 0)
}
} }
z.Versioned, bts, err = msgp.ReadBoolBytes(bts) z.Versioned, bts, err = msgp.ReadBoolBytes(bts)
if err != nil { if err != nil {
@@ -3961,6 +3991,9 @@ func (z *RawFileInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Buf") err = msgp.WrapError(err, "Buf")
return return
} }
if z.Buf == nil {
z.Buf = make([]byte, 0)
}
} }
default: default:
err = dc.Skip() err = dc.Skip()
@@ -4038,6 +4071,9 @@ func (z *RawFileInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Buf") err = msgp.WrapError(err, "Buf")
return return
} }
if z.Buf == nil {
z.Buf = make([]byte, 0)
}
} }
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
@@ -4220,6 +4256,8 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, err = dc.ReadMapKeyPtr() field, err = dc.ReadMapKeyPtr()
@@ -4240,6 +4278,7 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Prefix") err = msgp.WrapError(err, "Prefix")
return return
} }
zb0001Mask |= 0x1
case "fl": case "fl":
var zb0002 uint32 var zb0002 uint32
zb0002, err = dc.ReadArrayHeader() zb0002, err = dc.ReadArrayHeader()
@@ -4291,6 +4330,11 @@ func (z *ReadMultipleReq) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.Prefix = ""
}
return return
} }
@@ -4309,87 +4353,88 @@ func (z *ReadMultipleReq) EncodeMsg(en *msgp.Writer) (err error) {
if err != nil { if err != nil {
return return
} }
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// write "bk" // write "bk"
err = en.Append(0xa2, 0x62, 0x6b) err = en.Append(0xa2, 0x62, 0x6b)
if err != nil {
return
}
err = en.WriteString(z.Bucket)
if err != nil {
err = msgp.WrapError(err, "Bucket")
return
}
if (zb0001Mask & 0x2) == 0 { // if not omitted
// write "pr"
err = en.Append(0xa2, 0x70, 0x72)
if err != nil { if err != nil {
return return
} }
err = en.WriteString(z.Prefix) err = en.WriteString(z.Bucket)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Prefix") err = msgp.WrapError(err, "Bucket")
return return
} }
} if (zb0001Mask & 0x2) == 0 { // if not omitted
// write "fl" // write "pr"
err = en.Append(0xa2, 0x66, 0x6c) err = en.Append(0xa2, 0x70, 0x72)
if err != nil { if err != nil {
return return
} }
err = en.WriteArrayHeader(uint32(len(z.Files))) err = en.WriteString(z.Prefix)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Files") err = msgp.WrapError(err, "Prefix")
return return
} }
for za0001 := range z.Files { }
err = en.WriteString(z.Files[za0001]) // write "fl"
err = en.Append(0xa2, 0x66, 0x6c)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Files", za0001)
return return
} }
} err = en.WriteArrayHeader(uint32(len(z.Files)))
// write "ms" if err != nil {
err = en.Append(0xa2, 0x6d, 0x73) err = msgp.WrapError(err, "Files")
if err != nil { return
return }
} for za0001 := range z.Files {
err = en.WriteInt64(z.MaxSize) err = en.WriteString(z.Files[za0001])
if err != nil { if err != nil {
err = msgp.WrapError(err, "MaxSize") err = msgp.WrapError(err, "Files", za0001)
return return
} }
// write "mo" }
err = en.Append(0xa2, 0x6d, 0x6f) // write "ms"
if err != nil { err = en.Append(0xa2, 0x6d, 0x73)
return if err != nil {
} return
err = en.WriteBool(z.MetadataOnly) }
if err != nil { err = en.WriteInt64(z.MaxSize)
err = msgp.WrapError(err, "MetadataOnly") if err != nil {
return err = msgp.WrapError(err, "MaxSize")
} return
// write "ab" }
err = en.Append(0xa2, 0x61, 0x62) // write "mo"
if err != nil { err = en.Append(0xa2, 0x6d, 0x6f)
return if err != nil {
} return
err = en.WriteBool(z.AbortOn404) }
if err != nil { err = en.WriteBool(z.MetadataOnly)
err = msgp.WrapError(err, "AbortOn404") if err != nil {
return err = msgp.WrapError(err, "MetadataOnly")
} return
// write "mr" }
err = en.Append(0xa2, 0x6d, 0x72) // write "ab"
if err != nil { err = en.Append(0xa2, 0x61, 0x62)
return if err != nil {
} return
err = en.WriteInt(z.MaxResults) }
if err != nil { err = en.WriteBool(z.AbortOn404)
err = msgp.WrapError(err, "MaxResults") if err != nil {
return err = msgp.WrapError(err, "AbortOn404")
return
}
// write "mr"
err = en.Append(0xa2, 0x6d, 0x72)
if err != nil {
return
}
err = en.WriteInt(z.MaxResults)
if err != nil {
err = msgp.WrapError(err, "MaxResults")
return
}
} }
return return
} }
@@ -4407,35 +4452,36 @@ func (z *ReadMultipleReq) MarshalMsg(b []byte) (o []byte, err error) {
} }
// variable map header, size zb0001Len // variable map header, size zb0001Len
o = append(o, 0x80|uint8(zb0001Len)) o = append(o, 0x80|uint8(zb0001Len))
if zb0001Len == 0 {
return // skip if no fields are to be emitted
if zb0001Len != 0 {
// string "bk"
o = append(o, 0xa2, 0x62, 0x6b)
o = msgp.AppendString(o, z.Bucket)
if (zb0001Mask & 0x2) == 0 { // if not omitted
// string "pr"
o = append(o, 0xa2, 0x70, 0x72)
o = msgp.AppendString(o, z.Prefix)
}
// string "fl"
o = append(o, 0xa2, 0x66, 0x6c)
o = msgp.AppendArrayHeader(o, uint32(len(z.Files)))
for za0001 := range z.Files {
o = msgp.AppendString(o, z.Files[za0001])
}
// string "ms"
o = append(o, 0xa2, 0x6d, 0x73)
o = msgp.AppendInt64(o, z.MaxSize)
// string "mo"
o = append(o, 0xa2, 0x6d, 0x6f)
o = msgp.AppendBool(o, z.MetadataOnly)
// string "ab"
o = append(o, 0xa2, 0x61, 0x62)
o = msgp.AppendBool(o, z.AbortOn404)
// string "mr"
o = append(o, 0xa2, 0x6d, 0x72)
o = msgp.AppendInt(o, z.MaxResults)
} }
// string "bk"
o = append(o, 0xa2, 0x62, 0x6b)
o = msgp.AppendString(o, z.Bucket)
if (zb0001Mask & 0x2) == 0 { // if not omitted
// string "pr"
o = append(o, 0xa2, 0x70, 0x72)
o = msgp.AppendString(o, z.Prefix)
}
// string "fl"
o = append(o, 0xa2, 0x66, 0x6c)
o = msgp.AppendArrayHeader(o, uint32(len(z.Files)))
for za0001 := range z.Files {
o = msgp.AppendString(o, z.Files[za0001])
}
// string "ms"
o = append(o, 0xa2, 0x6d, 0x73)
o = msgp.AppendInt64(o, z.MaxSize)
// string "mo"
o = append(o, 0xa2, 0x6d, 0x6f)
o = msgp.AppendBool(o, z.MetadataOnly)
// string "ab"
o = append(o, 0xa2, 0x61, 0x62)
o = msgp.AppendBool(o, z.AbortOn404)
// string "mr"
o = append(o, 0xa2, 0x6d, 0x72)
o = msgp.AppendInt(o, z.MaxResults)
return return
} }
@@ -4449,6 +4495,8 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 1 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts) field, bts, err = msgp.ReadMapKeyZC(bts)
@@ -4469,6 +4517,7 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Prefix") err = msgp.WrapError(err, "Prefix")
return return
} }
zb0001Mask |= 0x1
case "fl": case "fl":
var zb0002 uint32 var zb0002 uint32
zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts) zb0002, bts, err = msgp.ReadArrayHeaderBytes(bts)
@@ -4520,6 +4569,11 @@ func (z *ReadMultipleReq) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
// Clear omitted fields.
if (zb0001Mask & 0x1) == 0 {
z.Prefix = ""
}
o = bts o = bts
return return
} }
@@ -4544,6 +4598,8 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 2 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, err = dc.ReadMapKeyPtr() field, err = dc.ReadMapKeyPtr()
@@ -4564,6 +4620,7 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Prefix") err = msgp.WrapError(err, "Prefix")
return return
} }
zb0001Mask |= 0x1
case "fl": case "fl":
z.File, err = dc.ReadString() z.File, err = dc.ReadString()
if err != nil { if err != nil {
@@ -4582,6 +4639,7 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Error") err = msgp.WrapError(err, "Error")
return return
} }
zb0001Mask |= 0x2
case "d": case "d":
z.Data, err = dc.ReadBytes(z.Data) z.Data, err = dc.ReadBytes(z.Data)
if err != nil { if err != nil {
@@ -4602,6 +4660,15 @@ func (z *ReadMultipleResp) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
// Clear omitted fields.
if zb0001Mask != 0x3 {
if (zb0001Mask & 0x1) == 0 {
z.Prefix = ""
}
if (zb0001Mask & 0x2) == 0 {
z.Error = ""
}
}
return return
} }
@@ -4624,82 +4691,83 @@ func (z *ReadMultipleResp) EncodeMsg(en *msgp.Writer) (err error) {
if err != nil { if err != nil {
return return
} }
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// write "bk" // write "bk"
err = en.Append(0xa2, 0x62, 0x6b) err = en.Append(0xa2, 0x62, 0x6b)
if err != nil {
return
}
err = en.WriteString(z.Bucket)
if err != nil {
err = msgp.WrapError(err, "Bucket")
return
}
if (zb0001Mask & 0x2) == 0 { // if not omitted
// write "pr"
err = en.Append(0xa2, 0x70, 0x72)
if err != nil { if err != nil {
return return
} }
err = en.WriteString(z.Prefix) err = en.WriteString(z.Bucket)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Prefix") err = msgp.WrapError(err, "Bucket")
return return
} }
} if (zb0001Mask & 0x2) == 0 { // if not omitted
// write "fl" // write "pr"
err = en.Append(0xa2, 0x66, 0x6c) err = en.Append(0xa2, 0x70, 0x72)
if err != nil { if err != nil {
return return
} }
err = en.WriteString(z.File) err = en.WriteString(z.Prefix)
if err != nil { if err != nil {
err = msgp.WrapError(err, "File") err = msgp.WrapError(err, "Prefix")
return return
} }
// write "ex" }
err = en.Append(0xa2, 0x65, 0x78) // write "fl"
if err != nil { err = en.Append(0xa2, 0x66, 0x6c)
return
}
err = en.WriteBool(z.Exists)
if err != nil {
err = msgp.WrapError(err, "Exists")
return
}
if (zb0001Mask & 0x10) == 0 { // if not omitted
// write "er"
err = en.Append(0xa2, 0x65, 0x72)
if err != nil { if err != nil {
return return
} }
err = en.WriteString(z.Error) err = en.WriteString(z.File)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Error") err = msgp.WrapError(err, "File")
return
}
// write "ex"
err = en.Append(0xa2, 0x65, 0x78)
if err != nil {
return
}
err = en.WriteBool(z.Exists)
if err != nil {
err = msgp.WrapError(err, "Exists")
return
}
if (zb0001Mask & 0x10) == 0 { // if not omitted
// write "er"
err = en.Append(0xa2, 0x65, 0x72)
if err != nil {
return
}
err = en.WriteString(z.Error)
if err != nil {
err = msgp.WrapError(err, "Error")
return
}
}
// write "d"
err = en.Append(0xa1, 0x64)
if err != nil {
return
}
err = en.WriteBytes(z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
// write "m"
err = en.Append(0xa1, 0x6d)
if err != nil {
return
}
err = en.WriteTime(z.Modtime)
if err != nil {
err = msgp.WrapError(err, "Modtime")
return return
} }
}
// write "d"
err = en.Append(0xa1, 0x64)
if err != nil {
return
}
err = en.WriteBytes(z.Data)
if err != nil {
err = msgp.WrapError(err, "Data")
return
}
// write "m"
err = en.Append(0xa1, 0x6d)
if err != nil {
return
}
err = en.WriteTime(z.Modtime)
if err != nil {
err = msgp.WrapError(err, "Modtime")
return
} }
return return
} }
@@ -4721,34 +4789,35 @@ func (z *ReadMultipleResp) MarshalMsg(b []byte) (o []byte, err error) {
} }
// variable map header, size zb0001Len // variable map header, size zb0001Len
o = append(o, 0x80|uint8(zb0001Len)) o = append(o, 0x80|uint8(zb0001Len))
if zb0001Len == 0 {
return // skip if no fields are to be emitted
if zb0001Len != 0 {
// string "bk"
o = append(o, 0xa2, 0x62, 0x6b)
o = msgp.AppendString(o, z.Bucket)
if (zb0001Mask & 0x2) == 0 { // if not omitted
// string "pr"
o = append(o, 0xa2, 0x70, 0x72)
o = msgp.AppendString(o, z.Prefix)
}
// string "fl"
o = append(o, 0xa2, 0x66, 0x6c)
o = msgp.AppendString(o, z.File)
// string "ex"
o = append(o, 0xa2, 0x65, 0x78)
o = msgp.AppendBool(o, z.Exists)
if (zb0001Mask & 0x10) == 0 { // if not omitted
// string "er"
o = append(o, 0xa2, 0x65, 0x72)
o = msgp.AppendString(o, z.Error)
}
// string "d"
o = append(o, 0xa1, 0x64)
o = msgp.AppendBytes(o, z.Data)
// string "m"
o = append(o, 0xa1, 0x6d)
o = msgp.AppendTime(o, z.Modtime)
} }
// string "bk"
o = append(o, 0xa2, 0x62, 0x6b)
o = msgp.AppendString(o, z.Bucket)
if (zb0001Mask & 0x2) == 0 { // if not omitted
// string "pr"
o = append(o, 0xa2, 0x70, 0x72)
o = msgp.AppendString(o, z.Prefix)
}
// string "fl"
o = append(o, 0xa2, 0x66, 0x6c)
o = msgp.AppendString(o, z.File)
// string "ex"
o = append(o, 0xa2, 0x65, 0x78)
o = msgp.AppendBool(o, z.Exists)
if (zb0001Mask & 0x10) == 0 { // if not omitted
// string "er"
o = append(o, 0xa2, 0x65, 0x72)
o = msgp.AppendString(o, z.Error)
}
// string "d"
o = append(o, 0xa1, 0x64)
o = msgp.AppendBytes(o, z.Data)
// string "m"
o = append(o, 0xa1, 0x6d)
o = msgp.AppendTime(o, z.Modtime)
return return
} }
@@ -4762,6 +4831,8 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 2 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts) field, bts, err = msgp.ReadMapKeyZC(bts)
@@ -4782,6 +4853,7 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Prefix") err = msgp.WrapError(err, "Prefix")
return return
} }
zb0001Mask |= 0x1
case "fl": case "fl":
z.File, bts, err = msgp.ReadStringBytes(bts) z.File, bts, err = msgp.ReadStringBytes(bts)
if err != nil { if err != nil {
@@ -4800,6 +4872,7 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Error") err = msgp.WrapError(err, "Error")
return return
} }
zb0001Mask |= 0x2
case "d": case "d":
z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data) z.Data, bts, err = msgp.ReadBytesBytes(bts, z.Data)
if err != nil { if err != nil {
@@ -4820,6 +4893,15 @@ func (z *ReadMultipleResp) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
// Clear omitted fields.
if zb0001Mask != 0x3 {
if (zb0001Mask & 0x1) == 0 {
z.Prefix = ""
}
if (zb0001Mask & 0x2) == 0 {
z.Error = ""
}
}
o = bts o = bts
return return
} }
+7 -3
View File
@@ -464,16 +464,20 @@ func (client *storageRESTClient) WriteAll(ctx context.Context, volume string, pa
// CheckParts - stat all file parts. // CheckParts - stat all file parts.
func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) { func (client *storageRESTClient) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
var resp *CheckPartsResp var resp *CheckPartsResp
resp, err := storageCheckPartsRPC.Call(ctx, client.gridConn, &CheckPartsHandlerParams{ st, err := storageCheckPartsRPC.Call(ctx, client.gridConn, &CheckPartsHandlerParams{
DiskID: *client.diskID.Load(), DiskID: *client.diskID.Load(),
Volume: volume, Volume: volume,
FilePath: path, FilePath: path,
FI: fi, FI: fi,
}) })
if err != nil { if err != nil {
return nil, err return nil, toStorageErr(err)
} }
return resp, nil err = st.Results(func(r *CheckPartsResp) error {
resp = r
return nil
})
return resp, toStorageErr(err)
} }
// RenameData - rename source path to destination path atomically, metadata and data file. // RenameData - rename source path to destination path atomically, metadata and data file.
+11 -7
View File
@@ -56,7 +56,7 @@ type storageRESTServer struct {
} }
var ( var (
storageCheckPartsRPC = grid.NewSingleHandler[*CheckPartsHandlerParams, *CheckPartsResp](grid.HandlerCheckParts2, func() *CheckPartsHandlerParams { return &CheckPartsHandlerParams{} }, func() *CheckPartsResp { return &CheckPartsResp{} }) storageCheckPartsRPC = grid.NewStream[*CheckPartsHandlerParams, grid.NoPayload, *CheckPartsResp](grid.HandlerCheckParts3, func() *CheckPartsHandlerParams { return &CheckPartsHandlerParams{} }, nil, func() *CheckPartsResp { return &CheckPartsResp{} })
storageDeleteFileRPC = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams { return &DeleteFileHandlerParams{} }, grid.NewNoPayload).AllowCallRequestPool(true) storageDeleteFileRPC = grid.NewSingleHandler[*DeleteFileHandlerParams, grid.NoPayload](grid.HandlerDeleteFile, func() *DeleteFileHandlerParams { return &DeleteFileHandlerParams{} }, grid.NewNoPayload).AllowCallRequestPool(true)
storageDeleteVersionRPC = grid.NewSingleHandler[*DeleteVersionHandlerParams, grid.NoPayload](grid.HandlerDeleteVersion, func() *DeleteVersionHandlerParams { return &DeleteVersionHandlerParams{} }, grid.NewNoPayload) storageDeleteVersionRPC = grid.NewSingleHandler[*DeleteVersionHandlerParams, grid.NoPayload](grid.HandlerDeleteVersion, func() *DeleteVersionHandlerParams { return &DeleteVersionHandlerParams{} }, grid.NewNoPayload)
storageDiskInfoRPC = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} }, func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true) storageDiskInfoRPC = grid.NewSingleHandler[*DiskInfoOptions, *DiskInfo](grid.HandlerDiskInfo, func() *DiskInfoOptions { return &DiskInfoOptions{} }, func() *DiskInfo { return &DiskInfo{} }).WithSharedResponse().AllowCallRequestPool(true)
@@ -456,16 +456,20 @@ func (s *storageRESTServer) UpdateMetadataHandler(p *MetadataHandlerParams) (gri
return grid.NewNPErr(s.getStorage().UpdateMetadata(context.Background(), volume, filePath, p.FI, p.UpdateOpts)) return grid.NewNPErr(s.getStorage().UpdateMetadata(context.Background(), volume, filePath, p.FI, p.UpdateOpts))
} }
// CheckPartsHandler - check if a file metadata exists. // CheckPartsHandler - check if a file parts exists.
func (s *storageRESTServer) CheckPartsHandler(p *CheckPartsHandlerParams) (*CheckPartsResp, *grid.RemoteErr) { func (s *storageRESTServer) CheckPartsHandler(ctx context.Context, p *CheckPartsHandlerParams, out chan<- *CheckPartsResp) *grid.RemoteErr {
if !s.checkID(p.DiskID) { if !s.checkID(p.DiskID) {
return nil, grid.NewRemoteErr(errDiskNotFound) return grid.NewRemoteErr(errDiskNotFound)
} }
volume := p.Volume volume := p.Volume
filePath := p.FilePath filePath := p.FilePath
resp, err := s.getStorage().CheckParts(context.Background(), volume, filePath, p.FI) resp, err := s.getStorage().CheckParts(ctx, volume, filePath, p.FI)
return resp, grid.NewRemoteErr(err) if err != nil {
return grid.NewRemoteErr(err)
}
out <- resp
return grid.NewRemoteErr(err)
} }
func (s *storageRESTServer) WriteAllHandler(p *WriteAllHandlerParams) (grid.NoPayload, *grid.RemoteErr) { func (s *storageRESTServer) WriteAllHandler(p *WriteAllHandlerParams) (grid.NoPayload, *grid.RemoteErr) {
@@ -1382,7 +1386,7 @@ func registerStorageRESTHandlers(router *mux.Router, endpointServerPools Endpoin
logger.FatalIf(storageRenameDataRPC.Register(gm, server.RenameDataHandler, endpoint.Path), "unable to register handler") logger.FatalIf(storageRenameDataRPC.Register(gm, server.RenameDataHandler, endpoint.Path), "unable to register handler")
logger.FatalIf(storageRenameDataInlineRPC.Register(gm, server.RenameDataInlineHandler, endpoint.Path), "unable to register handler") logger.FatalIf(storageRenameDataInlineRPC.Register(gm, server.RenameDataInlineHandler, endpoint.Path), "unable to register handler")
logger.FatalIf(storageDeleteFileRPC.Register(gm, server.DeleteFileHandler, endpoint.Path), "unable to register handler") logger.FatalIf(storageDeleteFileRPC.Register(gm, server.DeleteFileHandler, endpoint.Path), "unable to register handler")
logger.FatalIf(storageCheckPartsRPC.Register(gm, server.CheckPartsHandler, endpoint.Path), "unable to register handler") logger.FatalIf(storageCheckPartsRPC.RegisterNoInput(gm, server.CheckPartsHandler, endpoint.Path), "unable to register handler")
logger.FatalIf(storageReadVersionRPC.Register(gm, server.ReadVersionHandlerWS, endpoint.Path), "unable to register handler") logger.FatalIf(storageReadVersionRPC.Register(gm, server.ReadVersionHandlerWS, endpoint.Path), "unable to register handler")
logger.FatalIf(storageWriteMetadataRPC.Register(gm, server.WriteMetadataHandler, endpoint.Path), "unable to register handler") logger.FatalIf(storageWriteMetadataRPC.Register(gm, server.WriteMetadataHandler, endpoint.Path), "unable to register handler")
logger.FatalIf(storageUpdateMetadataRPC.Register(gm, server.UpdateMetadataHandler, endpoint.Path), "unable to register handler") logger.FatalIf(storageUpdateMetadataRPC.Register(gm, server.UpdateMetadataHandler, endpoint.Path), "unable to register handler")
+1 -1
View File
@@ -24,6 +24,7 @@ import (
"io" "io"
"os" "os"
"reflect" "reflect"
"slices"
"strings" "strings"
"testing" "testing"
"time" "time"
@@ -34,7 +35,6 @@ import (
cr "github.com/minio/minio-go/v7/pkg/credentials" cr "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/set" "github.com/minio/minio-go/v7/pkg/set"
"github.com/minio/pkg/v3/ldap" "github.com/minio/pkg/v3/ldap"
"golang.org/x/exp/slices"
) )
func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) { func runAllIAMSTSTests(suite *TestSuiteIAM, c *check) {
+4 -4
View File
@@ -2247,12 +2247,12 @@ func getEndpointsLocalAddr(endpointServerPools EndpointServerPools) string {
} }
// fetches a random number between range min-max. // fetches a random number between range min-max.
func getRandomRange(min, max int, seed int64) int { func getRandomRange(minN, maxN int, seed int64) int {
// special value -1 means no explicit seeding. // special value -1 means no explicit seeding.
if seed != -1 { if seed == -1 {
rand.Seed(seed) return rand.New(rand.NewSource(time.Now().UnixNano())).Intn(maxN-minN) + minN
} }
return rand.Intn(max-min) + min return rand.New(rand.NewSource(seed)).Intn(maxN-minN) + minN
} }
// Randomizes the order of bytes in the byte array // Randomizes the order of bytes in the byte array
+5
View File
@@ -411,7 +411,12 @@ func startProfiler(profilerType string) (minioProfiler, error) {
return nil, err return nil, err
} }
stop := fgprof.Start(f, fgprof.FormatPprof) stop := fgprof.Start(f, fgprof.FormatPprof)
startedAt := time.Now()
prof.stopFn = func() ([]byte, error) { prof.stopFn = func() ([]byte, error) {
if elapsed := time.Since(startedAt); elapsed < 100*time.Millisecond {
// Light hack around https://github.com/felixge/fgprof/pull/34
time.Sleep(100*time.Millisecond - elapsed)
}
err := stop() err := stop()
if err != nil { if err != nil {
return nil, err return nil, err
+11 -1
View File
@@ -26,6 +26,7 @@ import (
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
@@ -59,10 +60,15 @@ func (az *warmBackendAzure) getDest(object string) string {
return destObj return destObj
} }
func (az *warmBackendAzure) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) { func (az *warmBackendAzure) PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
azMeta := map[string]*string{}
for k, v := range meta {
azMeta[k] = to.Ptr(v)
}
resp, err := az.clnt.UploadStream(ctx, az.Bucket, az.getDest(object), io.LimitReader(r, length), &azblob.UploadStreamOptions{ resp, err := az.clnt.UploadStream(ctx, az.Bucket, az.getDest(object), io.LimitReader(r, length), &azblob.UploadStreamOptions{
Concurrency: 4, Concurrency: 4,
AccessTier: az.tier(), // set tier if specified AccessTier: az.tier(), // set tier if specified
Metadata: azMeta,
}) })
if err != nil { if err != nil {
return "", azureToObjectError(err, az.Bucket, az.getDest(object)) return "", azureToObjectError(err, az.Bucket, az.getDest(object))
@@ -74,6 +80,10 @@ func (az *warmBackendAzure) Put(ctx context.Context, object string, r io.Reader,
return remoteVersionID(vid), nil return remoteVersionID(vid), nil
} }
func (az *warmBackendAzure) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
return az.PutWithMeta(ctx, object, r, length, map[string]string{})
}
func (az *warmBackendAzure) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) { func (az *warmBackendAzure) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) {
if opts.startOffset < 0 { if opts.startOffset < 0 {
return nil, InvalidRange{} return nil, InvalidRange{}
+12 -5
View File
@@ -47,16 +47,17 @@ func (gcs *warmBackendGCS) getDest(object string) string {
return destObj return destObj
} }
// FIXME: add support for remote version ID in GCS remote tier and remove this. func (gcs *warmBackendGCS) PutWithMeta(ctx context.Context, key string, data io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
// Currently it's a no-op.
func (gcs *warmBackendGCS) Put(ctx context.Context, key string, data io.Reader, length int64) (remoteVersionID, error) {
object := gcs.client.Bucket(gcs.Bucket).Object(gcs.getDest(key)) object := gcs.client.Bucket(gcs.Bucket).Object(gcs.getDest(key))
// TODO: set storage class
w := object.NewWriter(ctx) w := object.NewWriter(ctx)
if gcs.StorageClass != "" { if gcs.StorageClass != "" {
w.ObjectAttrs.StorageClass = gcs.StorageClass w.ObjectAttrs.StorageClass = gcs.StorageClass
} }
w.ObjectAttrs.Metadata = meta
if _, err := xioutil.Copy(w, data); err != nil {
return "", gcsToObjectError(err, gcs.Bucket, key)
}
if _, err := xioutil.Copy(w, data); err != nil { if _, err := xioutil.Copy(w, data); err != nil {
return "", gcsToObjectError(err, gcs.Bucket, key) return "", gcsToObjectError(err, gcs.Bucket, key)
} }
@@ -64,6 +65,12 @@ func (gcs *warmBackendGCS) Put(ctx context.Context, key string, data io.Reader,
return "", w.Close() return "", w.Close()
} }
// FIXME: add support for remote version ID in GCS remote tier and remove this.
// Currently it's a no-op.
func (gcs *warmBackendGCS) Put(ctx context.Context, key string, data io.Reader, length int64) (remoteVersionID, error) {
return gcs.PutWithMeta(ctx, key, data, length, map[string]string{})
}
func (gcs *warmBackendGCS) Get(ctx context.Context, key string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) { func (gcs *warmBackendGCS) Get(ctx context.Context, key string, rv remoteVersionID, opts WarmBackendGetOpts) (r io.ReadCloser, err error) {
// GCS storage decompresses a gzipped object by default and returns the data. // GCS storage decompresses a gzipped object by default and returns the data.
// Refer to https://cloud.google.com/storage/docs/transcoding#decompressive_transcoding // Refer to https://cloud.google.com/storage/docs/transcoding#decompressive_transcoding
+6 -1
View File
@@ -78,7 +78,7 @@ func optimalPartSize(objectSize int64) (partSize int64, err error) {
return partSize, nil return partSize, nil
} }
func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) { func (m *warmBackendMinIO) PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
partSize, err := optimalPartSize(length) partSize, err := optimalPartSize(length)
if err != nil { if err != nil {
return remoteVersionID(""), err return remoteVersionID(""), err
@@ -87,10 +87,15 @@ func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader,
StorageClass: m.StorageClass, StorageClass: m.StorageClass,
PartSize: uint64(partSize), PartSize: uint64(partSize),
DisableContentSha256: true, DisableContentSha256: true,
UserMetadata: meta,
}) })
return remoteVersionID(res.VersionID), m.ToObjectError(err, object) return remoteVersionID(res.VersionID), m.ToObjectError(err, object)
} }
func (m *warmBackendMinIO) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
return m.PutWithMeta(ctx, object, r, length, map[string]string{})
}
func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO, error) { func newWarmBackendMinIO(conf madmin.TierMinIO, tier string) (*warmBackendMinIO, error) {
// Validation of credentials // Validation of credentials
if conf.AccessKey == "" || conf.SecretKey == "" { if conf.AccessKey == "" || conf.SecretKey == "" {
+6 -1
View File
@@ -56,14 +56,19 @@ func (s3 *warmBackendS3) getDest(object string) string {
return destObj return destObj
} }
func (s3 *warmBackendS3) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) { func (s3 *warmBackendS3) PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error) {
res, err := s3.client.PutObject(ctx, s3.Bucket, s3.getDest(object), r, length, minio.PutObjectOptions{ res, err := s3.client.PutObject(ctx, s3.Bucket, s3.getDest(object), r, length, minio.PutObjectOptions{
SendContentMd5: true, SendContentMd5: true,
StorageClass: s3.StorageClass, StorageClass: s3.StorageClass,
UserMetadata: meta,
}) })
return remoteVersionID(res.VersionID), s3.ToObjectError(err, object) return remoteVersionID(res.VersionID), s3.ToObjectError(err, object)
} }
func (s3 *warmBackendS3) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) {
return s3.PutWithMeta(ctx, object, r, length, map[string]string{})
}
func (s3 *warmBackendS3) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error) { func (s3 *warmBackendS3) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error) {
gopts := minio.GetObjectOptions{} gopts := minio.GetObjectOptions{}
+1
View File
@@ -38,6 +38,7 @@ type WarmBackendGetOpts struct {
// WarmBackend provides interface to be implemented by remote tier backends // WarmBackend provides interface to be implemented by remote tier backends
type WarmBackend interface { type WarmBackend interface {
Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error) Put(ctx context.Context, object string, r io.Reader, length int64) (remoteVersionID, error)
PutWithMeta(ctx context.Context, object string, r io.Reader, length int64, meta map[string]string) (remoteVersionID, error)
Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error) Get(ctx context.Context, object string, rv remoteVersionID, opts WarmBackendGetOpts) (io.ReadCloser, error)
Remove(ctx context.Context, object string, rv remoteVersionID) error Remove(ctx context.Context, object string, rv remoteVersionID) error
InUse(ctx context.Context) (bool, error) InUse(ctx context.Context) (bool, error)
+1 -3
View File
@@ -509,9 +509,7 @@ func (p *xlStorageDiskIDCheck) CheckParts(ctx context.Context, volume string, pa
} }
defer done(0, &err) defer done(0, &err)
return xioutil.WithDeadline[*CheckPartsResp](ctx, globalDriveConfig.GetMaxTimeout(), func(ctx context.Context) (res *CheckPartsResp, err error) { return p.storage.CheckParts(ctx, volume, path, fi)
return p.storage.CheckParts(ctx, volume, path, fi)
})
} }
func (p *xlStorageDiskIDCheck) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) { func (p *xlStorageDiskIDCheck) DeleteBulk(ctx context.Context, volume string, paths ...string) (err error) {
+2
View File
@@ -55,6 +55,8 @@ func isXLMetaErasureInfoValid(data, parity int) bool {
return ((data >= parity) && (data > 0) && (parity >= 0)) return ((data >= parity) && (data > 0) && (parity >= 0))
} }
//msgp:clearomitted
//go:generate msgp -file=$GOFILE -unexported //go:generate msgp -file=$GOFILE -unexported
// A xlMetaV1Object represents `xl.meta` metadata header. // A xlMetaV1Object represents `xl.meta` metadata header.
+152 -116
View File
@@ -561,6 +561,8 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 3 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, err = dc.ReadMapKeyPtr() field, err = dc.ReadMapKeyPtr()
@@ -605,6 +607,7 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
err = msgp.WrapError(err, "Index") err = msgp.WrapError(err, "Index")
return return
} }
zb0001Mask |= 0x1
case "crc": case "crc":
var zb0002 uint32 var zb0002 uint32
zb0002, err = dc.ReadMapHeader() zb0002, err = dc.ReadMapHeader()
@@ -635,12 +638,14 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
} }
z.Checksums[za0001] = za0002 z.Checksums[za0001] = za0002
} }
zb0001Mask |= 0x2
case "err": case "err":
z.Error, err = dc.ReadString() z.Error, err = dc.ReadString()
if err != nil { if err != nil {
err = msgp.WrapError(err, "Error") err = msgp.WrapError(err, "Error")
return return
} }
zb0001Mask |= 0x4
default: default:
err = dc.Skip() err = dc.Skip()
if err != nil { if err != nil {
@@ -649,6 +654,18 @@ func (z *ObjectPartInfo) DecodeMsg(dc *msgp.Reader) (err error) {
} }
} }
} }
// Clear omitted fields.
if zb0001Mask != 0x7 {
if (zb0001Mask & 0x1) == 0 {
z.Index = nil
}
if (zb0001Mask & 0x2) == 0 {
z.Checksums = nil
}
if (zb0001Mask & 0x4) == 0 {
z.Error = ""
}
}
return return
} }
@@ -675,105 +692,106 @@ func (z *ObjectPartInfo) EncodeMsg(en *msgp.Writer) (err error) {
if err != nil { if err != nil {
return return
} }
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// write "e" // write "e"
err = en.Append(0xa1, 0x65) err = en.Append(0xa1, 0x65)
if err != nil {
return
}
err = en.WriteString(z.ETag)
if err != nil {
err = msgp.WrapError(err, "ETag")
return
}
// write "n"
err = en.Append(0xa1, 0x6e)
if err != nil {
return
}
err = en.WriteInt(z.Number)
if err != nil {
err = msgp.WrapError(err, "Number")
return
}
// write "s"
err = en.Append(0xa1, 0x73)
if err != nil {
return
}
err = en.WriteInt64(z.Size)
if err != nil {
err = msgp.WrapError(err, "Size")
return
}
// write "as"
err = en.Append(0xa2, 0x61, 0x73)
if err != nil {
return
}
err = en.WriteInt64(z.ActualSize)
if err != nil {
err = msgp.WrapError(err, "ActualSize")
return
}
// write "mt"
err = en.Append(0xa2, 0x6d, 0x74)
if err != nil {
return
}
err = en.WriteTime(z.ModTime)
if err != nil {
err = msgp.WrapError(err, "ModTime")
return
}
if (zb0001Mask & 0x20) == 0 { // if not omitted
// write "i"
err = en.Append(0xa1, 0x69)
if err != nil { if err != nil {
return return
} }
err = en.WriteBytes(z.Index) err = en.WriteString(z.ETag)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Index") err = msgp.WrapError(err, "ETag")
return return
} }
} // write "n"
if (zb0001Mask & 0x40) == 0 { // if not omitted err = en.Append(0xa1, 0x6e)
// write "crc"
err = en.Append(0xa3, 0x63, 0x72, 0x63)
if err != nil { if err != nil {
return return
} }
err = en.WriteMapHeader(uint32(len(z.Checksums))) err = en.WriteInt(z.Number)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Checksums") err = msgp.WrapError(err, "Number")
return return
} }
for za0001, za0002 := range z.Checksums { // write "s"
err = en.WriteString(za0001) err = en.Append(0xa1, 0x73)
if err != nil {
return
}
err = en.WriteInt64(z.Size)
if err != nil {
err = msgp.WrapError(err, "Size")
return
}
// write "as"
err = en.Append(0xa2, 0x61, 0x73)
if err != nil {
return
}
err = en.WriteInt64(z.ActualSize)
if err != nil {
err = msgp.WrapError(err, "ActualSize")
return
}
// write "mt"
err = en.Append(0xa2, 0x6d, 0x74)
if err != nil {
return
}
err = en.WriteTime(z.ModTime)
if err != nil {
err = msgp.WrapError(err, "ModTime")
return
}
if (zb0001Mask & 0x20) == 0 { // if not omitted
// write "i"
err = en.Append(0xa1, 0x69)
if err != nil {
return
}
err = en.WriteBytes(z.Index)
if err != nil {
err = msgp.WrapError(err, "Index")
return
}
}
if (zb0001Mask & 0x40) == 0 { // if not omitted
// write "crc"
err = en.Append(0xa3, 0x63, 0x72, 0x63)
if err != nil {
return
}
err = en.WriteMapHeader(uint32(len(z.Checksums)))
if err != nil { if err != nil {
err = msgp.WrapError(err, "Checksums") err = msgp.WrapError(err, "Checksums")
return return
} }
err = en.WriteString(za0002) for za0001, za0002 := range z.Checksums {
if err != nil { err = en.WriteString(za0001)
err = msgp.WrapError(err, "Checksums", za0001) if err != nil {
return err = msgp.WrapError(err, "Checksums")
return
}
err = en.WriteString(za0002)
if err != nil {
err = msgp.WrapError(err, "Checksums", za0001)
return
}
} }
} }
} if (zb0001Mask & 0x80) == 0 { // if not omitted
if (zb0001Mask & 0x80) == 0 { // if not omitted // write "err"
// write "err" err = en.Append(0xa3, 0x65, 0x72, 0x72)
err = en.Append(0xa3, 0x65, 0x72, 0x72) if err != nil {
if err != nil { return
return }
} err = en.WriteString(z.Error)
err = en.WriteString(z.Error) if err != nil {
if err != nil { err = msgp.WrapError(err, "Error")
err = msgp.WrapError(err, "Error") return
return }
} }
} }
return return
@@ -800,42 +818,43 @@ func (z *ObjectPartInfo) MarshalMsg(b []byte) (o []byte, err error) {
} }
// variable map header, size zb0001Len // variable map header, size zb0001Len
o = append(o, 0x80|uint8(zb0001Len)) o = append(o, 0x80|uint8(zb0001Len))
if zb0001Len == 0 {
return // skip if no fields are to be emitted
} if zb0001Len != 0 {
// string "e" // string "e"
o = append(o, 0xa1, 0x65) o = append(o, 0xa1, 0x65)
o = msgp.AppendString(o, z.ETag) o = msgp.AppendString(o, z.ETag)
// string "n" // string "n"
o = append(o, 0xa1, 0x6e) o = append(o, 0xa1, 0x6e)
o = msgp.AppendInt(o, z.Number) o = msgp.AppendInt(o, z.Number)
// string "s" // string "s"
o = append(o, 0xa1, 0x73) o = append(o, 0xa1, 0x73)
o = msgp.AppendInt64(o, z.Size) o = msgp.AppendInt64(o, z.Size)
// string "as" // string "as"
o = append(o, 0xa2, 0x61, 0x73) o = append(o, 0xa2, 0x61, 0x73)
o = msgp.AppendInt64(o, z.ActualSize) o = msgp.AppendInt64(o, z.ActualSize)
// string "mt" // string "mt"
o = append(o, 0xa2, 0x6d, 0x74) o = append(o, 0xa2, 0x6d, 0x74)
o = msgp.AppendTime(o, z.ModTime) o = msgp.AppendTime(o, z.ModTime)
if (zb0001Mask & 0x20) == 0 { // if not omitted if (zb0001Mask & 0x20) == 0 { // if not omitted
// string "i" // string "i"
o = append(o, 0xa1, 0x69) o = append(o, 0xa1, 0x69)
o = msgp.AppendBytes(o, z.Index) o = msgp.AppendBytes(o, z.Index)
} }
if (zb0001Mask & 0x40) == 0 { // if not omitted if (zb0001Mask & 0x40) == 0 { // if not omitted
// string "crc" // string "crc"
o = append(o, 0xa3, 0x63, 0x72, 0x63) o = append(o, 0xa3, 0x63, 0x72, 0x63)
o = msgp.AppendMapHeader(o, uint32(len(z.Checksums))) o = msgp.AppendMapHeader(o, uint32(len(z.Checksums)))
for za0001, za0002 := range z.Checksums { for za0001, za0002 := range z.Checksums {
o = msgp.AppendString(o, za0001) o = msgp.AppendString(o, za0001)
o = msgp.AppendString(o, za0002) o = msgp.AppendString(o, za0002)
}
}
if (zb0001Mask & 0x80) == 0 { // if not omitted
// string "err"
o = append(o, 0xa3, 0x65, 0x72, 0x72)
o = msgp.AppendString(o, z.Error)
} }
}
if (zb0001Mask & 0x80) == 0 { // if not omitted
// string "err"
o = append(o, 0xa3, 0x65, 0x72, 0x72)
o = msgp.AppendString(o, z.Error)
} }
return return
} }
@@ -850,6 +869,8 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err) err = msgp.WrapError(err)
return return
} }
var zb0001Mask uint8 /* 3 bits */
_ = zb0001Mask
for zb0001 > 0 { for zb0001 > 0 {
zb0001-- zb0001--
field, bts, err = msgp.ReadMapKeyZC(bts) field, bts, err = msgp.ReadMapKeyZC(bts)
@@ -894,6 +915,7 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
err = msgp.WrapError(err, "Index") err = msgp.WrapError(err, "Index")
return return
} }
zb0001Mask |= 0x1
case "crc": case "crc":
var zb0002 uint32 var zb0002 uint32
zb0002, bts, err = msgp.ReadMapHeaderBytes(bts) zb0002, bts, err = msgp.ReadMapHeaderBytes(bts)
@@ -924,12 +946,14 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
z.Checksums[za0001] = za0002 z.Checksums[za0001] = za0002
} }
zb0001Mask |= 0x2
case "err": case "err":
z.Error, bts, err = msgp.ReadStringBytes(bts) z.Error, bts, err = msgp.ReadStringBytes(bts)
if err != nil { if err != nil {
err = msgp.WrapError(err, "Error") err = msgp.WrapError(err, "Error")
return return
} }
zb0001Mask |= 0x4
default: default:
bts, err = msgp.Skip(bts) bts, err = msgp.Skip(bts)
if err != nil { if err != nil {
@@ -938,6 +962,18 @@ func (z *ObjectPartInfo) UnmarshalMsg(bts []byte) (o []byte, err error) {
} }
} }
} }
// Clear omitted fields.
if zb0001Mask != 0x7 {
if (zb0001Mask & 0x1) == 0 {
z.Index = nil
}
if (zb0001Mask & 0x2) == 0 {
z.Checksums = nil
}
if (zb0001Mask & 0x4) == 0 {
z.Error = ""
}
}
o = bts o = bts
return return
} }
+48 -2
View File
@@ -47,6 +47,8 @@ var (
xlVersionCurrent [4]byte xlVersionCurrent [4]byte
) )
//msgp:clearomitted
//go:generate msgp -file=$GOFILE -unexported //go:generate msgp -file=$GOFILE -unexported
//go:generate stringer -type VersionType,ErasureAlgo -output=xl-storage-format-v2_string.go $GOFILE //go:generate stringer -type VersionType,ErasureAlgo -output=xl-storage-format-v2_string.go $GOFILE
@@ -460,7 +462,7 @@ func (j *xlMetaV2Version) ToFileInfo(volume, path string, allParts bool) (fi Fil
const ( const (
xlHeaderVersion = 3 xlHeaderVersion = 3
xlMetaVersion = 2 xlMetaVersion = 3
) )
func (j xlMetaV2DeleteMarker) ToFileInfo(volume, path string) (FileInfo, error) { func (j xlMetaV2DeleteMarker) ToFileInfo(volume, path string) (FileInfo, error) {
@@ -967,6 +969,50 @@ func (x *xlMetaV2) loadIndexed(buf xlMetaBuf, data xlMetaInlineData) error {
} }
ver.meta = meta ver.meta = meta
// Fix inconsistent compression index due to https://github.com/minio/minio/pull/20575
// First search marshaled content for encoded values.
// We have bumped metaV to make this check cheaper.
if metaV < 3 && ver.header.Type == ObjectType && bytes.Contains(meta, []byte("\xa7PartIdx")) &&
bytes.Contains(meta, []byte("\xbcX-Minio-Internal-compression\xc4\x15klauspost/compress/s2")) {
// Likely candidate...
version, err := x.getIdx(i)
if err == nil {
// Check write date...
// RELEASE.2023-12-02T10-51-33Z -> RELEASE.2024-10-29T16-01-48Z
const dateStart = 1701471618
const dateEnd = 1730156418
if version.WrittenByVersion > dateStart && version.WrittenByVersion < dateEnd &&
version.ObjectV2 != nil && len(version.ObjectV2.PartIndices) > 0 {
var changed bool
clearField := true
for i, sz := range version.ObjectV2.PartActualSizes {
if len(version.ObjectV2.PartIndices) > i {
// 8<<20 is current 'compMinIndexSize', but we detach it in case it should change in the future.
if sz <= 8<<20 && len(version.ObjectV2.PartIndices[i]) > 0 {
changed = true
version.ObjectV2.PartIndices[i] = nil
}
clearField = clearField && len(version.ObjectV2.PartIndices[i]) == 0
}
}
if changed {
// All empty, clear.
if clearField {
version.ObjectV2.PartIndices = nil
}
// Reindex since it was changed.
meta, err := version.MarshalMsg(make([]byte, 0, len(ver.meta)+10))
if err == nil {
// Override both if fine.
ver.header = version.header()
ver.meta = meta
}
}
}
}
}
// Fix inconsistent x-minio-internal-replication-timestamp by loading and reindexing. // Fix inconsistent x-minio-internal-replication-timestamp by loading and reindexing.
if metaV < 2 && ver.header.Type == DeleteType { if metaV < 2 && ver.header.Type == DeleteType {
// load (and convert) version. // load (and convert) version.
@@ -1629,7 +1675,7 @@ func (x *xlMetaV2) AddVersion(fi FileInfo) error {
} }
ventry.ObjectV2.PartNumbers[i] = fi.Parts[i].Number ventry.ObjectV2.PartNumbers[i] = fi.Parts[i].Number
ventry.ObjectV2.PartActualSizes[i] = fi.Parts[i].ActualSize ventry.ObjectV2.PartActualSizes[i] = fi.Parts[i].ActualSize
if len(ventry.ObjectV2.PartIndices) > 0 { if len(ventry.ObjectV2.PartIndices) > i {
ventry.ObjectV2.PartIndices[i] = fi.Parts[i].Index ventry.ObjectV2.PartIndices[i] = fi.Parts[i].Index
} }
} }
File diff suppressed because it is too large Load Diff
+23
View File
@@ -23,6 +23,7 @@ import (
"compress/gzip" "compress/gzip"
"context" "context"
"encoding/base64" "encoding/base64"
"encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -583,6 +584,28 @@ func Test_xlMetaV2Shallow_Load(t *testing.T) {
} }
// t.Logf("data := %#v\n", data) // t.Logf("data := %#v\n", data)
}) })
// Test compressed index consistency fix
t.Run("comp-index", func(t *testing.T) {
// This file has a compressed index, due to https://github.com/minio/minio/pull/20575
// We ensure it is rewritten without an index.
// We compare this against the signature of the files stored without a version.
data, err := base64.StdEncoding.DecodeString(`WEwyIAEAAwDGAAACKgMCAcQml8QQAAAAAAAAAAAAAAAAAAAAANMYGu+UIK7akcQEofwXhAECCAjFAfyDpFR5cGUBpVYyT2Jq3gASoklExBAAAAAAAAAAAAAAAAAAAAAApEREaXLEEFTyKFqhkkXVoWn+8R1Lr2ymRWNBbGdvAaNFY00Io0VjTginRWNCU2l6ZdIAEAAAp0VjSW5kZXgBpkVjRGlzdNwAEAECAwQFBgcICQoLDA0ODxCoQ1N1bUFsZ28BqFBhcnROdW1zkgECqVBhcnRFVGFnc8CpUGFydFNpemVzktIAFtgq0gAGvb+qUGFydEFTaXplc5LSAFKb69IAGZg0p1BhcnRJZHiSxFqKm+4h9J7JCYCAgAFEABSPlBzH5g6z9gah3wOPnwLDlAGeD+os0xbjFd8O8w+TBoM8rz6bHO0KzQWtBu4GwgGSBocH6QPUSu8J5A/8gwSWtQPOtgL0euoMmAPEAKRTaXpl0gAdlemlTVRpbWXTGBrvlCCu2pGnTWV0YVN5c4K8WC1NaW5pby1JbnRlcm5hbC1hY3R1YWwtc2l6ZcQHNzA5MTIzMbxYLU1pbmlvLUludGVybmFsLWNvbXByZXNzaW9uxBVrbGF1c3Bvc3QvY29tcHJlc3MvczKnTWV0YVVzcoKsY29udGVudC10eXBlqHRleHQvY3N2pGV0YWfZIjEzYmYyMDU0NGVjN2VmY2YxNzhiYWRmNjc4NzNjODg2LTKhds5mYYMqzv8Vdtk=`)
if err != nil {
t.Fatal(err)
}
var xl xlMetaV2
err = xl.Load(data)
if err != nil {
t.Fatal(err)
}
for _, v := range xl.versions {
// Signature should match
if binary.BigEndian.Uint32(v.header.Signature[:]) != 0x8e5a6406 {
t.Log(v.header.String())
t.Fatalf("invalid signature 0x%x", binary.BigEndian.Uint32(v.header.Signature[:]))
}
}
})
} }
func Test_xlMetaV2Shallow_LoadTimeStamp(t *testing.T) { func Test_xlMetaV2Shallow_LoadTimeStamp(t *testing.T) {
+3 -3
View File
@@ -378,11 +378,11 @@ func (x *xlMetaInlineData) remove(keys ...string) bool {
// xlMetaV2TrimData will trim any data from the metadata without unmarshalling it. // xlMetaV2TrimData will trim any data from the metadata without unmarshalling it.
// If any error occurs the unmodified data is returned. // If any error occurs the unmodified data is returned.
func xlMetaV2TrimData(buf []byte) []byte { func xlMetaV2TrimData(buf []byte) []byte {
metaBuf, min, maj, err := checkXL2V1(buf) metaBuf, maj, minor, err := checkXL2V1(buf)
if err != nil { if err != nil {
return buf return buf
} }
if maj == 1 && min < 1 { if maj == 1 && minor < 1 {
// First version to carry data. // First version to carry data.
return buf return buf
} }
@@ -393,7 +393,7 @@ func xlMetaV2TrimData(buf []byte) []byte {
return buf return buf
} }
// Skip CRC // Skip CRC
if maj > 1 || min >= 2 { if maj > 1 || minor >= 2 {
_, metaBuf, err = msgp.ReadUint32Bytes(metaBuf) _, metaBuf, err = msgp.ReadUint32Bytes(metaBuf)
storageLogIf(GlobalContext, err) storageLogIf(GlobalContext, err)
} }
+48 -47
View File
@@ -432,15 +432,19 @@ func (s *xlStorage) Healing() *healingTracker {
bucketMetaPrefix, healingTrackerFilename) bucketMetaPrefix, healingTrackerFilename)
b, err := os.ReadFile(healingFile) b, err := os.ReadFile(healingFile)
if err != nil { if err != nil {
if !errors.Is(err, os.ErrNotExist) {
internalLogIf(GlobalContext, fmt.Errorf("unable to read %s: %w", healingFile, err))
}
return nil return nil
} }
if len(b) == 0 { if len(b) == 0 {
internalLogIf(GlobalContext, fmt.Errorf("%s is empty", healingFile))
// 'healing.bin' might be truncated // 'healing.bin' might be truncated
return nil return nil
} }
h := newHealingTracker() h := newHealingTracker()
_, err = h.UnmarshalMsg(b) _, err = h.UnmarshalMsg(b)
bugLogIf(GlobalContext, err) internalLogIf(GlobalContext, err)
return h return h
} }
@@ -2246,6 +2250,7 @@ func (s *xlStorage) writeAllMeta(ctx context.Context, volume string, path string
return renameAll(tmpFilePath, filePath, volumeDir) return renameAll(tmpFilePath, filePath, volumeDir)
} }
// Create or truncate an existing file before writing
func (s *xlStorage) writeAllInternal(ctx context.Context, filePath string, b []byte, sync bool, skipParent string) (err error) { func (s *xlStorage) writeAllInternal(ctx context.Context, filePath string, b []byte, sync bool, skipParent string) (err error) {
flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC flags := os.O_CREATE | os.O_WRONLY | os.O_TRUNC
@@ -2268,16 +2273,11 @@ func (s *xlStorage) writeAllInternal(ctx context.Context, filePath string, b []b
return err return err
} }
n, err := w.Write(b) _, err = w.Write(b)
if err != nil { if err != nil {
w.Close()
return err
}
if n != len(b) {
w.Truncate(0) // to indicate that we did partial write. w.Truncate(0) // to indicate that we did partial write.
w.Close() w.Close()
return io.ErrShortWrite return err
} }
// Dealing with error returns from close() - 'man 2 close' // Dealing with error returns from close() - 'man 2 close'
@@ -2367,6 +2367,41 @@ func (s *xlStorage) AppendFile(ctx context.Context, volume string, path string,
return nil return nil
} }
// checkPart is a light check of an existing and size of a part, without doing a bitrot operation
// For any unexpected error, return checkPartUnknown (zero)
func (s *xlStorage) checkPart(volumeDir, path, dataDir string, partNum int, expectedSize int64, skipAccessCheck bool) (resp int) {
partPath := pathJoin(path, dataDir, fmt.Sprintf("part.%d", partNum))
filePath := pathJoin(volumeDir, partPath)
st, err := Lstat(filePath)
if err != nil {
if osIsNotExist(err) {
if !skipAccessCheck {
// Stat a volume entry.
if verr := Access(volumeDir); verr != nil {
if osIsNotExist(verr) {
resp = checkPartVolumeNotFound
}
return
}
}
}
if osErrToFileErr(err) == errFileNotFound {
resp = checkPartFileNotFound
}
return
}
if st.Mode().IsDir() {
resp = checkPartFileNotFound
return
}
// Check if shard is truncated.
if st.Size() < expectedSize {
resp = checkPartFileCorrupt
return
}
return checkPartSuccess
}
// CheckParts check if path has necessary parts available. // CheckParts check if path has necessary parts available.
func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) { func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string, fi FileInfo) (*CheckPartsResp, error) {
volumeDir, err := s.getVolDir(volume) volumeDir, err := s.getVolDir(volume)
@@ -2385,36 +2420,12 @@ func (s *xlStorage) CheckParts(ctx context.Context, volume string, path string,
} }
for i, part := range fi.Parts { for i, part := range fi.Parts {
partPath := pathJoin(path, fi.DataDir, fmt.Sprintf("part.%d", part.Number)) resp.Results[i], err = xioutil.WithDeadline[int](ctx, globalDriveConfig.GetMaxTimeout(), func(ctx context.Context) (int, error) {
filePath := pathJoin(volumeDir, partPath) return s.checkPart(volumeDir, path, fi.DataDir, part.Number, fi.Erasure.ShardFileSize(part.Size), skipAccessChecks(volume)), nil
st, err := Lstat(filePath) })
if err != nil { if err != nil {
if osIsNotExist(err) { return nil, err
if !skipAccessChecks(volume) {
// Stat a volume entry.
if verr := Access(volumeDir); verr != nil {
if osIsNotExist(verr) {
resp.Results[i] = checkPartVolumeNotFound
}
continue
}
}
}
if osErrToFileErr(err) == errFileNotFound {
resp.Results[i] = checkPartFileNotFound
}
continue
} }
if st.Mode().IsDir() {
resp.Results[i] = checkPartFileNotFound
continue
}
// Check if shard is truncated.
if st.Size() < fi.Erasure.ShardFileSize(part.Size) {
resp.Results[i] = checkPartFileCorrupt
continue
}
resp.Results[i] = checkPartSuccess
} }
return &resp, nil return &resp, nil
@@ -2546,17 +2557,7 @@ func (s *xlStorage) Delete(ctx context.Context, volume string, path string, dele
} }
func skipAccessChecks(volume string) (ok bool) { func skipAccessChecks(volume string) (ok bool) {
for _, prefix := range []string{ return strings.HasPrefix(volume, minioMetaBucket)
minioMetaTmpDeletedBucket,
minioMetaTmpBucket,
minioMetaMultipartBucket,
minioMetaBucket,
} {
if strings.HasPrefix(volume, prefix) {
return true
}
}
return ok
} }
// RenameData - rename source path to destination path atomically, metadata and data directory. // RenameData - rename source path to destination path atomically, metadata and data directory.
+2 -2
View File
@@ -18,7 +18,7 @@ docker buildx build --push --no-cache \
-t "quay.io/minio/minio:latest" \ -t "quay.io/minio/minio:latest" \
-t "minio/minio:${release}" \ -t "minio/minio:${release}" \
-t "quay.io/minio/minio:${release}" \ -t "quay.io/minio/minio:${release}" \
--platform=linux/arm64,linux/amd64,linux/ppc64le,linux/s390x \ --platform=linux/arm64,linux/amd64,linux/ppc64le \
-f Dockerfile.release . -f Dockerfile.release .
docker buildx prune -f docker buildx prune -f
@@ -27,7 +27,7 @@ docker buildx build --push --no-cache \
--build-arg RELEASE="${release}" \ --build-arg RELEASE="${release}" \
-t "minio/minio:${release}-cpuv1" \ -t "minio/minio:${release}-cpuv1" \
-t "quay.io/minio/minio:${release}-cpuv1" \ -t "quay.io/minio/minio:${release}-cpuv1" \
--platform=linux/arm64,linux/amd64,linux/ppc64le,linux/s390x \ --platform=linux/arm64,linux/amd64,linux/ppc64le \
-f Dockerfile.release.old_cpu . -f Dockerfile.release.old_cpu .
docker buildx prune -f docker buildx prune -f

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